25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-24 16:23:40 +01:00

Accumulated patch to binarize and indent code

This commit is contained in:
Badlop 2013-03-14 10:33:02 +01:00
parent 9c41abde10
commit 9deb294328
185 changed files with 49979 additions and 42974 deletions

View File

@ -35,7 +35,7 @@ ERLANG_CFLAGS += @ERLANG_SSLVER@
# make debug=true to compile Erlang module with debug informations. # make debug=true to compile Erlang module with debug informations.
ifdef debug ifdef debug
EFLAGS+=+debug_info +export_all EFLAGS+=+debug_info
endif endif
DEBUGTOOLS = p1_prof.erl DEBUGTOOLS = p1_prof.erl
@ -79,7 +79,7 @@ exec_prefix = @exec_prefix@
SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ @mod_proxy65@ @eldap@ @pam@ @web@ stringprep stun @tls@ @odbc@ @ejabberd_zlib@ SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ @mod_proxy65@ @eldap@ @pam@ @web@ stringprep stun @tls@ @odbc@ @ejabberd_zlib@
ERLSHLIBS += expat_erl.so ERLSHLIBS += expat_erl.so
ERLBEHAVS = cyrsasl.erl gen_mod.erl p1_fsm.erl ERLBEHAVS = cyrsasl.erl gen_mod.erl p1_fsm.erl ejabberd_auth.erl
SOURCES_ALL = $(wildcard *.erl) SOURCES_ALL = $(wildcard *.erl)
SOURCES_MISC = $(ERLBEHAVS) $(DEBUGTOOLS) SOURCES_MISC = $(ERLBEHAVS) $(DEBUGTOOLS)
SOURCES += $(filter-out $(SOURCES_MISC),$(SOURCES_ALL)) SOURCES += $(filter-out $(SOURCES_MISC),$(SOURCES_ALL))

View File

@ -25,30 +25,58 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(acl). -module(acl).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-export([start/0, -export([start/0, to_record/3, add/3, add_list/3,
to_record/3, match_rule/3, match_acl/3]).
add/3,
add_list/3,
match_rule/3,
% for debugging only
match_acl/3]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl").
-record(acl, {aclname, aclspec}). -record(acl, {aclname, aclspec}).
-type regexp() :: binary().
-type glob() :: binary().
-type aclname() :: {atom(), binary() | global}.
-type aclspec() :: all | none |
{user, binary()} |
{user, binary(), binary()} |
{server, binary()} |
{resource, binary()} |
{user_regexp, regexp()} |
{shared_group, binary()} |
{shared_group, binary(), binary()} |
{user_regexp, regexp(), binary()} |
{server_regexp, regexp()} |
{resource_regexp, regexp()} |
{node_regexp, regexp(), regexp()} |
{user_glob, glob()} |
{user_glob, glob(), binary()} |
{server_glob, glob()} |
{resource_glob, glob()} |
{node_glob, glob(), glob()}.
-type acl() :: #acl{aclname :: aclname(),
aclspec :: aclspec()}.
-export_type([acl/0]).
start() -> start() ->
mnesia:create_table(acl, mnesia:create_table(acl,
[{disc_copies, [node()]}, [{disc_copies, [node()]}, {type, bag},
{type, bag},
{attributes, record_info(fields, acl)}]), {attributes, record_info(fields, acl)}]),
mnesia:add_table_copy(acl, node(), ram_copies), mnesia:add_table_copy(acl, node(), ram_copies),
update_table(),
ok. ok.
-spec to_record(binary(), atom(), aclspec()) -> acl().
to_record(Host, ACLName, ACLSpec) -> to_record(Host, ACLName, ACLSpec) ->
#acl{aclname = {ACLName, Host}, aclspec = normalize_spec(ACLSpec)}. #acl{aclname = {ACLName, Host},
aclspec = normalize_spec(ACLSpec)}.
-spec add(binary(), aclname(), aclspec()) -> {atomic, ok} | {aborted, any()}.
add(Host, ACLName, ACLSpec) -> add(Host, ACLName, ACLSpec) ->
F = fun () -> F = fun () ->
@ -57,102 +85,100 @@ add(Host, ACLName, ACLSpec) ->
end, end,
mnesia:transaction(F). mnesia:transaction(F).
-spec add_list(binary(), [acl()], boolean()) -> false | ok.
add_list(Host, ACLs, Clear) -> add_list(Host, ACLs, Clear) ->
F = fun () -> F = fun () ->
if if Clear ->
Clear -> Ks = mnesia:select(acl,
Ks = mnesia:select( [{{acl, {'$1', Host}, '$2'}, [],
acl, [{{acl, {'$1', Host}, '$2'}, [], ['$1']}]), ['$1']}]),
lists:foreach(fun(K) -> lists:foreach(fun (K) -> mnesia:delete({acl, {K, Host}})
mnesia:delete({acl, {K, Host}}) end,
end, Ks); Ks);
true -> true -> ok
ok
end, end,
lists:foreach(fun (ACL) -> lists:foreach(fun (ACL) ->
case ACL of case ACL of
#acl{aclname = ACLName, #acl{aclname = ACLName,
aclspec = ACLSpec} -> aclspec = ACLSpec} ->
mnesia:write( mnesia:write(#acl{aclname =
#acl{aclname = {ACLName, Host}, {ACLName,
aclspec = normalize_spec(ACLSpec)}) Host},
aclspec =
normalize_spec(ACLSpec)})
end end
end, ACLs) end,
ACLs)
end, end,
case mnesia:transaction(F) of case mnesia:transaction(F) of
{atomic, _} -> {atomic, _} -> ok;
ok; _ -> false
_ ->
false
end. end.
normalize(A) -> normalize(A) -> jlib:nodeprep(iolist_to_binary(A)).
jlib:nodeprep(A).
normalize_spec({A, B}) -> normalize_spec({A, B}) -> {A, normalize(B)};
{A, normalize(B)};
normalize_spec({A, B, C}) -> normalize_spec({A, B, C}) ->
{A, normalize(B), normalize(C)}; {A, normalize(B), normalize(C)};
normalize_spec(all) -> normalize_spec(all) -> all;
all; normalize_spec(none) -> none.
normalize_spec(none) ->
none.
-spec match_rule(global | binary(), atom(), jid() | ljid()) -> any().
match_rule(global, Rule, JID) -> match_rule(global, Rule, JID) ->
case Rule of case Rule of
all -> allow; all -> allow;
none -> deny; none -> deny;
_ -> _ ->
case ejabberd_config:get_global_option({access, Rule, global}) of case ejabberd_config:get_global_option(
undefined -> {access, Rule, global}, fun(V) -> V end)
deny; of
GACLs -> undefined -> deny;
match_acls(GACLs, JID, global) GACLs -> match_acls(GACLs, JID, global)
end end
end; end;
match_rule(Host, Rule, JID) -> match_rule(Host, Rule, JID) ->
case Rule of case Rule of
all -> allow; all -> allow;
none -> deny; none -> deny;
_ -> _ ->
case ejabberd_config:get_global_option({access, Rule, global}) of case ejabberd_config:get_global_option(
{access, Rule, global}, fun(V) -> V end)
of
undefined -> undefined ->
case ejabberd_config:get_global_option({access, Rule, Host}) of case ejabberd_config:get_global_option(
undefined -> {access, Rule, Host}, fun(V) -> V end)
deny; of
ACLs -> undefined -> deny;
match_acls(ACLs, JID, Host) ACLs -> match_acls(ACLs, JID, Host)
end; end;
GACLs -> GACLs ->
case ejabberd_config:get_global_option({access, Rule, Host}) of case ejabberd_config:get_global_option(
undefined -> {access, Rule, Host}, fun(V) -> V end)
match_acls(GACLs, JID, Host); of
undefined -> match_acls(GACLs, JID, Host);
ACLs -> ACLs ->
case lists:reverse(GACLs) of case lists:reverse(GACLs) of
[{allow, all} | Rest] -> [{allow, all} | Rest] ->
match_acls( match_acls(lists:reverse(Rest) ++
lists:reverse(Rest) ++ ACLs ++ ACLs ++ [{allow, all}],
[{allow, all}],
JID, Host); JID, Host);
_ -> _ -> match_acls(GACLs ++ ACLs, JID, Host)
match_acls(GACLs ++ ACLs, JID, Host)
end end
end end
end end
end. end.
match_acls([], _, _Host) -> match_acls([], _, _Host) -> deny;
deny;
match_acls([{Access, ACL} | ACLs], JID, Host) -> match_acls([{Access, ACL} | ACLs], JID, Host) ->
case match_acl(ACL, JID, Host) of case match_acl(ACL, JID, Host) of
true -> true -> Access;
Access; _ -> match_acls(ACLs, JID, Host)
_ ->
match_acls(ACLs, JID, Host)
end. end.
-spec match_acl(atom(), jid() | ljid(), binary()) -> boolean().
match_acl(ACL, JID, Host) -> match_acl(ACL, JID, Host) ->
case ACL of case ACL of
all -> true; all -> true;
@ -161,24 +187,19 @@ match_acl(ACL, JID, Host) ->
{User, Server, Resource} = jlib:jid_tolower(JID), {User, Server, Resource} = jlib:jid_tolower(JID),
lists:any(fun (#acl{aclspec = Spec}) -> lists:any(fun (#acl{aclspec = Spec}) ->
case Spec of case Spec of
all -> all -> true;
true;
{user, U} -> {user, U} ->
(U == User) U == User andalso
andalso (Host == Server orelse
((Host == Server) orelse Host == global andalso
((Host == global) andalso lists:member(Server, ?MYHOSTS));
lists:member(Server, ?MYHOSTS))); {user, U, S} -> U == User andalso S == Server;
{user, U, S} -> {server, S} -> S == Server;
(U == User) andalso (S == Server); {resource, R} -> R == Resource;
{server, S} ->
S == Server;
{resource, R} ->
R == Resource;
{user_regexp, UR} -> {user_regexp, UR} ->
((Host == Server) orelse (Host == Server orelse
((Host == global) andalso Host == global andalso
lists:member(Server, ?MYHOSTS))) lists:member(Server, ?MYHOSTS))
andalso is_regexp_match(User, UR); andalso is_regexp_match(User, UR);
{shared_group, G} -> {shared_group, G} ->
Mod = loaded_shared_roster_module(Host), Mod = loaded_shared_roster_module(Host),
@ -187,8 +208,7 @@ match_acl(ACL, JID, Host) ->
Mod = loaded_shared_roster_module(H), Mod = loaded_shared_roster_module(H),
Mod:is_user_in_group({User, Server}, G, H); Mod:is_user_in_group({User, Server}, G, H);
{user_regexp, UR, S} -> {user_regexp, UR, S} ->
(S == Server) andalso S == Server andalso is_regexp_match(User, UR);
is_regexp_match(User, UR);
{server_regexp, SR} -> {server_regexp, SR} ->
is_regexp_match(Server, SR); is_regexp_match(Server, SR);
{resource_regexp, RR} -> {resource_regexp, RR} ->
@ -197,25 +217,22 @@ match_acl(ACL, JID, Host) ->
is_regexp_match(Server, SR) andalso is_regexp_match(Server, SR) andalso
is_regexp_match(User, UR); is_regexp_match(User, UR);
{user_glob, UR} -> {user_glob, UR} ->
((Host == Server) orelse (Host == Server orelse
((Host == global) andalso Host == global andalso
lists:member(Server, ?MYHOSTS))) lists:member(Server, ?MYHOSTS))
andalso andalso is_glob_match(User, UR);
is_glob_match(User, UR);
{user_glob, UR, S} -> {user_glob, UR, S} ->
(S == Server) andalso S == Server andalso is_glob_match(User, UR);
is_glob_match(User, UR); {server_glob, SR} -> is_glob_match(Server, SR);
{server_glob, SR} ->
is_glob_match(Server, SR);
{resource_glob, RR} -> {resource_glob, RR} ->
is_glob_match(Resource, RR); is_glob_match(Resource, RR);
{node_glob, UR, SR} -> {node_glob, UR, SR} ->
is_glob_match(Server, SR) andalso is_glob_match(Server, SR) andalso
is_glob_match(User, UR); is_glob_match(User, UR);
WrongSpec -> WrongSpec ->
?ERROR_MSG( ?ERROR_MSG("Wrong ACL expression: ~p~nCheck your "
"Wrong ACL expression: ~p~n" "config file and reload it with the override_a"
"Check your config file and reload it with the override_acls option enabled", "cls option enabled",
[WrongSpec]), [WrongSpec]),
false false
end end
@ -226,24 +243,46 @@ match_acl(ACL, JID, Host) ->
is_regexp_match(String, RegExp) -> is_regexp_match(String, RegExp) ->
case ejabberd_regexp:run(String, RegExp) of case ejabberd_regexp:run(String, RegExp) of
nomatch -> nomatch -> false;
false; match -> true;
match ->
true;
{error, ErrDesc} -> {error, ErrDesc} ->
?ERROR_MSG( ?ERROR_MSG("Wrong regexp ~p in ACL: ~p",
"Wrong regexp ~p in ACL: ~p",
[RegExp, ErrDesc]), [RegExp, ErrDesc]),
false false
end. end.
is_glob_match(String, Glob) -> is_glob_match(String, Glob) ->
is_regexp_match(String, ejabberd_regexp:sh_to_awk(Glob)). is_regexp_match(String,
ejabberd_regexp:sh_to_awk(Glob)).
loaded_shared_roster_module(Host) -> loaded_shared_roster_module(Host) ->
case gen_mod:is_loaded(Host, mod_shared_roster_ldap) of case gen_mod:is_loaded(Host, mod_shared_roster_ldap) of
true -> true -> mod_shared_roster_ldap;
mod_shared_roster_ldap; false -> mod_shared_roster
false -> end.
mod_shared_roster
update_table() ->
Fields = record_info(fields, acl),
case mnesia:table_info(acl, attributes) of
Fields ->
ejabberd_config:convert_table_to_binary(
acl, Fields, bag,
fun(#acl{aclspec = Spec}) when is_tuple(Spec) ->
element(2, Spec);
(_) ->
'$next'
end,
fun(#acl{aclname = {ACLName, Host},
aclspec = Spec} = R) ->
NewHost = if Host == global ->
Host;
true ->
iolist_to_binary(Host)
end,
R#acl{aclname = {ACLName, NewHost},
aclspec = normalize_spec(Spec)}
end);
_ ->
?INFO_MSG("Recreating acl table", []),
mnesia:transform_table(acl, ignore, Fields)
end. end.

View File

@ -25,11 +25,14 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(adhoc). -module(adhoc).
-author('henoch@dtek.chalmers.se'). -author('henoch@dtek.chalmers.se').
-export([parse_request/1, -export([
parse_request/1,
produce_response/2, produce_response/2,
produce_response/1]). produce_response/1
]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl"). -include("jlib.hrl").
@ -37,93 +40,121 @@
%% Parse an ad-hoc request. Return either an adhoc_request record or %% Parse an ad-hoc request. Return either an adhoc_request record or
%% an {error, ErrorType} tuple. %% an {error, ErrorType} tuple.
%%
-spec(parse_request/1 ::
(
IQ :: iq_request())
-> adhoc_response()
%%
| {error, _}
).
parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}) -> parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}) ->
?DEBUG("entering parse_request...", []), ?DEBUG("entering parse_request...", []),
Node = xml:get_tag_attr_s("node", SubEl), Node = xml:get_tag_attr_s(<<"node">>, SubEl),
SessionID = xml:get_tag_attr_s("sessionid", SubEl), SessionID = xml:get_tag_attr_s(<<"sessionid">>, SubEl),
Action = xml:get_tag_attr_s("action", SubEl), Action = xml:get_tag_attr_s(<<"action">>, SubEl),
XData = find_xdata_el(SubEl), XData = find_xdata_el(SubEl),
{xmlelement, _, _, AllEls} = SubEl, #xmlel{children = AllEls} = SubEl,
Others = case XData of Others = case XData of
false -> false -> AllEls;
AllEls; _ -> lists:delete(XData, AllEls)
_ ->
lists:delete(XData, AllEls)
end, end,
#adhoc_request{
#adhoc_request{lang = Lang, lang = Lang,
node = Node, node = Node,
sessionid = SessionID, sessionid = SessionID,
action = Action, action = Action,
xdata = XData, xdata = XData,
others = Others}; others = Others
parse_request(_) -> };
{error, ?ERR_BAD_REQUEST}. parse_request(_) -> {error, ?ERR_BAD_REQUEST}.
%% Borrowed from mod_vcard.erl %% Borrowed from mod_vcard.erl
find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) -> find_xdata_el(#xmlel{children = SubEls}) ->
find_xdata_el1(SubEls). find_xdata_el1(SubEls).
find_xdata_el1([]) -> find_xdata_el1([]) -> false;
false; find_xdata_el1([El | Els]) when is_record(El, xmlel) ->
find_xdata_el1([{xmlelement, Name, Attrs, SubEls} | Els]) -> case xml:get_tag_attr_s(<<"xmlns">>, El) of
case xml:get_attr_s("xmlns", Attrs) of ?NS_XDATA -> El;
?NS_XDATA -> _ -> find_xdata_el1(Els)
{xmlelement, Name, Attrs, SubEls};
_ ->
find_xdata_el1(Els)
end; end;
find_xdata_el1([_ | Els]) -> find_xdata_el1([_ | Els]) -> find_xdata_el1(Els).
find_xdata_el1(Els).
%% Produce a <command/> node to use as response from an adhoc_response %% Produce a <command/> node to use as response from an adhoc_response
%% record, filling in values for language, node and session id from %% record, filling in values for language, node and session id from
%% the request. %% the request.
produce_response(#adhoc_request{lang = Lang, %%
node = Node, -spec(produce_response/2 ::
sessionid = SessionID}, (
Response) -> Adhoc_Request :: adhoc_request(),
produce_response(Response#adhoc_response{lang = Lang, Adhoc_Response :: adhoc_response())
node = Node, -> Xmlel::xmlel()
sessionid = SessionID}). ).
%% Produce a <command/> node to use as response from an adhoc_response %% Produce a <command/> node to use as response from an adhoc_response
%% record. %% record.
produce_response(#adhoc_response{lang = _Lang, produce_response(#adhoc_request{lang = Lang, node = Node, sessionid = SessionID},
Adhoc_Response) ->
produce_response(Adhoc_Response#adhoc_response{
lang = Lang, node = Node, sessionid = SessionID
}).
%%
-spec(produce_response/1 ::
(
Adhoc_Response::adhoc_response())
-> Xmlel::xmlel()
).
produce_response(
#adhoc_response{
%lang = _Lang,
node = Node, node = Node,
sessionid = ProvidedSessionID, sessionid = ProvidedSessionID,
status = Status, status = Status,
defaultaction = DefaultAction, defaultaction = DefaultAction,
actions = Actions, actions = Actions,
notes = Notes, notes = Notes,
elements = Elements}) -> elements = Elements
SessionID = if is_list(ProvidedSessionID), ProvidedSessionID /= "" -> }) ->
ProvidedSessionID; SessionID = if is_binary(ProvidedSessionID),
true -> ProvidedSessionID /= <<"">> -> ProvidedSessionID;
jlib:now_to_utc_string(now()) true -> jlib:now_to_utc_string(now())
end, end,
case Actions of case Actions of
[] -> [] ->
ActionsEls = []; ActionsEls = [];
_ -> _ ->
case DefaultAction of case DefaultAction of
"" -> <<"">> -> ActionsElAttrs = [];
ActionsElAttrs = []; _ -> ActionsElAttrs = [{<<"execute">>, DefaultAction}]
_ ->
ActionsElAttrs = [{"execute", DefaultAction}]
end, end,
ActionsEls = [{xmlelement, "actions", ActionsEls = [
ActionsElAttrs, #xmlel{
[{xmlelement, Action, [], []} || Action <- Actions]}] name = <<"actions">>,
attrs = ActionsElAttrs,
children = [
#xmlel{name = Action, attrs = [], children = []}
|| Action <- Actions]
}
]
end, end,
NotesEls = lists:map(fun({Type, Text}) -> NotesEls = lists:map(fun({Type, Text}) ->
{xmlelement, "note", #xmlel{
[{"type", Type}], name = <<"note">>,
[{xmlcdata, Text}]} attrs = [{<<"type">>, Type}],
children = [{xmlcdata, Text}]
}
end, Notes), end, Notes),
{xmlelement, "command", #xmlel{
[{"xmlns", ?NS_COMMANDS}, name = <<"command">>,
{"sessionid", SessionID}, attrs = [
{"node", Node}, {<<"xmlns">>, ?NS_COMMANDS},
{"status", atom_to_list(Status)}], {<<"sessionid">>, SessionID},
ActionsEls ++ NotesEls ++ Elements}. {<<"node">>, Node},
{<<"status">>, iolist_to_binary(atom_to_list(Status))}
],
children = ActionsEls ++ NotesEls ++ Elements
}.

View File

@ -19,18 +19,27 @@
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-record(adhoc_request, {lang, -record(adhoc_request,
node, {
sessionid, lang = <<"">> :: binary(),
action, node = <<"">> :: binary(),
xdata, sessionid = <<"">> :: binary(),
others}). action = <<"">> :: binary(),
xdata = false :: false | xmlel(),
others = [] :: [xmlel()]
}).
-record(adhoc_response, {lang, -record(adhoc_response,
node, {
sessionid, lang = <<"">> :: binary(),
status, node = <<"">> :: binary(),
defaultaction = "", sessionid = <<"">> :: binary(),
actions = [], status :: atom(),
notes = [], defaultaction = <<"">> :: binary(),
elements = []}). actions = [] :: [binary()],
notes = [] :: [{binary(), binary()}],
elements = [] :: [xmlel()]
}).
-type adhoc_request() :: #adhoc_request{}.
-type adhoc_response() :: #adhoc_response{}.

View File

@ -380,11 +380,11 @@ do_setopts(#state{procs_num = N} = State, Opts) ->
shrink_size = ShrinkSize}. shrink_size = ShrinkSize}.
get_proc_num() -> get_proc_num() ->
case erlang:system_info(logical_processors) of case catch erlang:system_info(logical_processors) of
unknown -> Num when is_integer(Num) ->
1; Num;
Num -> _ ->
Num 1
end. end.
get_proc_by_hash(Tab, Term) -> get_proc_by_hash(Tab, Term) ->

View File

@ -62,7 +62,7 @@ start() ->
RootDirS = "ERLANG_DIR = " ++ code:root_dir() ++ "\n", RootDirS = "ERLANG_DIR = " ++ code:root_dir() ++ "\n",
%% Load the ejabberd application description so that ?VERSION can read the vsn key %% Load the ejabberd application description so that ?VERSION can read the vsn key
application:load(ejabberd), application:load(ejabberd),
Version = "EJABBERD_VERSION = " ++ ?VERSION ++ "\n", Version = "EJABBERD_VERSION = " ++ binary_to_list(?VERSION) ++ "\n",
ExpatDir = "EXPAT_DIR = c:\\sdk\\Expat-2.0.0\n", ExpatDir = "EXPAT_DIR = c:\\sdk\\Expat-2.0.0\n",
OpenSSLDir = "OPENSSL_DIR = c:\\sdk\\OpenSSL\n", OpenSSLDir = "OPENSSL_DIR = c:\\sdk\\OpenSSL\n",
DBType = "DBTYPE = generic\n", %% 'generic' or 'mssql' DBType = "DBTYPE = generic\n", %% 'generic' or 'mssql'

View File

@ -25,32 +25,57 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(cyrsasl). -module(cyrsasl).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-export([start/0, -export([start/0, register_mechanism/3, listmech/1,
register_mechanism/3, server_new/7, server_start/3, server_step/2]).
listmech/1,
server_new/7,
server_start/3,
server_step/2]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-record(sasl_mechanism, {mechanism, module, password_type}). %%
-record(sasl_state, {service, myname, realm, -export_type([
get_password, check_password, check_password_digest, mechanism/0,
mech_mod, mech_state}). mechanisms/0,
sasl_mechanism/0
]).
-export([behaviour_info/1]). -record(sasl_mechanism,
{mechanism = <<"">> :: mechanism() | '$1',
module :: atom(),
password_type = plain :: password_type() | '$2'}).
behaviour_info(callbacks) -> -type(mechanism() :: binary()).
[{mech_new, 4}, {mech_step, 2}]; -type(mechanisms() :: [mechanism(),...]).
behaviour_info(_Other) -> -type(password_type() :: plain | digest | scram).
undefined. -type(props() :: [{username, binary()} |
{authzid, binary()} |
{auth_module, atom()}]).
-type(sasl_mechanism() :: #sasl_mechanism{}).
-record(sasl_state,
{
service,
myname,
realm,
get_password,
check_password,
check_password_digest,
mech_mod,
mech_state
}).
-callback mech_new(binary(), fun(), fun(), fun()) -> any().
-callback mech_step(any(), binary()) -> {ok, props()} |
{ok, props(), binary()} |
{continue, binary(), any()} |
{error, binary()} |
{error, binary(), binary()}.
start() -> start() ->
ets:new(sasl_mechanism, [named_table, ets:new(sasl_mechanism,
public, [named_table, public,
{keypos, #sasl_mechanism.mechanism}]), {keypos, #sasl_mechanism.mechanism}]),
cyrsasl_plain:start([]), cyrsasl_plain:start([]),
cyrsasl_digest:start([]), cyrsasl_digest:start([]),
@ -58,10 +83,18 @@ start() ->
cyrsasl_anonymous:start([]), cyrsasl_anonymous:start([]),
ok. ok.
%%
-spec(register_mechanism/3 ::
(
Mechanim :: mechanism(),
Module :: module(),
PasswordType :: password_type())
-> any()
).
register_mechanism(Mechanism, Module, PasswordType) -> register_mechanism(Mechanism, Module, PasswordType) ->
ets:insert(sasl_mechanism, ets:insert(sasl_mechanism,
#sasl_mechanism{mechanism = Mechanism, #sasl_mechanism{mechanism = Mechanism, module = Module,
module = Module,
password_type = PasswordType}). password_type = PasswordType}).
%%% TODO: use callbacks %%% TODO: use callbacks
@ -89,62 +122,60 @@ register_mechanism(Mechanism, Module, PasswordType) ->
%% end. %% end.
check_credentials(_State, Props) -> check_credentials(_State, Props) ->
User = xml:get_attr_s(username, Props), User = proplists:get_value(username, Props, <<>>),
case jlib:nodeprep(User) of case jlib:nodeprep(User) of
error -> error -> {error, <<"not-authorized">>};
{error, "not-authorized"}; <<"">> -> {error, <<"not-authorized">>};
"" -> _LUser -> ok
{error, "not-authorized"};
_LUser ->
ok
end. end.
-spec(listmech/1 ::
(
Host ::binary())
-> Mechanisms::mechanisms()
).
listmech(Host) -> listmech(Host) ->
Mechs = ets:select(sasl_mechanism, Mechs = ets:select(sasl_mechanism,
[{#sasl_mechanism{mechanism = '$1', [{#sasl_mechanism{mechanism = '$1',
password_type = '$2', password_type = '$2', _ = '_'},
_ = '_'},
case catch ejabberd_auth:store_type(Host) of case catch ejabberd_auth:store_type(Host) of
external -> external -> [{'==', '$2', plain}];
[{'==', '$2', plain}]; scram -> [{'/=', '$2', digest}];
scram ->
[{'/=', '$2', digest}];
{'EXIT', {undef, [{Module, store_type, []} | _]}} -> {'EXIT', {undef, [{Module, store_type, []} | _]}} ->
?WARNING_MSG("~p doesn't implement the function store_type/0", [Module]), ?WARNING_MSG("~p doesn't implement the function store_type/0",
[Module]),
[]; [];
_Else -> _Else -> []
[]
end, end,
['$1']}]), ['$1']}]),
filter_anonymous(Host, Mechs). filter_anonymous(Host, Mechs).
server_new(Service, ServerFQDN, UserRealm, _SecFlags, server_new(Service, ServerFQDN, UserRealm, _SecFlags,
GetPassword, CheckPassword, CheckPasswordDigest) -> GetPassword, CheckPassword, CheckPasswordDigest) ->
#sasl_state{service = Service, #sasl_state{service = Service, myname = ServerFQDN,
myname = ServerFQDN, realm = UserRealm, get_password = GetPassword,
realm = UserRealm,
get_password = GetPassword,
check_password = CheckPassword, check_password = CheckPassword,
check_password_digest = CheckPasswordDigest}. check_password_digest = CheckPasswordDigest}.
server_start(State, Mech, ClientIn) -> server_start(State, Mech, ClientIn) ->
case lists:member(Mech, listmech(State#sasl_state.myname)) of case lists:member(Mech,
listmech(State#sasl_state.myname))
of
true -> true ->
case ets:lookup(sasl_mechanism, Mech) of case ets:lookup(sasl_mechanism, Mech) of
[#sasl_mechanism{module = Module}] -> [#sasl_mechanism{module = Module}] ->
{ok, MechState} = Module:mech_new( {ok, MechState} =
State#sasl_state.myname, Module:mech_new(State#sasl_state.myname,
State#sasl_state.get_password, State#sasl_state.get_password,
State#sasl_state.check_password, State#sasl_state.check_password,
State#sasl_state.check_password_digest), State#sasl_state.check_password_digest),
server_step(State#sasl_state{mech_mod = Module, server_step(State#sasl_state{mech_mod = Module,
mech_state = MechState}, mech_state = MechState},
ClientIn); ClientIn);
_ -> _ -> {error, <<"no-mechanism">>}
{error, "no-mechanism"}
end; end;
false -> false -> {error, <<"no-mechanism">>}
{error, "no-mechanism"}
end. end.
server_step(State, ClientIn) -> server_step(State, ClientIn) ->
@ -153,21 +184,16 @@ server_step(State, ClientIn) ->
case Module:mech_step(MechState, ClientIn) of case Module:mech_step(MechState, ClientIn) of
{ok, Props} -> {ok, Props} ->
case check_credentials(State, Props) of case check_credentials(State, Props) of
ok -> ok -> {ok, Props};
{ok, Props}; {error, Error} -> {error, Error}
{error, Error} ->
{error, Error}
end; end;
{ok, Props, ServerOut} -> {ok, Props, ServerOut} ->
case check_credentials(State, Props) of case check_credentials(State, Props) of
ok -> ok -> {ok, Props, ServerOut};
{ok, Props, ServerOut}; {error, Error} -> {error, Error}
{error, Error} ->
{error, Error}
end; end;
{continue, ServerOut, NewMechState} -> {continue, ServerOut, NewMechState} ->
{continue, ServerOut, {continue, ServerOut, State#sasl_state{mech_state = NewMechState}};
State#sasl_state{mech_state = NewMechState}};
{error, Error, Username} -> {error, Error, Username} ->
{error, Error, Username}; {error, Error, Username};
{error, Error} -> {error, Error} ->
@ -176,8 +202,16 @@ server_step(State, ClientIn) ->
%% Remove the anonymous mechanism from the list if not enabled for the given %% Remove the anonymous mechanism from the list if not enabled for the given
%% host %% host
%%
-spec(filter_anonymous/2 ::
(
Host :: binary(),
Mechs :: mechanisms())
-> mechanisms()
).
filter_anonymous(Host, Mechs) -> filter_anonymous(Host, Mechs) ->
case ejabberd_auth_anonymous:is_sasl_anonymous_enabled(Host) of case ejabberd_auth_anonymous:is_sasl_anonymous_enabled(Host) of
true -> Mechs; true -> Mechs;
false -> Mechs -- ["ANONYMOUS"] false -> Mechs -- [<<"ANONYMOUS">>]
end. end.

View File

@ -31,26 +31,20 @@
-behaviour(cyrsasl). -behaviour(cyrsasl).
-record(state, {server}). -record(state, {server = <<"">> :: binary()}).
start(_Opts) -> start(_Opts) ->
cyrsasl:register_mechanism("ANONYMOUS", ?MODULE, plain), cyrsasl:register_mechanism(<<"ANONYMOUS">>, ?MODULE, plain),
ok. ok.
stop() -> stop() -> ok.
ok.
mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) -> mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
{ok, #state{server = Host}}. {ok, #state{server = Host}}.
mech_step(State, _ClientIn) -> mech_step(#state{server = Server}, _ClientIn) ->
%% We generate a random username: User = iolist_to_binary([randoms:get_string() | tuple_to_list(now())]),
User = lists:concat([randoms:get_string() | tuple_to_list(now())]),
Server = State#state.server,
%% Checks that the username is available
case ejabberd_auth:is_user_exists(User, Server) of case ejabberd_auth:is_user_exists(User, Server) of
true -> {error, "not-authorized"}; true -> {error, <<"not-authorized">>};
false -> {ok, [{username, User}, false -> {ok, [{username, User}, {auth_module, ejabberd_auth_anonymous}]}
{auth_module, ejabberd_auth_anonymous}]}
end. end.

View File

@ -25,134 +25,145 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(cyrsasl_digest). -module(cyrsasl_digest).
-author('alexey@sevcom.net'). -author('alexey@sevcom.net').
-export([start/1, -export([start/1, stop/0, mech_new/4, mech_step/2, parse/1]).
stop/0,
mech_new/4,
mech_step/2]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-behaviour(cyrsasl). -behaviour(cyrsasl).
-record(state, {step, nonce, username, authzid, get_password, check_password, auth_module, -type get_password_fun() :: fun((binary()) -> {false, any()} |
host, hostfqdn}). {binary(), atom()}).
-type check_password_fun() :: fun((binary(), binary(), binary(),
fun((binary()) -> binary())) ->
{boolean(), any()} |
false).
-record(state, {step = 1 :: 1 | 3 | 5,
nonce = <<"">> :: binary(),
username = <<"">> :: binary(),
authzid = <<"">> :: binary(),
get_password = fun(_) -> {false, <<>>} end :: get_password_fun(),
check_password = fun(_, _, _, _) -> false end :: check_password_fun(),
auth_module :: atom(),
host = <<"">> :: binary(),
hostfqdn = <<"">> :: binary()}).
start(_Opts) -> start(_Opts) ->
Fqdn = get_local_fqdn(), Fqdn = get_local_fqdn(),
?INFO_MSG("FQDN used to check DIGEST-MD5 SASL authentication: ~p", [Fqdn]), ?INFO_MSG("FQDN used to check DIGEST-MD5 SASL authentication: ~s",
cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE, digest). [Fqdn]),
cyrsasl:register_mechanism(<<"DIGEST-MD5">>, ?MODULE,
digest).
stop() -> stop() -> ok.
ok.
mech_new(Host, GetPassword, _CheckPassword, CheckPasswordDigest) -> mech_new(Host, GetPassword, _CheckPassword,
{ok, #state{step = 1, CheckPasswordDigest) ->
nonce = randoms:get_string(), {ok,
host = Host, #state{step = 1, nonce = randoms:get_string(),
hostfqdn = get_local_fqdn(), host = Host, hostfqdn = get_local_fqdn(),
get_password = GetPassword, get_password = GetPassword,
check_password = CheckPasswordDigest}}. check_password = CheckPasswordDigest}}.
mech_step(#state{step = 1, nonce = Nonce} = State, _) -> mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
{continue, {continue,
"nonce=\"" ++ Nonce ++ <<"nonce=\"", Nonce/binary,
"\",qop=\"auth\",charset=utf-8,algorithm=md5-sess", "\",qop=\"auth\",charset=utf-8,algorithm=md5-sess">>,
State#state{step = 3}}; State#state{step = 3}};
mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) -> mech_step(#state{step = 3, nonce = Nonce} = State,
ClientIn) ->
case parse(ClientIn) of case parse(ClientIn) of
bad -> bad -> {error, <<"bad-protocol">>};
{error, "bad-protocol"};
KeyVals -> KeyVals ->
DigestURI = xml:get_attr_s("digest-uri", KeyVals), DigestURI = proplists:get_value(<<"digest-uri">>, KeyVals, <<>>),
UserName = xml:get_attr_s("username", KeyVals), %DigestURI = xml:get_attr_s(<<"digest-uri">>, KeyVals),
case is_digesturi_valid(DigestURI, State#state.host, State#state.hostfqdn) of UserName = proplists:get_value(<<"username">>, KeyVals, <<>>),
%UserName = xml:get_attr_s(<<"username">>, KeyVals),
case is_digesturi_valid(DigestURI, State#state.host,
State#state.hostfqdn)
of
false -> false ->
?DEBUG("User login not authorized because digest-uri " ?DEBUG("User login not authorized because digest-uri "
"seems invalid: ~p (checking for Host ~p, FQDN ~p)", [DigestURI, "seems invalid: ~p (checking for Host "
State#state.host, State#state.hostfqdn]), "~p, FQDN ~p)",
{error, "not-authorized", UserName}; [DigestURI, State#state.host, State#state.hostfqdn]),
{error, <<"not-authorized">>, UserName};
true -> true ->
AuthzId = xml:get_attr_s("authzid", KeyVals), AuthzId = proplists:get_value(<<"authzid">>, KeyVals, <<>>),
%AuthzId = xml:get_attr_s(<<"authzid">>, KeyVals),
case (State#state.get_password)(UserName) of case (State#state.get_password)(UserName) of
{false, _} -> {false, _} -> {error, <<"not-authorized">>, UserName};
{error, "not-authorized", UserName};
{Passwd, AuthModule} -> {Passwd, AuthModule} ->
case (State#state.check_password)(UserName, "", case (State#state.check_password)(UserName, <<"">>,
xml:get_attr_s("response", KeyVals), proplists:get_value(<<"response">>, KeyVals, <<>>),
fun(PW) -> response(KeyVals, UserName, PW, Nonce, AuthzId, %xml:get_attr_s(<<"response">>, KeyVals),
"AUTHENTICATE") end) of fun (PW) ->
response(KeyVals,
UserName,
PW,
Nonce,
AuthzId,
<<"AUTHENTICATE">>)
end)
of
{true, _} -> {true, _} ->
RspAuth = response(KeyVals, RspAuth = response(KeyVals, UserName, Passwd, Nonce,
UserName, Passwd, AuthzId, <<"">>),
Nonce, AuthzId, ""), {continue, <<"rspauth=", RspAuth/binary>>,
{continue, State#state{step = 5, auth_module = AuthModule,
"rspauth=" ++ RspAuth,
State#state{step = 5,
auth_module = AuthModule,
username = UserName, username = UserName,
authzid = AuthzId}}; authzid = AuthzId}};
false -> false -> {error, <<"not-authorized">>, UserName};
{error, "not-authorized", UserName}; {false, _} -> {error, <<"not-authorized">>, UserName}
{false, _} ->
{error, "not-authorized", UserName}
end end
end end
end end
end; end;
mech_step(#state{step = 5, mech_step(#state{step = 5, auth_module = AuthModule,
auth_module = AuthModule, username = UserName, authzid = AuthzId},
username = UserName, <<"">>) ->
authzid = AuthzId}, "") -> {ok,
{ok, [{username, UserName}, {authzid, AuthzId}, [{username, UserName}, {authzid, AuthzId},
{auth_module, AuthModule}]}; {auth_module, AuthModule}]};
mech_step(A, B) -> mech_step(A, B) ->
?DEBUG("SASL DIGEST: A ~p B ~p", [A, B]), ?DEBUG("SASL DIGEST: A ~p B ~p", [A, B]),
{error, "bad-protocol"}. {error, <<"bad-protocol">>}.
parse(S) -> parse(S) -> parse1(binary_to_list(S), "", []).
parse1(S, "", []).
parse1([$= | Cs], S, Ts) -> parse1([$= | Cs], S, Ts) ->
parse2(Cs, lists:reverse(S), "", Ts); parse2(Cs, lists:reverse(S), "", Ts);
parse1([$, | Cs], [], Ts) -> parse1([$, | Cs], [], Ts) -> parse1(Cs, [], Ts);
parse1(Cs, [], Ts); parse1([$\s | Cs], [], Ts) -> parse1(Cs, [], Ts);
parse1([$\s | Cs], [], Ts) -> parse1([C | Cs], S, Ts) -> parse1(Cs, [C | S], Ts);
parse1(Cs, [], Ts); parse1([], [], T) -> lists:reverse(T);
parse1([C | Cs], S, Ts) -> parse1([], _S, _T) -> bad.
parse1(Cs, [C | S], Ts);
parse1([], [], T) ->
lists:reverse(T);
parse1([], _S, _T) ->
bad.
parse2([$\" | Cs], Key, Val, Ts) -> parse2([$" | Cs], Key, Val, Ts) ->
parse3(Cs, Key, Val, Ts); parse3(Cs, Key, Val, Ts);
parse2([C | Cs], Key, Val, Ts) -> parse2([C | Cs], Key, Val, Ts) ->
parse4(Cs, Key, [C | Val], Ts); parse4(Cs, Key, [C | Val], Ts);
parse2([], _, _, _) -> parse2([], _, _, _) -> bad.
bad.
parse3([$\" | Cs], Key, Val, Ts) -> parse3([$" | Cs], Key, Val, Ts) ->
parse4(Cs, Key, Val, Ts); parse4(Cs, Key, Val, Ts);
parse3([$\\, C | Cs], Key, Val, Ts) -> parse3([$\\, C | Cs], Key, Val, Ts) ->
parse3(Cs, Key, [C | Val], Ts); parse3(Cs, Key, [C | Val], Ts);
parse3([C | Cs], Key, Val, Ts) -> parse3([C | Cs], Key, Val, Ts) ->
parse3(Cs, Key, [C | Val], Ts); parse3(Cs, Key, [C | Val], Ts);
parse3([], _, _, _) -> parse3([], _, _, _) -> bad.
bad.
parse4([$, | Cs], Key, Val, Ts) -> parse4([$, | Cs], Key, Val, Ts) ->
parse1(Cs, "", [{Key, lists:reverse(Val)} | Ts]); parse1(Cs, "", [{list_to_binary(Key), list_to_binary(lists:reverse(Val))} | Ts]);
parse4([$\s | Cs], Key, Val, Ts) -> parse4([$\s | Cs], Key, Val, Ts) ->
parse4(Cs, Key, Val, Ts); parse4(Cs, Key, Val, Ts);
parse4([C | Cs], Key, Val, Ts) -> parse4([C | Cs], Key, Val, Ts) ->
parse4(Cs, Key, [C | Val], Ts); parse4(Cs, Key, [C | Val], Ts);
parse4([], Key, Val, Ts) -> parse4([], Key, Val, Ts) ->
parse1([], "", [{Key, lists:reverse(Val)} | Ts]).
%% @doc Check if the digest-uri is valid. %% @doc Check if the digest-uri is valid.
%% RFC-2831 allows to provide the IP address in Host, %% RFC-2831 allows to provide the IP address in Host,
%% however ejabberd doesn't allow that. %% however ejabberd doesn't allow that.
@ -162,14 +173,17 @@ parse4([], Key, Val, Ts) ->
%% xmpp/server3.example.org/jabber.example.org, xmpp/server3.example.org and %% xmpp/server3.example.org/jabber.example.org, xmpp/server3.example.org and
%% xmpp/jabber.example.org %% xmpp/jabber.example.org
%% The last version is not actually allowed by the RFC, but implemented by popular clients %% The last version is not actually allowed by the RFC, but implemented by popular clients
is_digesturi_valid(DigestURICase, JabberDomain, JabberFQDN) -> parse1([], "", [{list_to_binary(Key), list_to_binary(lists:reverse(Val))} | Ts]).
is_digesturi_valid(DigestURICase, JabberDomain,
JabberFQDN) ->
DigestURI = stringprep:tolower(DigestURICase), DigestURI = stringprep:tolower(DigestURICase),
case catch string:tokens(DigestURI, "/") of case catch str:tokens(DigestURI, <<"/">>) of
["xmpp", Host] -> [<<"xmpp">>, Host] ->
IsHostFqdn = is_host_fqdn(Host, JabberFQDN), IsHostFqdn = is_host_fqdn(binary_to_list(Host), binary_to_list(JabberFQDN)),
(Host == JabberDomain) or IsHostFqdn; (Host == JabberDomain) or IsHostFqdn;
["xmpp", Host, ServName] -> [<<"xmpp">>, Host, ServName] ->
IsHostFqdn = is_host_fqdn(Host, JabberFQDN), IsHostFqdn = is_host_fqdn(binary_to_list(Host), binary_to_list(JabberFQDN)),
(ServName == JabberDomain) and IsHostFqdn; (ServName == JabberDomain) and IsHostFqdn;
_ -> _ ->
false false
@ -185,62 +199,60 @@ is_host_fqdn(Host, [Fqdn | FqdnTail]) when Host /= Fqdn ->
is_host_fqdn(Host, FqdnTail). is_host_fqdn(Host, FqdnTail).
get_local_fqdn() -> get_local_fqdn() ->
case (catch get_local_fqdn2()) of case catch get_local_fqdn2() of
Str when is_list(Str) -> Str; Str when is_binary(Str) -> Str;
_ -> "unknown-fqdn, please configure fqdn option in ejabberd.cfg!" _ ->
end. <<"unknown-fqdn, please configure fqdn "
get_local_fqdn2() -> "option in ejabberd.cfg!">>
case ejabberd_config:get_local_option(fqdn) of
ConfiguredFqdn when is_list(ConfiguredFqdn) ->
ConfiguredFqdn;
_undefined ->
{ok, Hostname} = inet:gethostname(),
{ok, {hostent, Fqdn, _, _, _, _}} = inet:gethostbyname(Hostname),
Fqdn
end. end.
digit_to_xchar(D) when (D >= 0) and (D < 10) -> get_local_fqdn2() ->
D + 48; case ejabberd_config:get_local_option(
digit_to_xchar(D) -> fqdn, fun iolist_to_binary/1) of
D + 87. ConfiguredFqdn when is_binary(ConfiguredFqdn) ->
ConfiguredFqdn;
undefined ->
{ok, Hostname} = inet:gethostname(),
{ok, {hostent, Fqdn, _, _, _, _}} =
inet:gethostbyname(Hostname),
list_to_binary(Fqdn)
end.
hex(S) -> hex(S) ->
hex(S, []). sha:to_hexlist(S).
hex([], Res) -> proplists_get_bin_value(Key, Pairs, Default) ->
lists:reverse(Res); case proplists:get_value(Key, Pairs, Default) of
hex([N | Ns], Res) -> L when is_list(L) ->
hex(Ns, [digit_to_xchar(N rem 16), list_to_binary(L);
digit_to_xchar(N div 16) | Res]). L2 ->
L2
end.
response(KeyVals, User, Passwd, Nonce, AuthzId,
response(KeyVals, User, Passwd, Nonce, AuthzId, A2Prefix) -> A2Prefix) ->
Realm = xml:get_attr_s("realm", KeyVals), Realm = proplists_get_bin_value(<<"realm">>, KeyVals, <<>>),
CNonce = xml:get_attr_s("cnonce", KeyVals), CNonce = proplists_get_bin_value(<<"cnonce">>, KeyVals, <<>>),
DigestURI = xml:get_attr_s("digest-uri", KeyVals), DigestURI = proplists_get_bin_value(<<"digest-uri">>, KeyVals, <<>>),
NC = xml:get_attr_s("nc", KeyVals), NC = proplists_get_bin_value(<<"nc">>, KeyVals, <<>>),
QOP = xml:get_attr_s("qop", KeyVals), QOP = proplists_get_bin_value(<<"qop">>, KeyVals, <<>>),
MD5Hash = crypto:md5(<<User/binary, ":", Realm/binary, ":",
Passwd/binary>>),
A1 = case AuthzId of A1 = case AuthzId of
"" -> <<"">> ->
binary_to_list( <<MD5Hash/binary, ":", Nonce/binary, ":", CNonce/binary>>;
crypto:md5(User ++ ":" ++ Realm ++ ":" ++ Passwd)) ++
":" ++ Nonce ++ ":" ++ CNonce;
_ -> _ ->
binary_to_list( <<MD5Hash/binary, ":", Nonce/binary, ":", CNonce/binary, ":",
crypto:md5(User ++ ":" ++ Realm ++ ":" ++ Passwd)) ++ AuthzId/binary>>
":" ++ Nonce ++ ":" ++ CNonce ++ ":" ++ AuthzId
end, end,
A2 = case QOP of A2 = case QOP of
"auth" -> <<"auth">> ->
A2Prefix ++ ":" ++ DigestURI; <<A2Prefix/binary, ":", DigestURI/binary>>;
_ -> _ ->
A2Prefix ++ ":" ++ DigestURI ++ <<A2Prefix/binary, ":", DigestURI/binary,
":00000000000000000000000000000000" ":00000000000000000000000000000000">>
end, end,
T = hex(binary_to_list(crypto:md5(A1))) ++ ":" ++ Nonce ++ ":" ++ T = <<(hex((crypto:md5(A1))))/binary, ":", Nonce/binary,
NC ++ ":" ++ CNonce ++ ":" ++ QOP ++ ":" ++ ":", NC/binary, ":", CNonce/binary, ":", QOP/binary,
hex(binary_to_list(crypto:md5(A2))), ":", (hex((crypto:md5(A2))))/binary>>,
hex(binary_to_list(crypto:md5(T))). hex((crypto:md5(T))).

View File

@ -25,6 +25,7 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(cyrsasl_plain). -module(cyrsasl_plain).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-export([start/1, stop/0, mech_new/4, mech_step/2, parse/1]). -export([start/1, stop/0, mech_new/4, mech_step/2, parse/1]).
@ -34,11 +35,10 @@
-record(state, {check_password}). -record(state, {check_password}).
start(_Opts) -> start(_Opts) ->
cyrsasl:register_mechanism("PLAIN", ?MODULE, plain), cyrsasl:register_mechanism(<<"PLAIN">>, ?MODULE, plain),
ok. ok.
stop() -> stop() -> ok.
ok.
mech_new(_Host, _GetPassword, CheckPassword, _CheckPasswordDigest) -> mech_new(_Host, _GetPassword, CheckPassword, _CheckPasswordDigest) ->
{ok, #state{check_password = CheckPassword}}. {ok, #state{check_password = CheckPassword}}.
@ -48,53 +48,43 @@ mech_step(State, ClientIn) ->
[AuthzId, User, Password] -> [AuthzId, User, Password] ->
case (State#state.check_password)(User, Password) of case (State#state.check_password)(User, Password) of
{true, AuthModule} -> {true, AuthModule} ->
{ok, [{username, User}, {authzid, AuthzId}, {ok,
[{username, User}, {authzid, AuthzId},
{auth_module, AuthModule}]}; {auth_module, AuthModule}]};
_ -> _ -> {error, <<"not-authorized">>, User}
{error, "not-authorized", User}
end; end;
_ -> _ -> {error, <<"bad-protocol">>}
{error, "bad-protocol"}
end. end.
prepare(ClientIn) -> prepare(ClientIn) ->
case parse(ClientIn) of case parse(ClientIn) of
[[], UserMaybeDomain, Password] -> [<<"">>, UserMaybeDomain, Password] ->
case parse_domain(UserMaybeDomain) of case parse_domain(UserMaybeDomain) of
%% <NUL>login@domain<NUL>pwd %% <NUL>login@domain<NUL>pwd
[User, _Domain] -> [User, _Domain] -> [UserMaybeDomain, User, Password];
[UserMaybeDomain, User, Password];
%% <NUL>login<NUL>pwd %% <NUL>login<NUL>pwd
[User] -> [User] -> [<<"">>, User, Password]
["", User, Password]
end; end;
%% login@domain<NUL>login<NUL>pwd %% login@domain<NUL>login<NUL>pwd
[AuthzId, User, Password] -> [AuthzId, User, Password] -> [AuthzId, User, Password];
[AuthzId, User, Password]; _ -> error
_ ->
error
end. end.
parse(S) -> parse1(binary_to_list(S), "", []).
parse(S) ->
parse1(S, "", []).
parse1([0 | Cs], S, T) -> parse1([0 | Cs], S, T) ->
parse1(Cs, "", [lists:reverse(S) | T]); parse1(Cs, "", [list_to_binary(lists:reverse(S)) | T]);
parse1([C | Cs], S, T) -> parse1([C | Cs], S, T) -> parse1(Cs, [C | S], T);
parse1(Cs, [C | S], T);
%parse1([], [], T) -> %parse1([], [], T) ->
% lists:reverse(T); % lists:reverse(T);
parse1([], S, T) -> parse1([], S, T) ->
lists:reverse([lists:reverse(S) | T]). lists:reverse([list_to_binary(lists:reverse(S)) | T]).
parse_domain(S) -> parse_domain1(binary_to_list(S), "", []).
parse_domain(S) ->
parse_domain1(S, "", []).
parse_domain1([$@ | Cs], S, T) -> parse_domain1([$@ | Cs], S, T) ->
parse_domain1(Cs, "", [lists:reverse(S) | T]); parse_domain1(Cs, "", [list_to_binary(lists:reverse(S)) | T]);
parse_domain1([C | Cs], S, T) -> parse_domain1([C | Cs], S, T) ->
parse_domain1(Cs, [C | S], T); parse_domain1(Cs, [C | S], T);
parse_domain1([], S, T) -> parse_domain1([], S, T) ->
lists:reverse([lists:reverse(S) | T]). lists:reverse([list_to_binary(lists:reverse(S)) | T]).

View File

@ -25,166 +25,185 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(cyrsasl_scram). -module(cyrsasl_scram).
-author('stephen.roettger@googlemail.com'). -author('stephen.roettger@googlemail.com').
-export([start/1, -export([start/1, stop/0, mech_new/4, mech_step/2]).
stop/0,
mech_new/4,
mech_step/2]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-behaviour(cyrsasl). -behaviour(cyrsasl).
-record(state, {step, stored_key, server_key, username, get_password, check_password, -record(state,
auth_message, client_nonce, server_nonce}). {step = 2 :: 2 | 4,
stored_key = <<"">> :: binary(),
server_key = <<"">> :: binary(),
username = <<"">> :: binary(),
get_password :: fun(),
check_password :: fun(),
auth_message = <<"">> :: binary(),
client_nonce = <<"">> :: binary(),
server_nonce = <<"">> :: binary()}).
-define(SALT_LENGTH, 16). -define(SALT_LENGTH, 16).
-define(NONCE_LENGTH, 16). -define(NONCE_LENGTH, 16).
start(_Opts) -> start(_Opts) ->
cyrsasl:register_mechanism("SCRAM-SHA-1", ?MODULE, scram). cyrsasl:register_mechanism(<<"SCRAM-SHA-1">>, ?MODULE,
scram).
stop() -> stop() -> ok.
ok.
mech_new(_Host, GetPassword, _CheckPassword, _CheckPasswordDigest) -> mech_new(_Host, GetPassword, _CheckPassword,
_CheckPasswordDigest) ->
{ok, #state{step = 2, get_password = GetPassword}}. {ok, #state{step = 2, get_password = GetPassword}}.
mech_step(#state{step = 2} = State, ClientIn) -> mech_step(#state{step = 2} = State, ClientIn) ->
case string:tokens(ClientIn, ",") of case str:tokens(ClientIn, <<",">>) of
[CBind, UserNameAttribute, ClientNonceAttribute] when (CBind == "y") or (CBind == "n") -> [CBind, UserNameAttribute, ClientNonceAttribute]
when (CBind == <<"y">>) or (CBind == <<"n">>) ->
case parse_attribute(UserNameAttribute) of case parse_attribute(UserNameAttribute) of
{error, Reason} -> {error, Reason} -> {error, Reason};
{error, Reason};
{_, EscapedUserName} -> {_, EscapedUserName} ->
case unescape_username(EscapedUserName) of case unescape_username(EscapedUserName) of
error -> error -> {error, <<"protocol-error-bad-username">>};
{error, "protocol-error-bad-username"};
UserName -> UserName ->
case parse_attribute(ClientNonceAttribute) of case parse_attribute(ClientNonceAttribute) of
{$r, ClientNonce} -> {$r, ClientNonce} ->
case (State#state.get_password)(UserName) of case (State#state.get_password)(UserName) of
{false, _} -> {false, _} -> {error, <<"not-authorized">>, UserName};
{error, "not-authorized", UserName};
{Ret, _AuthModule} -> {Ret, _AuthModule} ->
{StoredKey, ServerKey, Salt, IterationCount} = if {StoredKey, ServerKey, Salt, IterationCount} =
is_tuple(Ret) -> if is_tuple(Ret) -> Ret;
Ret;
true -> true ->
TempSalt = crypto:rand_bytes(?SALT_LENGTH), TempSalt =
SaltedPassword = scram:salted_password(Ret, TempSalt, ?SCRAM_DEFAULT_ITERATION_COUNT), crypto:rand_bytes(?SALT_LENGTH),
SaltedPassword =
scram:salted_password(Ret,
TempSalt,
?SCRAM_DEFAULT_ITERATION_COUNT),
{scram:stored_key(scram:client_key(SaltedPassword)), {scram:stored_key(scram:client_key(SaltedPassword)),
scram:server_key(SaltedPassword), TempSalt, ?SCRAM_DEFAULT_ITERATION_COUNT} scram:server_key(SaltedPassword),
TempSalt,
?SCRAM_DEFAULT_ITERATION_COUNT}
end, end,
ClientFirstMessageBare = string:substr(ClientIn, string:str(ClientIn, "n=")), ClientFirstMessageBare =
ServerNonce = base64:encode_to_string(crypto:rand_bytes(?NONCE_LENGTH)), str:substr(ClientIn,
ServerFirstMessage = "r=" ++ ClientNonce ++ ServerNonce ++ "," ++ str:str(ClientIn, <<"n=">>)),
"s=" ++ base64:encode_to_string(Salt) ++ "," ++ ServerNonce =
"i=" ++ integer_to_list(IterationCount), jlib:encode_base64(crypto:rand_bytes(?NONCE_LENGTH)),
{continue, ServerFirstMessage =
ServerFirstMessage, iolist_to_binary(
State#state{step = 4, stored_key = StoredKey, server_key = ServerKey, ["r=",
auth_message = ClientFirstMessageBare ++ "," ++ ServerFirstMessage, ClientNonce,
client_nonce = ClientNonce, server_nonce = ServerNonce, username = UserName}} ServerNonce,
",", "s=",
jlib:encode_base64(Salt),
",", "i=",
integer_to_list(IterationCount)]),
{continue, ServerFirstMessage,
State#state{step = 4, stored_key = StoredKey,
server_key = ServerKey,
auth_message =
<<ClientFirstMessageBare/binary,
",", ServerFirstMessage/binary>>,
client_nonce = ClientNonce,
server_nonce = ServerNonce,
username = UserName}}
end; end;
_Else -> _Else -> {error, <<"not-supported">>}
{error, "not-supported"}
end end
end end
end; end;
_Else -> _Else -> {error, <<"bad-protocol">>}
{error, "bad-protocol"}
end; end;
mech_step(#state{step = 4} = State, ClientIn) -> mech_step(#state{step = 4} = State, ClientIn) ->
case string:tokens(ClientIn, ",") of case str:tokens(ClientIn, <<",">>) of
[GS2ChannelBindingAttribute, NonceAttribute, ClientProofAttribute] -> [GS2ChannelBindingAttribute, NonceAttribute,
ClientProofAttribute] ->
case parse_attribute(GS2ChannelBindingAttribute) of case parse_attribute(GS2ChannelBindingAttribute) of
{$c, CVal} when (CVal == "biws") or (CVal == "eSws") -> {$c, CVal} when (CVal == <<"biws">>) or (CVal == <<"eSws">>) ->
%% biws is base64 for n,, => channelbinding not supported %% biws is base64 for n,, => channelbinding not supported
%% eSws is base64 for y,, => channelbinding supported by client only %% eSws is base64 for y,, => channelbinding supported by client only
Nonce = State#state.client_nonce ++ State#state.server_nonce, Nonce = <<(State#state.client_nonce)/binary,
(State#state.server_nonce)/binary>>,
case parse_attribute(NonceAttribute) of case parse_attribute(NonceAttribute) of
{$r, CompareNonce} when CompareNonce == Nonce -> {$r, CompareNonce} when CompareNonce == Nonce ->
case parse_attribute(ClientProofAttribute) of case parse_attribute(ClientProofAttribute) of
{$p, ClientProofB64} -> {$p, ClientProofB64} ->
ClientProof = base64:decode(ClientProofB64), ClientProof = jlib:decode_base64(ClientProofB64),
AuthMessage = State#state.auth_message ++ "," ++ string:substr(ClientIn, 1, string:str(ClientIn, ",p=")-1), AuthMessage =
ClientSignature = scram:client_signature(State#state.stored_key, AuthMessage), iolist_to_binary(
ClientKey = scram:client_key(ClientProof, ClientSignature), [State#state.auth_message,
",",
str:substr(ClientIn, 1,
str:str(ClientIn, <<",p=">>)
- 1)]),
ClientSignature =
scram:client_signature(State#state.stored_key,
AuthMessage),
ClientKey = scram:client_key(ClientProof,
ClientSignature),
CompareStoredKey = scram:stored_key(ClientKey), CompareStoredKey = scram:stored_key(ClientKey),
if CompareStoredKey == State#state.stored_key -> if CompareStoredKey == State#state.stored_key ->
ServerSignature = scram:server_signature(State#state.server_key, AuthMessage), ServerSignature =
{ok, [{username, State#state.username}], "v=" ++ base64:encode_to_string(ServerSignature)}; scram:server_signature(State#state.server_key,
true -> AuthMessage),
{error, "bad-auth"} {ok, [{username, State#state.username}],
<<"v=",
(jlib:encode_base64(ServerSignature))/binary>>};
true -> {error, <<"bad-auth">>}
end; end;
_Else -> _Else -> {error, <<"bad-protocol">>}
{error, "bad-protocol"}
end; end;
{$r, _} -> {$r, _} -> {error, <<"bad-nonce">>};
{error, "bad-nonce"}; _Else -> {error, <<"bad-protocol">>}
_Else ->
{error, "bad-protocol"}
end; end;
_Else -> _Else -> {error, <<"bad-protocol">>}
{error, "bad-protocol"}
end; end;
_Else -> _Else -> {error, <<"bad-protocol">>}
{error, "bad-protocol"}
end. end.
parse_attribute(Attribute) -> parse_attribute(Attribute) ->
AttributeLen = string:len(Attribute), AttributeLen = byte_size(Attribute),
if if AttributeLen >= 3 ->
AttributeLen >= 3 -> AttributeS = binary_to_list(Attribute),
SecondChar = lists:nth(2, Attribute), SecondChar = lists:nth(2, AttributeS),
case is_alpha(lists:nth(1, Attribute)) of case is_alpha(lists:nth(1, AttributeS)) of
true -> true ->
if if SecondChar == $= ->
SecondChar == $= -> String = str:substr(Attribute, 3),
String = string:substr(Attribute, 3), {lists:nth(1, AttributeS), String};
{lists:nth(1, Attribute), String}; true -> {error, <<"bad-format second char not equal sign">>}
true ->
{error, "bad-format second char not equal sign"}
end; end;
_Else -> _Else -> {error, <<"bad-format first char not a letter">>}
{error, "bad-format first char not a letter"}
end; end;
true -> true -> {error, <<"bad-format attribute too short">>}
{error, "bad-format attribute too short"}
end. end.
unescape_username("") -> unescape_username(<<"">>) -> <<"">>;
"";
unescape_username(EscapedUsername) -> unescape_username(EscapedUsername) ->
Pos = string:str(EscapedUsername, "="), Pos = str:str(EscapedUsername, <<"=">>),
if if Pos == 0 -> EscapedUsername;
Pos == 0 ->
EscapedUsername;
true -> true ->
Start = string:substr(EscapedUsername, 1, Pos-1), Start = str:substr(EscapedUsername, 1, Pos - 1),
End = string:substr(EscapedUsername, Pos), End = str:substr(EscapedUsername, Pos),
EndLen = string:len(End), EndLen = byte_size(End),
if if EndLen < 3 -> error;
EndLen < 3 ->
error;
true -> true ->
case string:substr(End, 1, 3) of case str:substr(End, 1, 3) of
"=2C" -> <<"=2C">> ->
Start ++ "," ++ unescape_username(string:substr(End, 4)); <<Start/binary, ",",
"=3D" -> (unescape_username(str:substr(End, 4)))/binary>>;
Start ++ "=" ++ unescape_username(string:substr(End, 4)); <<"=3D">> ->
_Else -> <<Start/binary, "=",
error (unescape_username(str:substr(End, 4)))/binary>>;
_Else -> error
end end
end end
end. end.
is_alpha(Char) when Char >= $a, Char =< $z -> is_alpha(Char) when Char >= $a, Char =< $z -> true;
true; is_alpha(Char) when Char >= $A, Char =< $Z -> true;
is_alpha(Char) when Char >= $A, Char =< $Z -> is_alpha(_) -> false.
true;
is_alpha(_) ->
false.

View File

@ -21,44 +21,57 @@
%% This macro returns a string of the ejabberd version running, e.g. "2.3.4" %% This macro returns a string of the ejabberd version running, e.g. "2.3.4"
%% If the ejabberd application description isn't loaded, returns atom: undefined %% If the ejabberd application description isn't loaded, returns atom: undefined
-define(VERSION, element(2, application:get_key(ejabberd,vsn))). -define(VERSION, ejabberd_config:get_version()).
-define(MYHOSTS, ejabberd_config:get_global_option(hosts)). -define(MYHOSTS, ejabberd_config:get_myhosts()).
-define(MYNAME, hd(ejabberd_config:get_global_option(hosts))).
-define(MYLANG, ejabberd_config:get_global_option(language)).
-define(MSGS_DIR, "msgs"). -define(MYNAME, hd(ejabberd_config:get_myhosts())).
-define(CONFIG_PATH, "ejabberd.cfg").
-define(LOG_PATH, "ejabberd.log").
-define(EJABBERD_URI, "http://www.process-one.net/en/ejabberd/"). -define(MYLANG, ejabberd_config:get_mylang()).
-define(MSGS_DIR, <<"msgs">>).
-define(CONFIG_PATH, <<"ejabberd.cfg">>).
-define(LOG_PATH, <<"ejabberd.log">>).
-define(EJABBERD_URI, <<"http://www.process-one.net/en/ejabberd/">>).
-define(S2STIMEOUT, 600000). -define(S2STIMEOUT, 600000).
%%-define(DBGFSM, true). %%-define(DBGFSM, true).
-record(scram, {storedkey, serverkey, salt, iterationcount}). -record(scram,
{storedkey = <<"">>,
serverkey = <<"">>,
salt = <<"">>,
iterationcount = 0 :: integer()}).
-type scram() :: #scram{}.
-define(SCRAM_DEFAULT_ITERATION_COUNT, 4096). -define(SCRAM_DEFAULT_ITERATION_COUNT, 4096).
%% --------------------------------- %% ---------------------------------
%% Logging mechanism %% Logging mechanism
%% Print in standard output %% Print in standard output
-define(PRINT(Format, Args), -define(PRINT(Format, Args), io:format(Format, Args)).
io:format(Format, Args)).
-define(DEBUG(Format, Args), -define(DEBUG(Format, Args),
ejabberd_logger:debug_msg(?MODULE,?LINE,Format, Args)). ejabberd_logger:debug_msg(?MODULE, ?LINE, Format,
Args)).
-define(INFO_MSG(Format, Args), -define(INFO_MSG(Format, Args),
ejabberd_logger:info_msg(?MODULE, ?LINE, Format, Args)). ejabberd_logger:info_msg(?MODULE, ?LINE, Format, Args)).
-define(WARNING_MSG(Format, Args), -define(WARNING_MSG(Format, Args),
ejabberd_logger:warning_msg(?MODULE,?LINE,Format, Args)). ejabberd_logger:warning_msg(?MODULE, ?LINE, Format,
Args)).
-define(ERROR_MSG(Format, Args), -define(ERROR_MSG(Format, Args),
ejabberd_logger:error_msg(?MODULE,?LINE,Format, Args)). ejabberd_logger:error_msg(?MODULE, ?LINE, Format,
Args)).
-define(CRITICAL_MSG(Format, Args), -define(CRITICAL_MSG(Format, Args),
ejabberd_logger:critical_msg(?MODULE,?LINE,Format, Args)). ejabberd_logger:critical_msg(?MODULE, ?LINE, Format,
Args)).

View File

@ -117,17 +117,17 @@ commands() ->
#ejabberd_commands{name = register, tags = [accounts], #ejabberd_commands{name = register, tags = [accounts],
desc = "Register a user", desc = "Register a user",
module = ?MODULE, function = register, module = ?MODULE, function = register,
args = [{user, string}, {host, string}, {password, string}], args = [{user, binary}, {host, binary}, {password, binary}],
result = {res, restuple}}, result = {res, restuple}},
#ejabberd_commands{name = unregister, tags = [accounts], #ejabberd_commands{name = unregister, tags = [accounts],
desc = "Unregister a user", desc = "Unregister a user",
module = ?MODULE, function = unregister, module = ?MODULE, function = unregister,
args = [{user, string}, {host, string}], args = [{user, binary}, {host, binary}],
result = {res, restuple}}, result = {res, restuple}},
#ejabberd_commands{name = registered_users, tags = [accounts], #ejabberd_commands{name = registered_users, tags = [accounts],
desc = "List all registered users in HOST", desc = "List all registered users in HOST",
module = ?MODULE, function = registered_users, module = ?MODULE, function = registered_users,
args = [{host, string}], args = [{host, binary}],
result = {users, {list, {username, string}}}}, result = {users, {list, {username, string}}}},
#ejabberd_commands{name = registered_vhosts, tags = [server], #ejabberd_commands{name = registered_vhosts, tags = [server],
desc = "List all registered vhosts in SERVER", desc = "List all registered vhosts in SERVER",
@ -158,6 +158,11 @@ commands() ->
module = ejabberd_piefxis, function = export_host, module = ejabberd_piefxis, function = export_host,
args = [{dir, string}, {host, string}], result = {res, rescode}}, args = [{dir, string}, {host, string}], result = {res, rescode}},
#ejabberd_commands{name = export_odbc, tags = [mnesia, odbc],
desc = "Export all tables as SQL queries to a file",
module = ejd2odbc, function = export,
args = [{host, string}, {file, string}], result = {res, rescode}},
#ejabberd_commands{name = delete_expired_messages, tags = [purge], #ejabberd_commands{name = delete_expired_messages, tags = [purge],
desc = "Delete expired offline messages from database", desc = "Delete expired offline messages from database",
module = ?MODULE, function = delete_expired_messages, module = ?MODULE, function = delete_expired_messages,
@ -296,11 +301,12 @@ stop_kindly(DelaySeconds, AnnouncementText) ->
ok. ok.
send_service_message_all_mucs(Subject, AnnouncementText) -> send_service_message_all_mucs(Subject, AnnouncementText) ->
Message = io_lib:format("~s~n~s", [Subject, AnnouncementText]), Message = list_to_binary(
io_lib:format("~s~n~s", [Subject, AnnouncementText])),
lists:foreach( lists:foreach(
fun(ServerHost) -> fun(ServerHost) ->
MUCHost = gen_mod:get_module_opt_host( MUCHost = gen_mod:get_module_opt_host(
ServerHost, mod_muc, "conference.@HOST@"), ServerHost, mod_muc, <<"conference.@HOST@">>),
mod_muc:broadcast_service_message(MUCHost, Message) mod_muc:broadcast_service_message(MUCHost, Message)
end, end,
?MYHOSTS). ?MYHOSTS).
@ -320,6 +326,8 @@ update("all") ->
update(ModStr) -> update(ModStr) ->
update_module(ModStr). update_module(ModStr).
update_module(ModuleNameBin) when is_binary(ModuleNameBin) ->
update_module(binary_to_list(ModuleNameBin));
update_module(ModuleNameString) -> update_module(ModuleNameString) ->
ModuleName = list_to_atom(ModuleNameString), ModuleName = list_to_atom(ModuleNameString),
case ejabberd_update:update([ModuleName]) of case ejabberd_update:update([ModuleName]) of

View File

@ -57,8 +57,6 @@ start(normal, _Args) ->
ejabberd_config:start(), ejabberd_config:start(),
ejabberd_check:config(), ejabberd_check:config(),
connect_nodes(), connect_nodes(),
%% Loading ASN.1 driver explicitly to avoid races in LDAP
catch asn1rt:load_driver(),
Sup = ejabberd_sup:start_link(), Sup = ejabberd_sup:start_link(),
ejabberd_rdbms:start(), ejabberd_rdbms:start(),
ejabberd_auth:start(), ejabberd_auth:start(),
@ -135,41 +133,48 @@ db_init() ->
start_modules() -> start_modules() ->
lists:foreach( lists:foreach(
fun(Host) -> fun(Host) ->
case ejabberd_config:get_local_option({modules, Host}) of Modules = ejabberd_config:get_local_option(
undefined -> {modules, Host},
ok; fun(Mods) ->
Modules -> lists:map(
fun({M, A}) when is_atom(M), is_list(A) ->
{M, A}
end, Mods)
end, []),
lists:foreach( lists:foreach(
fun({Module, Args}) -> fun({Module, Args}) ->
gen_mod:start_module(Host, Module, Args) gen_mod:start_module(Host, Module, Args)
end, Modules) end, Modules)
end
end, ?MYHOSTS). end, ?MYHOSTS).
%% Stop all the modules in all the hosts %% Stop all the modules in all the hosts
stop_modules() -> stop_modules() ->
lists:foreach( lists:foreach(
fun(Host) -> fun(Host) ->
case ejabberd_config:get_local_option({modules, Host}) of Modules = ejabberd_config:get_local_option(
undefined -> {modules, Host},
ok; fun(Mods) ->
Modules -> lists:map(
fun({M, A}) when is_atom(M), is_list(A) ->
{M, A}
end, Mods)
end, []),
lists:foreach( lists:foreach(
fun({Module, _Args}) -> fun({Module, _Args}) ->
gen_mod:stop_module_keep_config(Host, Module) gen_mod:stop_module_keep_config(Host, Module)
end, Modules) end, Modules)
end
end, ?MYHOSTS). end, ?MYHOSTS).
connect_nodes() -> connect_nodes() ->
case ejabberd_config:get_local_option(cluster_nodes) of Nodes = ejabberd_config:get_local_option(
undefined -> cluster_nodes,
ok; fun(Ns) ->
Nodes when is_list(Nodes) -> true = lists:all(fun is_atom/1, Ns),
Ns
end, []),
lists:foreach(fun(Node) -> lists:foreach(fun(Node) ->
net_kernel:connect_node(Node) net_kernel:connect_node(Node)
end, Nodes) end, Nodes).
end.
%% @spec () -> string() %% @spec () -> string()
%% @doc Returns the full path to the ejabberd log file. %% @doc Returns the full path to the ejabberd log file.

View File

@ -27,32 +27,21 @@
%% TODO: Use the functions in ejabberd auth to add and remove users. %% TODO: Use the functions in ejabberd auth to add and remove users.
-module(ejabberd_auth). -module(ejabberd_auth).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
%% External exports %% External exports
-export([start/0, -export([start/0, set_password/3, check_password/3,
set_password/3, check_password/5, check_password_with_authmodule/3,
check_password/3, check_password_with_authmodule/5, try_register/3,
check_password/5, dirty_get_registered_users/0, get_vh_registered_users/1,
check_password_with_authmodule/3, get_vh_registered_users/2, export/1,
check_password_with_authmodule/5,
try_register/3,
dirty_get_registered_users/0,
get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1, get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_vh_registered_users_number/2, get_password/2,
get_password/2, get_password_s/2, get_password_with_authmodule/2,
get_password_s/2, is_user_exists/2, is_user_exists_in_other_modules/3,
get_password_with_authmodule/2, remove_user/2, remove_user/3, plain_password_required/1,
is_user_exists/2, store_type/1, entropy/1]).
is_user_exists_in_other_modules/3,
remove_user/2,
remove_user/3,
plain_password_required/1,
store_type/1,
entropy/1
]).
-export([auth_modules/1]). -export([auth_modules/1]).
@ -61,42 +50,62 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% API %%% API
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
start() -> -type opts() :: [{prefix, binary()} | {from, integer()} |
lists:foreach( {to, integer()} | {limit, integer()} |
fun(Host) -> {offset, integer()}].
lists:foreach(
fun(M) ->
M:start(Host)
end, auth_modules(Host))
end, ?MYHOSTS).
-callback start(binary()) -> any().
-callback plain_password_required() -> boolean().
-callback store_type() -> plain | external | scram.
-callback set_password(binary(), binary(), binary()) -> ok | {error, atom()}.
-callback remove_user(binary(), binary()) -> any().
-callback remove_user(binary(), binary(), binary()) -> any().
-callback is_user_exists(binary(), binary()) -> boolean() | {error, atom()}.
-callback check_password(binary(), binary(), binary()) -> boolean().
-callback check_password(binary(), binary(), binary(), binary(),
fun((binary()) -> binary())) -> boolean().
-callback try_register(binary(), binary(), binary()) -> {atomic, atom()} |
{error, atom()}.
-callback dirty_get_registered_users() -> [{binary(), binary()}].
-callback get_vh_registered_users(binary()) -> [{binary(), binary()}].
-callback get_vh_registered_users(binary(), opts()) -> [{binary(), binary()}].
-callback get_vh_registered_users_number(binary()) -> number().
-callback get_vh_registered_users_number(binary(), opts()) -> number().
-callback get_password(binary(), binary()) -> false | binary().
-callback get_password_s(binary(), binary()) -> binary().
start() ->
%% This is only executed by ejabberd_c2s for non-SASL auth client %% This is only executed by ejabberd_c2s for non-SASL auth client
lists:foreach(fun (Host) ->
lists:foreach(fun (M) -> M:start(Host) end,
auth_modules(Host))
end,
?MYHOSTS).
plain_password_required(Server) -> plain_password_required(Server) ->
lists:any( lists:any(fun (M) -> M:plain_password_required() end,
fun(M) -> auth_modules(Server)).
M:plain_password_required()
end, auth_modules(Server)).
store_type(Server) -> store_type(Server) ->
lists:foldl(
fun(_, external) ->
external;
(M, scram) ->
case M:store_type() of
external ->
external;
_Else ->
scram
end;
(M, plain) ->
M:store_type()
end, plain, auth_modules(Server)).
%% @doc Check if the user and password can login in server. %% @doc Check if the user and password can login in server.
%% @spec (User::string(), Server::string(), Password::string()) -> %% @spec (User::string(), Server::string(), Password::string()) ->
%% true | false %% true | false
lists:foldl(fun (_, external) -> external;
(M, scram) ->
case M:store_type() of
external -> external;
_Else -> scram
end;
(M, plain) -> M:store_type()
end,
plain, auth_modules(Server)).
-spec check_password(binary(), binary(), binary()) -> boolean().
check_password(User, Server, Password) -> check_password(User, Server, Password) ->
case check_password_with_authmodule(User, Server, Password) of case check_password_with_authmodule(User, Server,
Password)
of
{true, _AuthModule} -> true; {true, _AuthModule} -> true;
false -> false false -> false
end. end.
@ -105,9 +114,14 @@ check_password(User, Server, Password) ->
%% @spec (User::string(), Server::string(), Password::string(), %% @spec (User::string(), Server::string(), Password::string(),
%% Digest::string(), DigestGen::function()) -> %% Digest::string(), DigestGen::function()) ->
%% true | false %% true | false
check_password(User, Server, Password, Digest, DigestGen) -> -spec check_password(binary(), binary(), binary(), binary(),
case check_password_with_authmodule(User, Server, Password, fun((binary()) -> binary())) -> boolean().
Digest, DigestGen) of
check_password(User, Server, Password, Digest,
DigestGen) ->
case check_password_with_authmodule(User, Server,
Password, Digest, DigestGen)
of
{true, _AuthModule} -> true; {true, _AuthModule} -> true;
false -> false false -> false
end. end.
@ -122,55 +136,62 @@ check_password(User, Server, Password, Digest, DigestGen) ->
%% AuthModule = ejabberd_auth_anonymous | ejabberd_auth_external %% AuthModule = ejabberd_auth_anonymous | ejabberd_auth_external
%% | ejabberd_auth_internal | ejabberd_auth_ldap %% | ejabberd_auth_internal | ejabberd_auth_ldap
%% | ejabberd_auth_odbc | ejabberd_auth_pam %% | ejabberd_auth_odbc | ejabberd_auth_pam
check_password_with_authmodule(User, Server, Password) -> -spec check_password_with_authmodule(binary(), binary(), binary()) -> false |
check_password_loop(auth_modules(Server), [User, Server, Password]). {true, atom()}.
check_password_with_authmodule(User, Server, Password, Digest, DigestGen) -> check_password_with_authmodule(User, Server,
check_password_loop(auth_modules(Server), [User, Server, Password, Password) ->
Digest, DigestGen]). check_password_loop(auth_modules(Server),
[User, Server, Password]).
check_password_loop([], _Args) -> -spec check_password_with_authmodule(binary(), binary(), binary(), binary(),
false; fun((binary()) -> binary())) -> false |
{true, atom()}.
check_password_with_authmodule(User, Server, Password,
Digest, DigestGen) ->
check_password_loop(auth_modules(Server),
[User, Server, Password, Digest, DigestGen]).
check_password_loop([], _Args) -> false;
check_password_loop([AuthModule | AuthModules], Args) -> check_password_loop([AuthModule | AuthModules], Args) ->
case apply(AuthModule, check_password, Args) of case apply(AuthModule, check_password, Args) of
true -> true -> {true, AuthModule};
{true, AuthModule}; false -> check_password_loop(AuthModules, Args)
false ->
check_password_loop(AuthModules, Args)
end. end.
-spec set_password(binary(), binary(), binary()) -> ok |
{error, atom()}.
%% @spec (User::string(), Server::string(), Password::string()) -> %% @spec (User::string(), Server::string(), Password::string()) ->
%% ok | {error, ErrorType} %% ok | {error, ErrorType}
%% where ErrorType = empty_password | not_allowed | invalid_jid %% where ErrorType = empty_password | not_allowed | invalid_jid
set_password(_User, _Server, "") -> set_password(_User, _Server, <<"">>) ->
%% We do not allow empty password
{error, empty_password}; {error, empty_password};
set_password(User, Server, Password) -> set_password(User, Server, Password) ->
lists:foldl(
fun(M, {error, _}) ->
M:set_password(User, Server, Password);
(_M, Res) ->
Res
end, {error, not_allowed}, auth_modules(Server)).
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, not_allowed} %% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, not_allowed}
try_register(_User, _Server, "") -> lists:foldl(fun (M, {error, _}) ->
%% We do not allow empty password M:set_password(User, Server, Password);
(_M, Res) -> Res
end,
{error, not_allowed}, auth_modules(Server)).
-spec try_register(binary(), binary(), binary()) -> {atomic, atom()} |
{error, atom()}.
try_register(_User, _Server, <<"">>) ->
{error, not_allowed}; {error, not_allowed};
try_register(User, Server, Password) -> try_register(User, Server, Password) ->
case is_user_exists(User, Server) of case is_user_exists(User, Server) of
true -> true -> {atomic, exists};
{atomic, exists};
false -> false ->
case lists:member(jlib:nameprep(Server), ?MYHOSTS) of case lists:member(jlib:nameprep(Server), ?MYHOSTS) of
true -> true ->
Res = lists:foldl( Res = lists:foldl(fun (_M, {atomic, ok} = Res) -> Res;
fun(_M, {atomic, ok} = Res) ->
Res;
(M, _) -> (M, _) ->
M:try_register(User, Server, Password) M:try_register(User, Server, Password)
end, {error, not_allowed}, auth_modules(Server)), end,
{error, not_allowed}, auth_modules(Server)),
case Res of case Res of
{atomic, ok} -> {atomic, ok} ->
ejabberd_hooks:run(register_user, Server, ejabberd_hooks:run(register_user, Server,
@ -178,143 +199,161 @@ try_register(User, Server, Password) ->
{atomic, ok}; {atomic, ok};
_ -> Res _ -> Res
end; end;
false -> false -> {error, not_allowed}
{error, not_allowed}
end end
end. end.
%% Registered users list do not include anonymous users logged %% Registered users list do not include anonymous users logged
-spec dirty_get_registered_users() -> [{binary(), binary()}].
dirty_get_registered_users() -> dirty_get_registered_users() ->
lists:flatmap( lists:flatmap(fun (M) -> M:dirty_get_registered_users()
fun(M) -> end,
M:dirty_get_registered_users() auth_modules()).
end, auth_modules()).
-spec get_vh_registered_users(binary()) -> [{binary(), binary()}].
%% Registered users list do not include anonymous users logged %% Registered users list do not include anonymous users logged
get_vh_registered_users(Server) -> get_vh_registered_users(Server) ->
lists:flatmap( lists:flatmap(fun (M) ->
fun(M) ->
M:get_vh_registered_users(Server) M:get_vh_registered_users(Server)
end, auth_modules(Server)). end,
auth_modules(Server)).
-spec get_vh_registered_users(binary(), opts()) -> [{binary(), binary()}].
get_vh_registered_users(Server, Opts) -> get_vh_registered_users(Server, Opts) ->
lists:flatmap( lists:flatmap(fun (M) ->
fun(M) -> case erlang:function_exported(M,
case erlang:function_exported( get_vh_registered_users,
M, get_vh_registered_users, 2) of 2)
true -> of
M:get_vh_registered_users(Server, Opts); true -> M:get_vh_registered_users(Server, Opts);
false -> false -> M:get_vh_registered_users(Server)
M:get_vh_registered_users(Server)
end end
end, auth_modules(Server)). end,
auth_modules(Server)).
get_vh_registered_users_number(Server) -> get_vh_registered_users_number(Server) ->
lists:sum( lists:sum(lists:map(fun (M) ->
lists:map( case erlang:function_exported(M,
fun(M) -> get_vh_registered_users_number,
case erlang:function_exported( 1)
M, get_vh_registered_users_number, 1) of of
true -> true ->
M:get_vh_registered_users_number(Server); M:get_vh_registered_users_number(Server);
false -> false ->
length(M:get_vh_registered_users(Server)) length(M:get_vh_registered_users(Server))
end end
end, auth_modules(Server))). end,
auth_modules(Server))).
-spec get_vh_registered_users_number(binary(), opts()) -> number().
get_vh_registered_users_number(Server, Opts) -> get_vh_registered_users_number(Server, Opts) ->
lists:sum( %% @doc Get the password of the user.
lists:map( %% @spec (User::string(), Server::string()) -> Password::string()
fun(M) -> lists:sum(lists:map(fun (M) ->
case erlang:function_exported( case erlang:function_exported(M,
M, get_vh_registered_users_number, 2) of get_vh_registered_users_number,
2)
of
true -> true ->
M:get_vh_registered_users_number(Server, Opts); M:get_vh_registered_users_number(Server,
Opts);
false -> false ->
length(M:get_vh_registered_users(Server)) length(M:get_vh_registered_users(Server))
end end
end, auth_modules(Server))). end,
auth_modules(Server))).
-spec get_password(binary(), binary()) -> false | binary().
%% @doc Get the password of the user.
%% @spec (User::string(), Server::string()) -> Password::string()
get_password(User, Server) -> get_password(User, Server) ->
lists:foldl( lists:foldl(fun (M, false) ->
fun(M, false) ->
M:get_password(User, Server); M:get_password(User, Server);
(_M, Password) -> (_M, Password) -> Password
Password end,
end, false, auth_modules(Server)). false, auth_modules(Server)).
-spec get_password_s(binary(), binary()) -> binary().
get_password_s(User, Server) -> get_password_s(User, Server) ->
case get_password(User, Server) of case get_password(User, Server) of
false -> false -> <<"">>;
""; Password -> Password
Password when is_list(Password) ->
Password;
_ ->
""
end. end.
%% @doc Get the password of the user and the auth module. %% @doc Get the password of the user and the auth module.
%% @spec (User::string(), Server::string()) -> %% @spec (User::string(), Server::string()) ->
%% {Password::string(), AuthModule::atom()} | {false, none} %% {Password::string(), AuthModule::atom()} | {false, none}
get_password_with_authmodule(User, Server) -> -spec get_password_with_authmodule(binary(), binary()) -> {false | binary(), atom()}.
lists:foldl(
fun(M, {false, _}) ->
{M:get_password(User, Server), M};
(_M, {Password, AuthModule}) ->
{Password, AuthModule}
end, {false, none}, auth_modules(Server)).
get_password_with_authmodule(User, Server) ->
%% Returns true if the user exists in the DB or if an anonymous user is logged %% Returns true if the user exists in the DB or if an anonymous user is logged
%% under the given name %% under the given name
is_user_exists(User, Server) -> lists:foldl(fun (M, {false, _}) ->
lists:any( {M:get_password(User, Server), M};
fun(M) -> (_M, {Password, AuthModule}) -> {Password, AuthModule}
case M:is_user_exists(User, Server) of end,
{error, Error} -> {false, none}, auth_modules(Server)).
?ERROR_MSG("The authentication module ~p returned an "
"error~nwhen checking user ~p in server ~p~n"
"Error message: ~p",
[M, User, Server, Error]),
false;
Else ->
Else
end
end, auth_modules(Server)).
-spec is_user_exists(binary(), binary()) -> boolean().
is_user_exists(User, Server) ->
%% Check if the user exists in all authentications module except the module %% Check if the user exists in all authentications module except the module
%% passed as parameter %% passed as parameter
%% @spec (Module::atom(), User, Server) -> true | false | maybe %% @spec (Module::atom(), User, Server) -> true | false | maybe
is_user_exists_in_other_modules(Module, User, Server) -> lists:any(fun (M) ->
is_user_exists_in_other_modules_loop( case M:is_user_exists(User, Server) of
auth_modules(Server)--[Module],
User, Server).
is_user_exists_in_other_modules_loop([], _User, _Server) ->
false;
is_user_exists_in_other_modules_loop([AuthModule|AuthModules], User, Server) ->
case AuthModule:is_user_exists(User, Server) of
true ->
true;
false ->
is_user_exists_in_other_modules_loop(AuthModules, User, Server);
{error, Error} -> {error, Error} ->
?DEBUG("The authentication module ~p returned an error~nwhen " ?ERROR_MSG("The authentication module ~p returned "
"checking user ~p in server ~p~nError message: ~p", "an error~nwhen checking user ~p in server "
"~p~nError message: ~p",
[M, User, Server, Error]),
false;
Else -> Else
end
end,
auth_modules(Server)).
-spec is_user_exists_in_other_modules(atom(), binary(), binary()) -> boolean() | maybe.
is_user_exists_in_other_modules(Module, User, Server) ->
is_user_exists_in_other_modules_loop(auth_modules(Server)
-- [Module],
User, Server).
is_user_exists_in_other_modules_loop([], _User,
_Server) ->
false;
is_user_exists_in_other_modules_loop([AuthModule
| AuthModules],
User, Server) ->
case AuthModule:is_user_exists(User, Server) of
true -> true;
false ->
is_user_exists_in_other_modules_loop(AuthModules, User,
Server);
{error, Error} ->
?DEBUG("The authentication module ~p returned "
"an error~nwhen checking user ~p in server "
"~p~nError message: ~p",
[AuthModule, User, Server, Error]), [AuthModule, User, Server, Error]),
maybe maybe
end. end.
-spec remove_user(binary(), binary()) -> ok.
%% @spec (User, Server) -> ok %% @spec (User, Server) -> ok
%% @doc Remove user. %% @doc Remove user.
%% Note: it may return ok even if there was some problem removing the user. %% Note: it may return ok even if there was some problem removing the user.
remove_user(User, Server) -> remove_user(User, Server) ->
lists:foreach( lists:foreach(fun (M) -> M:remove_user(User, Server)
fun(M) -> end,
M:remove_user(User, Server) auth_modules(Server)),
end, auth_modules(Server)), ejabberd_hooks:run(remove_user, jlib:nameprep(Server),
ejabberd_hooks:run(remove_user, jlib:nameprep(Server), [User, Server]), [User, Server]),
ok. ok.
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request | error %% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request | error
@ -322,40 +361,48 @@ remove_user(User, Server) ->
%% The removal is attempted in each auth method provided: %% The removal is attempted in each auth method provided:
%% when one returns 'ok' the loop stops; %% when one returns 'ok' the loop stops;
%% if no method returns 'ok' then it returns the error message indicated by the last method attempted. %% if no method returns 'ok' then it returns the error message indicated by the last method attempted.
-spec remove_user(binary(), binary(), binary()) -> any().
remove_user(User, Server, Password) -> remove_user(User, Server, Password) ->
R = lists:foldl( R = lists:foldl(fun (_M, ok = Res) -> Res;
fun(_M, ok = Res) -> (M, _) -> M:remove_user(User, Server, Password)
Res; end,
(M, _) -> error, auth_modules(Server)),
M:remove_user(User, Server, Password)
end, error, auth_modules(Server)),
case R of case R of
ok -> ejabberd_hooks:run(remove_user, jlib:nameprep(Server), [User, Server]); ok ->
ejabberd_hooks:run(remove_user, jlib:nameprep(Server),
[User, Server]);
_ -> none _ -> none
end, end,
R. R.
%% @spec (IOList) -> non_negative_float() %% @spec (IOList) -> non_negative_float()
%% @doc Calculate informational entropy. %% @doc Calculate informational entropy.
entropy(IOList) -> entropy(B) ->
case binary_to_list(iolist_to_binary(IOList)) of case binary_to_list(B) of
"" -> "" -> 0.0;
0.0;
S -> S ->
Set = lists:foldl( Set = lists:foldl(fun (C,
fun(C, [Digit, Printable, LowLetter, HiLetter, Other]) -> [Digit, Printable, LowLetter, HiLetter,
Other]) ->
if C >= $a, C =< $z -> if C >= $a, C =< $z ->
[Digit, Printable, 26, HiLetter, Other]; [Digit, Printable, 26, HiLetter,
Other];
C >= $0, C =< $9 -> C >= $0, C =< $9 ->
[9, Printable, LowLetter, HiLetter, Other]; [9, Printable, LowLetter, HiLetter,
Other];
C >= $A, C =< $Z -> C >= $A, C =< $Z ->
[Digit, Printable, LowLetter, 26, Other]; [Digit, Printable, LowLetter, 26,
C >= 16#21, C =< 16#7e -> Other];
[Digit, 33, LowLetter, HiLetter, Other]; C >= 33, C =< 126 ->
[Digit, 33, LowLetter, HiLetter,
Other];
true -> true ->
[Digit, Printable, LowLetter, HiLetter, 128] [Digit, Printable, LowLetter,
HiLetter, 128]
end end
end, [0, 0, 0, 0, 0], S), end,
[0, 0, 0, 0, 0], S),
length(S) * math:log(lists:sum(Set)) / math:log(2) length(S) * math:log(lists:sum(Set)) / math:log(2)
end. end.
@ -365,19 +412,27 @@ entropy(IOList) ->
%% Return the lists of all the auth modules actually used in the %% Return the lists of all the auth modules actually used in the
%% configuration %% configuration
auth_modules() -> auth_modules() ->
lists:usort( lists:usort(lists:flatmap(fun (Server) ->
lists:flatmap(
fun(Server) ->
auth_modules(Server) auth_modules(Server)
end, ?MYHOSTS)). end,
?MYHOSTS)).
-spec auth_modules(binary()) -> [atom()].
%% Return the list of authenticated modules for a given host %% Return the list of authenticated modules for a given host
auth_modules(Server) -> auth_modules(Server) ->
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
Method = ejabberd_config:get_local_option({auth_method, LServer}), Methods = ejabberd_config:get_local_option(
Methods = if {auth_method, LServer},
Method == undefined -> []; fun(V) when is_list(V) ->
is_list(Method) -> Method; true = lists:all(fun is_atom/1, V),
is_atom(Method) -> [Method] V;
end, (V) when is_atom(V) ->
[list_to_atom("ejabberd_auth_" ++ atom_to_list(M)) || M <- Methods]. [V]
end, []),
[jlib:binary_to_atom(<<"ejabberd_auth_",
(jlib:atom_to_binary(M))/binary>>)
|| M <- Methods].
export(Server) ->
ejabberd_auth_internal:export(Server).

View File

@ -39,27 +39,24 @@
%% Function used by ejabberd_auth: %% Function used by ejabberd_auth:
-export([login/2, -export([login/2, set_password/3, check_password/3,
set_password/3, check_password/5, try_register/3,
check_password/3, dirty_get_registered_users/0, get_vh_registered_users/1,
check_password/5, get_vh_registered_users/2, get_vh_registered_users_number/1,
try_register/3, get_vh_registered_users_number/2, get_password_s/2,
dirty_get_registered_users/0, get_password/2, get_password/3, is_user_exists/2,
get_vh_registered_users/1, remove_user/2, remove_user/3, store_type/0,
get_password/2,
get_password/3,
is_user_exists/2,
remove_user/2,
remove_user/3,
store_type/0,
plain_password_required/0]). plain_password_required/0]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl"). -include("jlib.hrl").
-record(anonymous, {us, sid}).
%% Create the anonymous table if at least one virtual host has anonymous features enabled %% Create the anonymous table if at least one virtual host has anonymous features enabled
%% Register to login / logout events %% Register to login / logout events
-record(anonymous, {us = {<<"">>, <<"">>} :: {binary(), binary()},
sid = {now(), self()} :: ejabberd_sm:sid()}).
start(Host) -> start(Host) ->
%% TODO: Check cluster mode %% TODO: Check cluster mode
mnesia:create_table(anonymous, [{ram_copies, [node()]}, mnesia:create_table(anonymous, [{ram_copies, [node()]},
@ -106,18 +103,21 @@ is_login_anonymous_enabled(Host) ->
%% Return the anonymous protocol to use: sasl_anon|login_anon|both %% Return the anonymous protocol to use: sasl_anon|login_anon|both
%% defaults to login_anon %% defaults to login_anon
anonymous_protocol(Host) -> anonymous_protocol(Host) ->
case ejabberd_config:get_local_option({anonymous_protocol, Host}) of ejabberd_config:get_local_option(
sasl_anon -> sasl_anon; {anonymous_protocol, Host},
login_anon -> login_anon; fun(sasl_anon) -> sasl_anon;
both -> both; (login_anon) -> login_anon;
_Other -> sasl_anon (both) -> both
end. end,
sasl_anon).
%% Return true if multiple connections have been allowed in the config file %% Return true if multiple connections have been allowed in the config file
%% defaults to false %% defaults to false
allow_multiple_connections(Host) -> allow_multiple_connections(Host) ->
ejabberd_config:get_local_option( ejabberd_config:get_local_option(
{allow_multiple_connections, Host}) =:= true. {allow_multiple_connections, Host},
fun(V) when is_boolean(V) -> V end,
false).
%% Check if user exist in the anonymus database %% Check if user exist in the anonymus database
anonymous_user_exist(User, Server) -> anonymous_user_exist(User, Server) ->
@ -134,36 +134,39 @@ anonymous_user_exist(User, Server) ->
%% Remove connection from Mnesia tables %% Remove connection from Mnesia tables
remove_connection(SID, LUser, LServer) -> remove_connection(SID, LUser, LServer) ->
US = {LUser, LServer}, US = {LUser, LServer},
F = fun() -> F = fun () -> mnesia:delete_object({anonymous, US, SID})
mnesia:delete_object({anonymous, US, SID})
end, end,
mnesia:transaction(F). mnesia:transaction(F).
%% Register connection %% Register connection
register_connection(SID, #jid{luser = LUser, lserver = LServer}, Info) -> register_connection(SID,
AuthModule = xml:get_attr_s(auth_module, Info), #jid{luser = LUser, lserver = LServer}, Info) ->
case AuthModule == ?MODULE of AuthModule = list_to_atom(binary_to_list(xml:get_attr_s(<<"auth_module">>, Info))),
case AuthModule == (?MODULE) of
true -> true ->
ejabberd_hooks:run(register_user, LServer, [LUser, LServer]), ejabberd_hooks:run(register_user, LServer,
[LUser, LServer]),
US = {LUser, LServer}, US = {LUser, LServer},
mnesia:sync_dirty( mnesia:sync_dirty(fun () ->
fun() -> mnesia:write(#anonymous{us = US, sid=SID}) mnesia:write(#anonymous{us = US,
sid = SID})
end); end);
false -> false -> ok
ok
end. end.
%% Remove an anonymous user from the anonymous users table %% Remove an anonymous user from the anonymous users table
unregister_connection(SID, #jid{luser = LUser, lserver = LServer}, _) -> unregister_connection(SID,
purge_hook(anonymous_user_exist(LUser, LServer), #jid{luser = LUser, lserver = LServer}, _) ->
LUser, LServer), purge_hook(anonymous_user_exist(LUser, LServer), LUser,
LServer),
remove_connection(SID, LUser, LServer). remove_connection(SID, LUser, LServer).
%% Launch the hook to purge user data only for anonymous users %% Launch the hook to purge user data only for anonymous users
purge_hook(false, _LUser, _LServer) -> purge_hook(false, _LUser, _LServer) ->
ok; ok;
purge_hook(true, LUser, LServer) -> purge_hook(true, LUser, LServer) ->
ejabberd_hooks:run(anonymous_purge_hook, LServer, [LUser, LServer]). ejabberd_hooks:run(anonymous_purge_hook, LServer,
[LUser, LServer]).
%% --------------------------------- %% ---------------------------------
%% Specific anonymous auth functions %% Specific anonymous auth functions
@ -172,12 +175,15 @@ purge_hook(true, LUser, LServer) ->
%% When anonymous login is enabled, check the password for permenant users %% When anonymous login is enabled, check the password for permenant users
%% before allowing access %% before allowing access
check_password(User, Server, Password) -> check_password(User, Server, Password) ->
check_password(User, Server, Password, undefined, undefined). check_password(User, Server, Password, undefined,
check_password(User, Server, _Password, _Digest, _DigestGen) -> undefined).
%% We refuse login for registered accounts (They cannot logged but
%% they however are "reserved") check_password(User, Server, _Password, _Digest,
case ejabberd_auth:is_user_exists_in_other_modules(?MODULE, _DigestGen) ->
User, Server) of case
ejabberd_auth:is_user_exists_in_other_modules(?MODULE,
User, Server)
of
%% If user exists in other module, reject anonnymous authentication %% If user exists in other module, reject anonnymous authentication
true -> false; true -> false;
%% If we are not sure whether the user exists in other module, reject anon auth %% If we are not sure whether the user exists in other module, reject anon auth
@ -203,10 +209,8 @@ login(User, Server) ->
%% changing its password %% changing its password
set_password(User, Server, _Password) -> set_password(User, Server, _Password) ->
case anonymous_user_exist(User, Server) of case anonymous_user_exist(User, Server) of
true -> true -> ok;
ok; false -> {error, not_allowed}
false ->
{error, not_allowed}
end. end.
%% When anonymous login is enabled, check if permanent users are allowed on %% When anonymous login is enabled, check if permanent users are allowed on
@ -214,25 +218,42 @@ set_password(User, Server, _Password) ->
try_register(_User, _Server, _Password) -> try_register(_User, _Server, _Password) ->
{error, not_allowed}. {error, not_allowed}.
dirty_get_registered_users() -> dirty_get_registered_users() -> [].
[].
get_vh_registered_users(Server) -> get_vh_registered_users(Server) ->
[{U, S} || {U, S, _R} <- ejabberd_sm:get_vh_session_list(Server)]. [{U, S}
|| {U, S, _R}
<- ejabberd_sm:get_vh_session_list(Server)].
get_vh_registered_users(Server, _) ->
get_vh_registered_users(Server).
get_vh_registered_users_number(Server) ->
length(get_vh_registered_users(Server)).
get_vh_registered_users_number(Server, _) ->
get_vh_registered_users_number(Server).
%% Return password of permanent user or false for anonymous users %% Return password of permanent user or false for anonymous users
get_password(User, Server) -> get_password(User, Server) ->
get_password(User, Server, ""). get_password(User, Server, <<"">>).
get_password(User, Server, DefaultValue) -> get_password(User, Server, DefaultValue) ->
case anonymous_user_exist(User, Server) or login(User, Server) of case anonymous_user_exist(User, Server) or
login(User, Server)
of
%% We return the default value if the user is anonymous %% We return the default value if the user is anonymous
true -> true -> DefaultValue;
DefaultValue;
%% We return the permanent user password otherwise %% We return the permanent user password otherwise
false -> false
end.
get_password_s(User, Server) ->
case get_password(User, Server) of
false -> false ->
false <<"">>;
Password ->
Password
end. end.
%% Returns true if the user exists in the DB or if an anonymous user is logged %% Returns true if the user exists in the DB or if an anonymous user is logged
@ -240,14 +261,11 @@ get_password(User, Server, DefaultValue) ->
is_user_exists(User, Server) -> is_user_exists(User, Server) ->
anonymous_user_exist(User, Server). anonymous_user_exist(User, Server).
remove_user(_User, _Server) -> remove_user(_User, _Server) -> {error, not_allowed}.
{error, not_allowed}.
remove_user(_User, _Server, _Password) -> remove_user(_User, _Server, _Password) -> not_allowed.
not_allowed.
plain_password_required() -> plain_password_required() -> false.
false.
store_type() -> store_type() ->
plain. plain.

View File

@ -25,27 +25,21 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(ejabberd_auth_external). -module(ejabberd_auth_external).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(ejabberd_auth).
%% External exports %% External exports
-export([start/1, -export([start/1, set_password/3, check_password/3,
set_password/3, check_password/5, try_register/3,
check_password/3, dirty_get_registered_users/0, get_vh_registered_users/1,
check_password/5,
try_register/3,
dirty_get_registered_users/0,
get_vh_registered_users/1,
get_vh_registered_users/2, get_vh_registered_users/2,
get_vh_registered_users_number/1, get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_vh_registered_users_number/2, get_password/2,
get_password/2, get_password_s/2, is_user_exists/2, remove_user/2,
get_password_s/2, remove_user/3, store_type/0,
is_user_exists/2, plain_password_required/0]).
remove_user/2,
remove_user/3,
store_type/0,
plain_password_required/0
]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
@ -53,55 +47,59 @@
%%% API %%% API
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
start(Host) -> start(Host) ->
extauth:start( Cmd = ejabberd_config:get_local_option(
Host, ejabberd_config:get_local_option({extauth_program, Host})), {extauth_program, Host},
fun(V) ->
binary_to_list(iolist_to_binary(V))
end,
"extauth"),
extauth:start(Host, Cmd),
case check_cache_last_options(Host) of case check_cache_last_options(Host) of
cache -> cache -> ok = ejabberd_auth_internal:start(Host);
ok = ejabberd_auth_internal:start(Host); no_cache -> ok
no_cache ->
ok
end. end.
check_cache_last_options(Server) -> check_cache_last_options(Server) ->
%% if extauth_cache is enabled, then a mod_last module must also be enabled
case get_cache_option(Server) of case get_cache_option(Server) of
false -> no_cache; false -> no_cache;
{true, _CacheTime} -> {true, _CacheTime} ->
case get_mod_last_configured(Server) of case get_mod_last_configured(Server) of
no_mod_last -> no_mod_last ->
?ERROR_MSG("In host ~p extauth is used, extauth_cache is enabled but " ?ERROR_MSG("In host ~p extauth is used, extauth_cache "
"mod_last is not enabled.", [Server]), "is enabled but mod_last is not enabled.",
[Server]),
no_cache; no_cache;
_ -> cache _ -> cache
end end
end. end.
plain_password_required() -> plain_password_required() -> true.
true.
store_type() -> store_type() -> external.
external.
check_password(User, Server, Password) -> check_password(User, Server, Password) ->
case get_cache_option(Server) of case get_cache_option(Server) of
false -> check_password_extauth(User, Server, Password); false -> check_password_extauth(User, Server, Password);
{true, CacheTime} -> check_password_cache(User, Server, Password, CacheTime) {true, CacheTime} ->
check_password_cache(User, Server, Password, CacheTime)
end. end.
check_password(User, Server, Password, _Digest, _DigestGen) -> check_password(User, Server, Password, _Digest,
_DigestGen) ->
check_password(User, Server, Password). check_password(User, Server, Password).
set_password(User, Server, Password) -> set_password(User, Server, Password) ->
case extauth:set_password(User, Server, Password) of case extauth:set_password(User, Server, Password) of
true -> set_password_internal(User, Server, Password), true ->
ok; set_password_internal(User, Server, Password), ok;
_ -> {error, unknown_problem} _ -> {error, unknown_problem}
end. end.
try_register(User, Server, Password) -> try_register(User, Server, Password) ->
case get_cache_option(Server) of case get_cache_option(Server) of
false -> try_register_extauth(User, Server, Password); false -> try_register_extauth(User, Server, Password);
{true, _CacheTime} -> try_register_external_cache(User, Server, Password) {true, _CacheTime} ->
try_register_external_cache(User, Server, Password)
end. end.
dirty_get_registered_users() -> dirty_get_registered_users() ->
@ -111,24 +109,27 @@ get_vh_registered_users(Server) ->
ejabberd_auth_internal:get_vh_registered_users(Server). ejabberd_auth_internal:get_vh_registered_users(Server).
get_vh_registered_users(Server, Data) -> get_vh_registered_users(Server, Data) ->
ejabberd_auth_internal:get_vh_registered_users(Server, Data). ejabberd_auth_internal:get_vh_registered_users(Server,
Data).
get_vh_registered_users_number(Server) -> get_vh_registered_users_number(Server) ->
ejabberd_auth_internal:get_vh_registered_users_number(Server). ejabberd_auth_internal:get_vh_registered_users_number(Server).
get_vh_registered_users_number(Server, Data) -> get_vh_registered_users_number(Server, Data) ->
ejabberd_auth_internal:get_vh_registered_users_number(Server, Data). ejabberd_auth_internal:get_vh_registered_users_number(Server,
Data).
%% The password can only be returned if cache is enabled, cached info exists and is fresh enough. %% The password can only be returned if cache is enabled, cached info exists and is fresh enough.
get_password(User, Server) -> get_password(User, Server) ->
case get_cache_option(Server) of case get_cache_option(Server) of
false -> false; false -> false;
{true, CacheTime} -> get_password_cache(User, Server, CacheTime) {true, CacheTime} ->
get_password_cache(User, Server, CacheTime)
end. end.
get_password_s(User, Server) -> get_password_s(User, Server) ->
case get_password(User, Server) of case get_password(User, Server) of
false -> []; false -> <<"">>;
Other -> Other Other -> Other
end. end.
@ -158,7 +159,8 @@ remove_user(User, Server, Password) ->
case get_cache_option(Server) of case get_cache_option(Server) of
false -> false; false -> false;
{true, _CacheTime} -> {true, _CacheTime} ->
ejabberd_auth_internal:remove_user(User, Server, Password) ejabberd_auth_internal:remove_user(User, Server,
Password)
end end
end. end.
@ -168,37 +170,42 @@ remove_user(User, Server, Password) ->
%% @spec (Host::string()) -> false | {true, CacheTime::integer()} %% @spec (Host::string()) -> false | {true, CacheTime::integer()}
get_cache_option(Host) -> get_cache_option(Host) ->
case ejabberd_config:get_local_option({extauth_cache, Host}) of case ejabberd_config:get_local_option(
CacheTime when is_integer(CacheTime) -> {true, CacheTime}; {extauth_cache, Host},
_ -> false fun(I) when is_integer(I), I > 0 -> I end) of
undefined -> false;
CacheTime -> {true, CacheTime}
end. end.
%% @spec (User, Server, Password) -> true | false %% @spec (User, Server, Password) -> true | false
check_password_extauth(User, Server, Password) -> check_password_extauth(User, Server, Password) ->
extauth:check_password(User, Server, Password) andalso Password /= "". extauth:check_password(User, Server, Password) andalso
Password /= <<"">>.
%% @spec (User, Server, Password) -> true | false %% @spec (User, Server, Password) -> true | false
try_register_extauth(User, Server, Password) -> try_register_extauth(User, Server, Password) ->
extauth:try_register(User, Server, Password). extauth:try_register(User, Server, Password).
check_password_cache(User, Server, Password, CacheTime) -> check_password_cache(User, Server, Password,
CacheTime) ->
case get_last_access(User, Server) of case get_last_access(User, Server) of
online -> online ->
check_password_internal(User, Server, Password); check_password_internal(User, Server, Password);
never -> never ->
check_password_external_cache(User, Server, Password); check_password_external_cache(User, Server, Password);
mod_last_required -> mod_last_required ->
?ERROR_MSG("extauth is used, extauth_cache is enabled but mod_last is not enabled in that host", []), ?ERROR_MSG("extauth is used, extauth_cache is enabled "
"but mod_last is not enabled in that "
"host",
[]),
check_password_external_cache(User, Server, Password); check_password_external_cache(User, Server, Password);
TimeStamp -> TimeStamp ->
%% If last access exists, compare last access with cache refresh time
case is_fresh_enough(TimeStamp, CacheTime) of case is_fresh_enough(TimeStamp, CacheTime) of
%% If no need to refresh, check password against Mnesia %% If no need to refresh, check password against Mnesia
true -> true ->
case check_password_internal(User, Server, Password) of case check_password_internal(User, Server, Password) of
%% If password valid in Mnesia, accept it %% If password valid in Mnesia, accept it
true -> true -> true;
true;
%% Else (password nonvalid in Mnesia), check in extauth and cache result %% Else (password nonvalid in Mnesia), check in extauth and cache result
false -> false ->
check_password_external_cache(User, Server, Password) check_password_external_cache(User, Server, Password)
@ -215,60 +222,54 @@ get_password_internal(User, Server) ->
%% @spec (User, Server, CacheTime) -> false | Password::string() %% @spec (User, Server, CacheTime) -> false | Password::string()
get_password_cache(User, Server, CacheTime) -> get_password_cache(User, Server, CacheTime) ->
case get_last_access(User, Server) of case get_last_access(User, Server) of
online -> online -> get_password_internal(User, Server);
get_password_internal(User, Server); never -> false;
never ->
false;
mod_last_required -> mod_last_required ->
?ERROR_MSG("extauth is used, extauth_cache is enabled but mod_last is not enabled in that host", []), ?ERROR_MSG("extauth is used, extauth_cache is enabled "
"but mod_last is not enabled in that "
"host",
[]),
false; false;
TimeStamp -> TimeStamp ->
case is_fresh_enough(TimeStamp, CacheTime) of case is_fresh_enough(TimeStamp, CacheTime) of
true -> true -> get_password_internal(User, Server);
get_password_internal(User, Server); false -> false
false ->
false
end end
end. end.
%% Check the password using extauth; if success then cache it %% Check the password using extauth; if success then cache it
check_password_external_cache(User, Server, Password) -> check_password_external_cache(User, Server, Password) ->
case check_password_extauth(User, Server, Password) of case check_password_extauth(User, Server, Password) of
true -> true ->
set_password_internal(User, Server, Password), true; set_password_internal(User, Server, Password), true;
false -> false -> false
false
end. end.
%% Try to register using extauth; if success then cache it %% Try to register using extauth; if success then cache it
try_register_external_cache(User, Server, Password) -> try_register_external_cache(User, Server, Password) ->
case try_register_extauth(User, Server, Password) of case try_register_extauth(User, Server, Password) of
{atomic, ok} = R -> {atomic, ok} = R ->
set_password_internal(User, Server, Password), set_password_internal(User, Server, Password), R;
R;
_ -> {error, not_allowed} _ -> {error, not_allowed}
end. end.
%% @spec (User, Server, Password) -> true | false %% @spec (User, Server, Password) -> true | false
check_password_internal(User, Server, Password) -> check_password_internal(User, Server, Password) ->
ejabberd_auth_internal:check_password(User, Server, Password). ejabberd_auth_internal:check_password(User, Server,
Password).
%% @spec (User, Server, Password) -> ok | {error, invalid_jid} %% @spec (User, Server, Password) -> ok | {error, invalid_jid}
set_password_internal(User, Server, Password) -> set_password_internal(User, Server, Password) ->
ejabberd_auth_internal:set_password(User, Server, Password).
%% @spec (TimeLast, CacheTime) -> true | false %% @spec (TimeLast, CacheTime) -> true | false
%% TimeLast = online | never | integer() %% TimeLast = online | never | integer()
%% CacheTime = integer() | false %% CacheTime = integer() | false
is_fresh_enough(online, _CacheTime) -> ejabberd_auth_internal:set_password(User, Server,
true; Password).
is_fresh_enough(never, _CacheTime) ->
false;
is_fresh_enough(TimeStampLast, CacheTime) -> is_fresh_enough(TimeStampLast, CacheTime) ->
{MegaSecs, Secs, _MicroSecs} = now(), {MegaSecs, Secs, _MicroSecs} = now(),
Now = MegaSecs * 1000000 + Secs, Now = MegaSecs * 1000000 + Secs,
(TimeStampLast + CacheTime > Now). TimeStampLast + CacheTime > Now.
%% @spec (User, Server) -> online | never | mod_last_required | TimeStamp::integer() %% @spec (User, Server) -> online | never | mod_last_required | TimeStamp::integer()
%% Code copied from mod_configure.erl %% Code copied from mod_configure.erl
@ -279,17 +280,14 @@ get_last_access(User, Server) ->
[] -> [] ->
_US = {User, Server}, _US = {User, Server},
case get_last_info(User, Server) of case get_last_info(User, Server) of
mod_last_required -> mod_last_required -> mod_last_required;
mod_last_required; not_found -> never;
not_found -> {ok, Timestamp, _Status} -> Timestamp
never;
{ok, Timestamp, _Status} ->
Timestamp
end; end;
_ -> _ -> online
online
end. end.
%% @spec (User, Server) -> {ok, Timestamp, Status} | not_found | mod_last_required %% @spec (User, Server) -> {ok, Timestamp, Status} | not_found | mod_last_required
get_last_info(User, Server) -> get_last_info(User, Server) ->
case get_mod_last_enabled(Server) of case get_mod_last_enabled(Server) of
mod_last -> mod_last:get_last_info(User, Server); mod_last -> mod_last:get_last_info(User, Server);
@ -310,4 +308,4 @@ get_mod_last_configured(Server) ->
end. end.
is_configured(Host, Module) -> is_configured(Host, Module) ->
lists:keymember(Module, 1, ejabberd_config:get_local_option({modules, Host})). gen_mod:is_loaded(Host, Module).

View File

@ -25,32 +25,29 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(ejabberd_auth_internal). -module(ejabberd_auth_internal).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(ejabberd_auth).
%% External exports %% External exports
-export([start/1, -export([start/1, set_password/3, check_password/3,
set_password/3, check_password/5, try_register/3,
check_password/3, dirty_get_registered_users/0, get_vh_registered_users/1,
check_password/5,
try_register/3,
dirty_get_registered_users/0,
get_vh_registered_users/1,
get_vh_registered_users/2, get_vh_registered_users/2,
get_vh_registered_users_number/1, get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_vh_registered_users_number/2, get_password/2,
get_password/2, get_password_s/2, is_user_exists/2, remove_user/2,
get_password_s/2, remove_user/3, store_type/0, export/1,
is_user_exists/2, plain_password_required/0]).
remove_user/2,
remove_user/3,
store_type/0,
plain_password_required/0
]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-record(passwd, {us, password}). -record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
-record(reg_users_counter, {vhost, count}). password = <<"">> :: binary() | scram() | '_'}).
-record(reg_users_counter, {vhost = <<"">> :: binary(),
count = 0 :: integer() | '$1'}).
-define(SALT_LENGTH, 16). -define(SALT_LENGTH, 16).
@ -58,7 +55,8 @@
%%% API %%% API
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
start(Host) -> start(Host) ->
mnesia:create_table(passwd, [{disc_copies, [node()]}, mnesia:create_table(passwd,
[{disc_copies, [node()]},
{attributes, record_info(fields, passwd)}]), {attributes, record_info(fields, passwd)}]),
mnesia:create_table(reg_users_counter, mnesia:create_table(reg_users_counter,
[{ram_copies, [node()]}, [{ram_copies, [node()]},
@ -95,46 +93,40 @@ check_password(User, Server, Password) ->
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
US = {LUser, LServer}, US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of case catch mnesia:dirty_read({passwd, US}) of
[#passwd{password = Password}] when is_list(Password) -> [#passwd{password = Password}]
Password /= ""; when is_binary(Password) ->
[#passwd{password = Scram}] when is_record(Scram, scram) -> Password /= <<"">>;
[#passwd{password = Scram}]
when is_record(Scram, scram) ->
is_password_scram_valid(Password, Scram); is_password_scram_valid(Password, Scram);
_ -> _ -> false
false
end. end.
check_password(User, Server, Password, Digest, DigestGen) -> check_password(User, Server, Password, Digest,
DigestGen) ->
LUser = jlib:nodeprep(User), LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
US = {LUser, LServer}, US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of case catch mnesia:dirty_read({passwd, US}) of
[#passwd{password = Passwd}] when is_list(Passwd) -> [#passwd{password = Passwd}] when is_binary(Passwd) ->
DigRes = if DigRes = if Digest /= <<"">> ->
Digest /= "" ->
Digest == DigestGen(Passwd); Digest == DigestGen(Passwd);
true -> true -> false
false
end, end,
if DigRes -> if DigRes -> true;
true; true -> (Passwd == Password) and (Password /= <<"">>)
true ->
(Passwd == Password) and (Password /= "")
end; end;
[#passwd{password = Scram}] when is_record(Scram, scram) -> [#passwd{password = Scram}]
Passwd = base64:decode(Scram#scram.storedkey), when is_record(Scram, scram) ->
DigRes = if Passwd = jlib:decode_base64(Scram#scram.storedkey),
Digest /= "" -> DigRes = if Digest /= <<"">> ->
Digest == DigestGen(Passwd); Digest == DigestGen(Passwd);
true -> true -> false
false
end, end,
if DigRes -> if DigRes -> true;
true; true -> (Passwd == Password) and (Password /= <<"">>)
true ->
(Passwd == Password) and (Password /= "")
end; end;
_ -> _ -> false
false
end. end.
%% @spec (User::string(), Server::string(), Password::string()) -> %% @spec (User::string(), Server::string(), Password::string()) ->
@ -143,46 +135,45 @@ set_password(User, Server, Password) ->
LUser = jlib:nodeprep(User), LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
US = {LUser, LServer}, US = {LUser, LServer},
if if (LUser == error) or (LServer == error) ->
(LUser == error) or (LServer == error) ->
{error, invalid_jid}; {error, invalid_jid};
true -> true ->
F = fun () -> F = fun () ->
Password2 = case is_scrammed() and is_list(Password) of Password2 = case is_scrammed() and is_binary(Password)
of
true -> password_to_scram(Password); true -> password_to_scram(Password);
false -> Password false -> Password
end, end,
mnesia:write(#passwd{us = US, mnesia:write(#passwd{us = US, password = Password2})
password = Password2})
end, end,
{atomic, ok} = mnesia:transaction(F), {atomic, ok} = mnesia:transaction(F),
ok ok
end. end.
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} | {aborted, Reason} %% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} | {aborted, Reason}
try_register(User, Server, Password) -> try_register(User, Server, PasswordList) ->
LUser = jlib:nodeprep(User), LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
Password = iolist_to_binary(PasswordList),
US = {LUser, LServer}, US = {LUser, LServer},
if if (LUser == error) or (LServer == error) ->
(LUser == error) or (LServer == error) ->
{error, invalid_jid}; {error, invalid_jid};
true -> true ->
F = fun () -> F = fun () ->
case mnesia:read({passwd, US}) of case mnesia:read({passwd, US}) of
[] -> [] ->
Password2 = case is_scrammed() and is_list(Password) of Password2 = case is_scrammed() and
is_binary(Password)
of
true -> password_to_scram(Password); true -> password_to_scram(Password);
false -> Password false -> Password
end, end,
mnesia:write(#passwd{us = US, mnesia:write(#passwd{us = US,
password = Password2}), password = Password2}),
mnesia:dirty_update_counter( mnesia:dirty_update_counter(reg_users_counter,
reg_users_counter,
LServer, 1), LServer, 1),
ok; ok;
[_E] -> [_E] -> exists
exists
end end
end, end,
mnesia:transaction(F) mnesia:transaction(F)
@ -194,21 +185,20 @@ dirty_get_registered_users() ->
get_vh_registered_users(Server) -> get_vh_registered_users(Server) ->
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
mnesia:dirty_select( mnesia:dirty_select(passwd,
passwd,
[{#passwd{us = '$1', _ = '_'}, [{#passwd{us = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, LServer}], [{'==', {element, 2, '$1'}, LServer}], ['$1']}]).
['$1']}]).
get_vh_registered_users(Server, [{from, Start}, {to, End}]) get_vh_registered_users(Server,
[{from, Start}, {to, End}])
when is_integer(Start) and is_integer(End) -> when is_integer(Start) and is_integer(End) ->
get_vh_registered_users(Server, [{limit, End-Start+1}, {offset, Start}]); get_vh_registered_users(Server,
[{limit, End - Start + 1}, {offset, Start}]);
get_vh_registered_users(Server, [{limit, Limit}, {offset, Offset}]) get_vh_registered_users(Server,
[{limit, Limit}, {offset, Offset}])
when is_integer(Limit) and is_integer(Offset) -> when is_integer(Limit) and is_integer(Offset) ->
case get_vh_registered_users(Server) of case get_vh_registered_users(Server) of
[] -> [] -> [];
[];
Users -> Users ->
Set = lists:keysort(1, Users), Set = lists:keysort(1, Users),
L = length(Set), L = length(Set),
@ -218,21 +208,28 @@ get_vh_registered_users(Server, [{limit, Limit}, {offset, Offset}])
end, end,
lists:sublist(Set, Start, Limit) lists:sublist(Set, Start, Limit)
end; end;
get_vh_registered_users(Server, [{prefix, Prefix}]) get_vh_registered_users(Server, [{prefix, Prefix}])
when is_list(Prefix) -> when is_binary(Prefix) ->
Set = [{U,S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)], Set = [{U, S}
|| {U, S} <- get_vh_registered_users(Server),
str:prefix(Prefix, U)],
lists:keysort(1, Set); lists:keysort(1, Set);
get_vh_registered_users(Server,
get_vh_registered_users(Server, [{prefix, Prefix}, {from, Start}, {to, End}]) [{prefix, Prefix}, {from, Start}, {to, End}])
when is_list(Prefix) and is_integer(Start) and is_integer(End) -> when is_binary(Prefix) and is_integer(Start) and
get_vh_registered_users(Server, [{prefix, Prefix}, {limit, End-Start+1}, {offset, Start}]); is_integer(End) ->
get_vh_registered_users(Server,
get_vh_registered_users(Server, [{prefix, Prefix}, {limit, Limit}, {offset, Offset}]) [{prefix, Prefix}, {limit, End - Start + 1},
when is_list(Prefix) and is_integer(Limit) and is_integer(Offset) -> {offset, Start}]);
case [{U,S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)] of get_vh_registered_users(Server,
[] -> [{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
[]; when is_binary(Prefix) and is_integer(Limit) and
is_integer(Offset) ->
case [{U, S}
|| {U, S} <- get_vh_registered_users(Server),
str:prefix(Prefix, U)]
of
[] -> [];
Users -> Users ->
Set = lists:keysort(1, Users), Set = lists:keysort(1, Users),
L = length(Set), L = length(Set),
@ -242,27 +239,27 @@ get_vh_registered_users(Server, [{prefix, Prefix}, {limit, Limit}, {offset, Offs
end, end,
lists:sublist(Set, Start, Limit) lists:sublist(Set, Start, Limit)
end; end;
get_vh_registered_users(Server, _) -> get_vh_registered_users(Server, _) ->
get_vh_registered_users(Server). get_vh_registered_users(Server).
get_vh_registered_users_number(Server) -> get_vh_registered_users_number(Server) ->
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
Query = mnesia:dirty_select( Query = mnesia:dirty_select(reg_users_counter,
reg_users_counter, [{#reg_users_counter{vhost = LServer,
[{#reg_users_counter{vhost = LServer, count = '$1'}, count = '$1'},
[], [], ['$1']}]),
['$1']}]),
case Query of case Query of
[Count] -> [Count] -> Count;
Count;
_ -> 0 _ -> 0
end. end.
get_vh_registered_users_number(Server, [{prefix, Prefix}]) when is_list(Prefix) -> get_vh_registered_users_number(Server,
Set = [{U, S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)], [{prefix, Prefix}])
when is_binary(Prefix) ->
Set = [{U, S}
|| {U, S} <- get_vh_registered_users(Server),
str:prefix(Prefix, U)],
length(Set); length(Set);
get_vh_registered_users_number(Server, _) -> get_vh_registered_users_number(Server, _) ->
get_vh_registered_users_number(Server). get_vh_registered_users_number(Server).
@ -271,15 +268,16 @@ get_password(User, Server) ->
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
US = {LUser, LServer}, US = {LUser, LServer},
case catch mnesia:dirty_read(passwd, US) of case catch mnesia:dirty_read(passwd, US) of
[#passwd{password = Password}] when is_list(Password) -> [#passwd{password = Password}]
when is_binary(Password) ->
Password; Password;
[#passwd{password = Scram}] when is_record(Scram, scram) -> [#passwd{password = Scram}]
{base64:decode(Scram#scram.storedkey), when is_record(Scram, scram) ->
base64:decode(Scram#scram.serverkey), {jlib:decode_base64(Scram#scram.storedkey),
base64:decode(Scram#scram.salt), jlib:decode_base64(Scram#scram.serverkey),
jlib:decode_base64(Scram#scram.salt),
Scram#scram.iterationcount}; Scram#scram.iterationcount};
_ -> _ -> false
false
end. end.
get_password_s(User, Server) -> get_password_s(User, Server) ->
@ -287,12 +285,13 @@ get_password_s(User, Server) ->
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
US = {LUser, LServer}, US = {LUser, LServer},
case catch mnesia:dirty_read(passwd, US) of case catch mnesia:dirty_read(passwd, US) of
[#passwd{password = Password}] when is_list(Password) -> [#passwd{password = Password}]
when is_binary(Password) ->
Password; Password;
[#passwd{password = Scram}] when is_record(Scram, scram) -> [#passwd{password = Scram}]
[]; when is_record(Scram, scram) ->
_ -> <<"">>;
[] _ -> <<"">>
end. end.
%% @spec (User, Server) -> true | false | {error, Error} %% @spec (User, Server) -> true | false | {error, Error}
@ -301,12 +300,9 @@ is_user_exists(User, Server) ->
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
US = {LUser, LServer}, US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of case catch mnesia:dirty_read({passwd, US}) of
[] -> [] -> false;
false; [_] -> true;
[_] -> Other -> {error, Other}
true;
Other ->
{error, Other}
end. end.
%% @spec (User, Server) -> ok %% @spec (User, Server) -> ok
@ -318,8 +314,8 @@ remove_user(User, Server) ->
US = {LUser, LServer}, US = {LUser, LServer},
F = fun () -> F = fun () ->
mnesia:delete({passwd, US}), mnesia:delete({passwd, US}),
mnesia:dirty_update_counter(reg_users_counter, mnesia:dirty_update_counter(reg_users_counter, LServer,
LServer, -1) -1)
end, end,
mnesia:transaction(F), mnesia:transaction(F),
ok. ok.
@ -332,77 +328,63 @@ remove_user(User, Server, Password) ->
US = {LUser, LServer}, US = {LUser, LServer},
F = fun () -> F = fun () ->
case mnesia:read({passwd, US}) of case mnesia:read({passwd, US}) of
[#passwd{password = Password}] when is_list(Password) -> [#passwd{password = Password}]
when is_binary(Password) ->
mnesia:delete({passwd, US}), mnesia:delete({passwd, US}),
mnesia:dirty_update_counter(reg_users_counter, mnesia:dirty_update_counter(reg_users_counter, LServer,
LServer, -1), -1),
ok; ok;
[#passwd{password = Scram}] when is_record(Scram, scram) -> [#passwd{password = Scram}]
when is_record(Scram, scram) ->
case is_password_scram_valid(Password, Scram) of case is_password_scram_valid(Password, Scram) of
true -> true ->
mnesia:delete({passwd, US}), mnesia:delete({passwd, US}),
mnesia:dirty_update_counter(reg_users_counter, mnesia:dirty_update_counter(reg_users_counter,
LServer, -1), LServer, -1),
ok; ok;
false -> false -> not_allowed
not_allowed
end; end;
_ -> _ -> not_exists
not_exists
end end
end, end,
case mnesia:transaction(F) of case mnesia:transaction(F) of
{atomic, ok} -> {atomic, ok} -> ok;
ok; {atomic, Res} -> Res;
{atomic, Res} -> _ -> bad_request
Res;
_ ->
bad_request
end. end.
update_table() -> update_table() ->
Fields = record_info(fields, passwd), Fields = record_info(fields, passwd),
case mnesia:table_info(passwd, attributes) of case mnesia:table_info(passwd, attributes) of
Fields -> Fields ->
convert_to_binary(Fields),
maybe_scram_passwords(), maybe_scram_passwords(),
ok; ok;
[user, password] ->
?INFO_MSG("Converting passwd table from "
"{user, password} format", []),
Host = ?MYNAME,
{atomic, ok} = mnesia:create_table(
ejabberd_auth_internal_tmp_table,
[{disc_only_copies, [node()]},
{type, bag},
{local_content, true},
{record_name, passwd},
{attributes, record_info(fields, passwd)}]),
mnesia:transform_table(passwd, ignore, Fields),
F1 = fun() ->
mnesia:write_lock_table(ejabberd_auth_internal_tmp_table),
mnesia:foldl(
fun(#passwd{us = U} = R, _) ->
mnesia:dirty_write(
ejabberd_auth_internal_tmp_table,
R#passwd{us = {U, Host}})
end, ok, passwd)
end,
mnesia:transaction(F1),
mnesia:clear_table(passwd),
F2 = fun() ->
mnesia:write_lock_table(passwd),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, ejabberd_auth_internal_tmp_table)
end,
mnesia:transaction(F2),
mnesia:delete_table(ejabberd_auth_internal_tmp_table);
_ -> _ ->
?INFO_MSG("Recreating passwd table", []), ?INFO_MSG("Recreating passwd table", []),
mnesia:transform_table(passwd, ignore, Fields) mnesia:transform_table(passwd, ignore, Fields)
end. end.
convert_to_binary(Fields) ->
ejabberd_config:convert_table_to_binary(
passwd, Fields, set,
fun(#passwd{us = {U, _}}) -> U end,
fun(#passwd{us = {U, S}, password = Pass} = R) ->
NewUS = {iolist_to_binary(U), iolist_to_binary(S)},
NewPass = case Pass of
#scram{storedkey = StoredKey,
serverkey = ServerKey,
salt = Salt} ->
Pass#scram{
storedkey = iolist_to_binary(StoredKey),
serverkey = iolist_to_binary(ServerKey),
salt = iolist_to_binary(Salt)};
_ ->
iolist_to_binary(Pass)
end,
R#passwd{us = NewUS, password = NewPass}
end).
%%% %%%
%%% SCRAM %%% SCRAM
%%% %%%
@ -411,27 +393,30 @@ update_table() ->
%% or if at least the first password is scrammed. %% or if at least the first password is scrammed.
is_scrammed() -> is_scrammed() ->
OptionScram = is_option_scram(), OptionScram = is_option_scram(),
FirstElement = mnesia:dirty_read(passwd, mnesia:dirty_first(passwd)), FirstElement = mnesia:dirty_read(passwd,
mnesia:dirty_first(passwd)),
case {OptionScram, FirstElement} of case {OptionScram, FirstElement} of
{true, _} -> {true, _} -> true;
{false, [#passwd{password = Scram}]}
when is_record(Scram, scram) ->
true; true;
{false, [#passwd{password = Scram}]} when is_record(Scram, scram) -> _ -> false
true;
_ ->
false
end. end.
is_option_scram() -> is_option_scram() ->
scram == ejabberd_config:get_local_option({auth_password_format, ?MYNAME}). scram ==
ejabberd_config:get_local_option({auth_password_format, ?MYNAME},
fun(V) -> V end).
maybe_alert_password_scrammed_without_option() -> maybe_alert_password_scrammed_without_option() ->
case is_scrammed() andalso not is_option_scram() of case is_scrammed() andalso not is_option_scram() of
true -> true ->
?ERROR_MSG("Some passwords were stored in the database as SCRAM, " ?ERROR_MSG("Some passwords were stored in the database "
"but 'auth_password_format' is not configured 'scram'. " "as SCRAM, but 'auth_password_format' "
"The option will now be considered to be 'scram'.", []); "is not configured 'scram'. The option "
false -> "will now be considered to be 'scram'.",
ok []);
false -> ok
end. end.
maybe_scram_passwords() -> maybe_scram_passwords() ->
@ -441,7 +426,9 @@ maybe_scram_passwords() ->
end. end.
scram_passwords() -> scram_passwords() ->
?INFO_MSG("Converting the stored passwords into SCRAM bits", []), ?INFO_MSG("Converting the stored passwords into "
"SCRAM bits",
[]),
Fun = fun (#passwd{password = Password} = P) -> Fun = fun (#passwd{password = Password} = P) ->
Scram = password_to_scram(Password), Scram = password_to_scram(Password),
P#passwd{password = Scram} P#passwd{password = Scram}
@ -450,21 +437,39 @@ scram_passwords() ->
mnesia:transform_table(passwd, Fun, Fields). mnesia:transform_table(passwd, Fun, Fields).
password_to_scram(Password) -> password_to_scram(Password) ->
password_to_scram(Password, ?SCRAM_DEFAULT_ITERATION_COUNT). password_to_scram(Password,
?SCRAM_DEFAULT_ITERATION_COUNT).
password_to_scram(Password, IterationCount) -> password_to_scram(Password, IterationCount) ->
Salt = crypto:rand_bytes(?SALT_LENGTH), Salt = crypto:rand_bytes(?SALT_LENGTH),
SaltedPassword = scram:salted_password(Password, Salt, IterationCount), SaltedPassword = scram:salted_password(Password, Salt,
StoredKey = scram:stored_key(scram:client_key(SaltedPassword)), IterationCount),
StoredKey =
scram:stored_key(scram:client_key(SaltedPassword)),
ServerKey = scram:server_key(SaltedPassword), ServerKey = scram:server_key(SaltedPassword),
#scram{storedkey = base64:encode(StoredKey), #scram{storedkey = jlib:encode_base64(StoredKey),
serverkey = base64:encode(ServerKey), serverkey = jlib:encode_base64(ServerKey),
salt = base64:encode(Salt), salt = jlib:encode_base64(Salt),
iterationcount = IterationCount}. iterationcount = IterationCount}.
is_password_scram_valid(Password, Scram) -> is_password_scram_valid(Password, Scram) ->
IterationCount = Scram#scram.iterationcount, IterationCount = Scram#scram.iterationcount,
Salt = base64:decode(Scram#scram.salt), Salt = jlib:decode_base64(Scram#scram.salt),
SaltedPassword = scram:salted_password(Password, Salt, IterationCount), SaltedPassword = scram:salted_password(Password, Salt,
StoredKey = scram:stored_key(scram:client_key(SaltedPassword)), IterationCount),
(base64:decode(Scram#scram.storedkey) == StoredKey). StoredKey =
scram:stored_key(scram:client_key(SaltedPassword)),
jlib:decode_base64(Scram#scram.storedkey) == StoredKey.
export(_Server) ->
[{passwd,
fun(Host, #passwd{us = {LUser, LServer}, password = Password})
when LServer == Host ->
Username = ejabberd_odbc:escape(LUser),
Pass = ejabberd_odbc:escape(Password),
[[<<"delete from users where username='">>, Username, <<"';">>],
[<<"insert into users(username, password) "
"values ('">>, Username, <<"', '">>, Pass, <<"');">>]];
(_Host, _R) ->
[]
end}].

View File

@ -25,73 +25,59 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(ejabberd_auth_ldap). -module(ejabberd_auth_ldap).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(gen_server). -behaviour(gen_server).
-behaviour(ejabberd_auth).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, -export([init/1, handle_info/2, handle_call/3,
handle_info/2, handle_cast/2, terminate/2, code_change/3]).
handle_call/3,
handle_cast/2,
terminate/2,
code_change/3
]).
%% External exports %% External exports
-export([start/1, -export([start/1, stop/1, start_link/1, set_password/3,
stop/1, check_password/3, check_password/5, try_register/3,
start_link/1, dirty_get_registered_users/0, get_vh_registered_users/1,
set_password/3, get_vh_registered_users/2,
check_password/3,
check_password/5,
try_register/3,
dirty_get_registered_users/0,
get_vh_registered_users/1,
get_vh_registered_users_number/1, get_vh_registered_users_number/1,
get_password/2, get_vh_registered_users_number/2, get_password/2,
get_password_s/2, get_password_s/2, is_user_exists/2, remove_user/2,
is_user_exists/2, remove_user/3, store_type/0,
remove_user/2, plain_password_required/0]).
remove_user/3,
store_type/0,
plain_password_required/0
]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("eldap/eldap.hrl").
-record(state, {host,
eldap_id,
bind_eldap_id,
servers,
backups,
port,
tls_options,
dn,
password,
base,
uids,
ufilter,
sfilter,
lfilter, %% Local filter (performed by ejabberd, not LDAP)
deref_aliases,
dn_filter,
dn_filter_attrs
}).
%% Unused callbacks. %% Unused callbacks.
handle_cast(_Request, State) ->
{noreply, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
handle_info(_Info, State) ->
{noreply, State}.
%% ----- %% -----
-include("eldap/eldap.hrl").
-define(LDAP_SEARCH_TIMEOUT, 5). % Timeout for LDAP search queries in seconds -record(state,
{host = <<"">> :: binary(),
eldap_id = <<"">> :: binary(),
bind_eldap_id = <<"">> :: binary(),
servers = [] :: [binary()],
backups = [] :: [binary()],
port = ?LDAP_PORT :: inet:port_number(),
tls_options = [] :: list(),
dn = <<"">> :: binary(),
password = <<"">> :: binary(),
base = <<"">> :: binary(),
uids = [] :: [{binary()} | {binary(), binary()}],
ufilter = <<"">> :: binary(),
sfilter = <<"">> :: binary(),
lfilter :: {any(), any()},
deref_aliases = never :: never | searching | finding | always,
dn_filter :: binary(),
dn_filter_attrs = [] :: [binary()]}).
handle_cast(_Request, State) -> {noreply, State}.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
handle_info(_Info, State) -> {noreply, State}.
-define(LDAP_SEARCH_TIMEOUT, 5).
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% API %%% API
@ -99,10 +85,8 @@ handle_info(_Info, State) ->
start(Host) -> start(Host) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE), Proc = gen_mod:get_module_proc(Host, ?MODULE),
ChildSpec = { ChildSpec = {Proc, {?MODULE, start_link, [Host]},
Proc, {?MODULE, start_link, [Host]}, transient, 1000, worker, [?MODULE]},
transient, 1000, worker, [?MODULE]
},
supervisor:start_child(ejabberd_sup, ChildSpec). supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) -> stop(Host) ->
@ -115,56 +99,45 @@ start_link(Host) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE), Proc = gen_mod:get_module_proc(Host, ?MODULE),
gen_server:start_link({local, Proc}, ?MODULE, Host, []). gen_server:start_link({local, Proc}, ?MODULE, Host, []).
terminate(_Reason, _State) -> terminate(_Reason, _State) -> ok.
ok.
init(Host) -> init(Host) ->
State = parse_options(Host), State = parse_options(Host),
eldap_pool:start_link(State#state.eldap_id, eldap_pool:start_link(State#state.eldap_id,
State#state.servers, State#state.servers, State#state.backups,
State#state.backups, State#state.port, State#state.dn,
State#state.port, State#state.password, State#state.tls_options),
State#state.dn,
State#state.password,
State#state.tls_options),
eldap_pool:start_link(State#state.bind_eldap_id, eldap_pool:start_link(State#state.bind_eldap_id,
State#state.servers, State#state.servers, State#state.backups,
State#state.backups, State#state.port, State#state.dn,
State#state.port, State#state.password, State#state.tls_options),
State#state.dn,
State#state.password,
State#state.tls_options),
{ok, State}. {ok, State}.
plain_password_required() -> plain_password_required() -> true.
true.
store_type() -> store_type() -> external.
external.
check_password(User, Server, Password) -> check_password(User, Server, Password) ->
%% In LDAP spec: empty password means anonymous authentication. if Password == <<"">> -> false;
%% As ejabberd is providing other anonymous authentication mechanisms
%% we simply prevent the use of LDAP anonymous authentication.
if Password == "" ->
false;
true -> true ->
case catch check_password_ldap(User, Server, Password) of case catch check_password_ldap(User, Server, Password)
of
{'EXIT', _} -> false; {'EXIT', _} -> false;
Result -> Result Result -> Result
end end
end. end.
check_password(User, Server, Password, _Digest, _DigestGen) -> check_password(User, Server, Password, _Digest,
_DigestGen) ->
check_password(User, Server, Password). check_password(User, Server, Password).
set_password(User, Server, Password) -> set_password(User, Server, Password) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE), {ok, State} = eldap_utils:get_state(Server, ?MODULE),
case find_user_dn(User, State) of case find_user_dn(User, State) of
false -> false -> {error, user_not_found};
{error, user_not_found};
DN -> DN ->
eldap_pool:modify_passwd(State#state.eldap_id, DN, Password) eldap_pool:modify_passwd(State#state.eldap_id, DN,
Password)
end. end.
%% @spec (User, Server, Password) -> {error, not_allowed} %% @spec (User, Server, Password) -> {error, not_allowed}
@ -173,10 +146,10 @@ try_register(_User, _Server, _Password) ->
dirty_get_registered_users() -> dirty_get_registered_users() ->
Servers = ejabberd_config:get_vh_by_auth_method(ldap), Servers = ejabberd_config:get_vh_by_auth_method(ldap),
lists:flatmap( lists:flatmap(fun (Server) ->
fun(Server) ->
get_vh_registered_users(Server) get_vh_registered_users(Server)
end, Servers). end,
Servers).
get_vh_registered_users(Server) -> get_vh_registered_users(Server) ->
case catch get_vh_registered_users_ldap(Server) of case catch get_vh_registered_users_ldap(Server) of
@ -184,29 +157,29 @@ get_vh_registered_users(Server) ->
Result -> Result Result -> Result
end. end.
get_vh_registered_users(Server, _) ->
get_vh_registered_users(Server).
get_vh_registered_users_number(Server) -> get_vh_registered_users_number(Server) ->
length(get_vh_registered_users(Server)). length(get_vh_registered_users(Server)).
get_password(_User, _Server) -> get_vh_registered_users_number(Server, _) ->
false. get_vh_registered_users_number(Server).
get_password_s(_User, _Server) -> get_password(_User, _Server) -> false.
"".
get_password_s(_User, _Server) -> <<"">>.
%% @spec (User, Server) -> true | false | {error, Error} %% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) -> is_user_exists(User, Server) ->
case catch is_user_exists_ldap(User, Server) of case catch is_user_exists_ldap(User, Server) of
{'EXIT', Error} -> {'EXIT', Error} -> {error, Error};
{error, Error}; Result -> Result
Result ->
Result
end. end.
remove_user(_User, _Server) -> remove_user(_User, _Server) -> {error, not_allowed}.
{error, not_allowed}.
remove_user(_User, _Server, _Password) -> remove_user(_User, _Server, _Password) -> not_allowed.
not_allowed.
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% Internal functions %%% Internal functions
@ -214,10 +187,11 @@ remove_user(_User, _Server, _Password) ->
check_password_ldap(User, Server, Password) -> check_password_ldap(User, Server, Password) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE), {ok, State} = eldap_utils:get_state(Server, ?MODULE),
case find_user_dn(User, State) of case find_user_dn(User, State) of
false -> false -> false;
false;
DN -> DN ->
case eldap_pool:bind(State#state.bind_eldap_id, DN, Password) of case eldap_pool:bind(State#state.bind_eldap_id, DN,
Password)
of
ok -> true; ok -> true;
_ -> false _ -> false
end end
@ -236,33 +210,40 @@ get_vh_registered_users_ldap(Server) ->
{filter, EldapFilter}, {filter, EldapFilter},
{timeout, ?LDAP_SEARCH_TIMEOUT}, {timeout, ?LDAP_SEARCH_TIMEOUT},
{deref_aliases, State#state.deref_aliases}, {deref_aliases, State#state.deref_aliases},
{attributes, ResAttrs}]) of {attributes, ResAttrs}])
of
#eldap_search_result{entries = Entries} -> #eldap_search_result{entries = Entries} ->
lists:flatmap( lists:flatmap(fun (#eldap_entry{attributes = Attrs,
fun(#eldap_entry{attributes = Attrs,
object_name = DN}) -> object_name = DN}) ->
case is_valid_dn(DN, Attrs, State) of case is_valid_dn(DN, Attrs, State) of
false -> []; false -> [];
_ -> _ ->
case eldap_utils:find_ldap_attrs(UIDs, Attrs) of case
"" -> []; eldap_utils:find_ldap_attrs(UIDs,
Attrs)
of
<<"">> -> [];
{User, UIDFormat} -> {User, UIDFormat} ->
case eldap_utils:get_user_part(User, UIDFormat) of case
eldap_utils:get_user_part(User,
UIDFormat)
of
{ok, U} -> {ok, U} ->
case jlib:nodeprep(U) of case jlib:nodeprep(U) of
error -> []; error -> [];
LU -> [{LU, jlib:nameprep(Server)}] LU ->
[{LU,
jlib:nameprep(Server)}]
end; end;
_ -> [] _ -> []
end end
end end
end end
end, Entries); end,
_ -> Entries);
[] _ -> []
end; end;
_ -> _ -> []
[]
end. end.
is_user_exists_ldap(User, Server) -> is_user_exists_ldap(User, Server) ->
@ -274,70 +255,72 @@ is_user_exists_ldap(User, Server) ->
handle_call(get_state, _From, State) -> handle_call(get_state, _From, State) ->
{reply, {ok, State}, State}; {reply, {ok, State}, State};
handle_call(stop, _From, State) -> handle_call(stop, _From, State) ->
{stop, normal, ok, State}; {stop, normal, ok, State};
handle_call(_Request, _From, State) -> handle_call(_Request, _From, State) ->
{reply, bad_request, State}. {reply, bad_request, State}.
find_user_dn(User, State) -> find_user_dn(User, State) ->
ResAttrs = result_attrs(State), ResAttrs = result_attrs(State),
case eldap_filter:parse(State#state.ufilter, [{"%u", User}]) of case eldap_filter:parse(State#state.ufilter,
[{<<"%u">>, User}])
of
{ok, Filter} -> {ok, Filter} ->
case eldap_pool:search(State#state.eldap_id, case eldap_pool:search(State#state.eldap_id,
[{base, State#state.base}, [{base, State#state.base}, {filter, Filter},
{filter, Filter},
{deref_aliases, State#state.deref_aliases}, {deref_aliases, State#state.deref_aliases},
{attributes, ResAttrs}]) of {attributes, ResAttrs}])
#eldap_search_result{entries = [#eldap_entry{attributes = Attrs, of
object_name = DN} | _]} -> #eldap_search_result{entries =
[#eldap_entry{attributes = Attrs,
object_name = DN}
| _]} ->
dn_filter(DN, Attrs, State); dn_filter(DN, Attrs, State);
_ -> _ -> false
false
end; end;
_ -> _ -> false
false
end. end.
%% apply the dn filter and the local filter: %% apply the dn filter and the local filter:
dn_filter(DN, Attrs, State) -> dn_filter(DN, Attrs, State) ->
%% Check if user is denied access by attribute value (local check)
case check_local_filter(Attrs, State) of case check_local_filter(Attrs, State) of
false -> false; false -> false;
true -> is_valid_dn(DN, Attrs, State) true -> is_valid_dn(DN, Attrs, State)
end. end.
%% Check that the DN is valid, based on the dn filter %% Check that the DN is valid, based on the dn filter
is_valid_dn(DN, _, #state{dn_filter = undefined}) -> is_valid_dn(DN, _, #state{dn_filter = undefined}) -> DN;
DN;
is_valid_dn(DN, Attrs, State) -> is_valid_dn(DN, Attrs, State) ->
DNAttrs = State#state.dn_filter_attrs, DNAttrs = State#state.dn_filter_attrs,
UIDs = State#state.uids, UIDs = State#state.uids,
Values = [{"%s", eldap_utils:get_ldap_attr(Attr, Attrs), 1} || Attr <- DNAttrs], Values = [{<<"%s">>,
SubstValues = case eldap_utils:find_ldap_attrs(UIDs, Attrs) of eldap_utils:get_ldap_attr(Attr, Attrs), 1}
"" -> Values; || Attr <- DNAttrs],
SubstValues = case eldap_utils:find_ldap_attrs(UIDs,
Attrs)
of
<<"">> -> Values;
{S, UAF} -> {S, UAF} ->
case eldap_utils:get_user_part(S, UAF) of case eldap_utils:get_user_part(S, UAF) of
{ok, U} -> [{"%u", U} | Values]; {ok, U} -> [{<<"%u">>, U} | Values];
_ -> Values _ -> Values
end end
end ++ [{"%d", State#state.host}, {"%D", DN}], end
case eldap_filter:parse(State#state.dn_filter, SubstValues) of ++ [{<<"%d">>, State#state.host}, {<<"%D">>, DN}],
case eldap_filter:parse(State#state.dn_filter,
SubstValues)
of
{ok, EldapFilter} -> {ok, EldapFilter} ->
case eldap_pool:search(State#state.eldap_id, case eldap_pool:search(State#state.eldap_id,
[{base, State#state.base}, [{base, State#state.base},
{filter, EldapFilter}, {filter, EldapFilter},
{deref_aliases, State#state.deref_aliases}, {deref_aliases, State#state.deref_aliases},
{attributes, ["dn"]}]) of {attributes, [<<"dn">>]}])
#eldap_search_result{entries = [_|_]} -> of
DN; #eldap_search_result{entries = [_ | _]} -> DN;
_ -> _ -> false
false
end; end;
_ -> _ -> false
false
end. end.
%% The local filter is used to check an attribute in ejabberd %% The local filter is used to check an attribute in ejabberd
@ -346,9 +329,11 @@ is_valid_dn(DN, Attrs, State) ->
%% {equal, {"accountStatus",["active"]}} %% {equal, {"accountStatus",["active"]}}
%% {notequal, {"accountStatus",["disabled"]}} %% {notequal, {"accountStatus",["disabled"]}}
%% {ldap_local_filter, {notequal, {"accountStatus",["disabled"]}}} %% {ldap_local_filter, {notequal, {"accountStatus",["disabled"]}}}
check_local_filter(_Attrs, #state{lfilter = undefined}) -> check_local_filter(_Attrs,
#state{lfilter = undefined}) ->
true; true;
check_local_filter(Attrs, #state{lfilter = LocalFilter}) -> check_local_filter(Attrs,
#state{lfilter = LocalFilter}) ->
{Operation, FilterMatch} = LocalFilter, {Operation, FilterMatch} = LocalFilter,
local_filter(Operation, Attrs, FilterMatch). local_filter(Operation, Attrs, FilterMatch).
@ -362,93 +347,74 @@ local_filter(equal, Attrs, FilterMatch) ->
local_filter(notequal, Attrs, FilterMatch) -> local_filter(notequal, Attrs, FilterMatch) ->
not local_filter(equal, Attrs, FilterMatch). not local_filter(equal, Attrs, FilterMatch).
result_attrs(#state{uids = UIDs, dn_filter_attrs = DNFilterAttrs}) -> result_attrs(#state{uids = UIDs,
lists:foldl( dn_filter_attrs = DNFilterAttrs}) ->
fun({UID}, Acc) -> lists:foldl(fun ({UID}, Acc) -> [UID | Acc];
[UID | Acc]; ({UID, _}, Acc) -> [UID | Acc]
({UID, _}, Acc) -> end,
[UID | Acc] DNFilterAttrs, UIDs).
end, DNFilterAttrs, UIDs).
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% Auxiliary functions %%% Auxiliary functions
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
parse_options(Host) -> parse_options(Host) ->
Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, ?MODULE)), Cfg = eldap_utils:get_config(Host, []),
Bind_Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, bind_ejabberd_auth_ldap)), Eldap_ID = jlib:atom_to_binary(gen_mod:get_module_proc(Host, ?MODULE)),
LDAPServers = ejabberd_config:get_local_option({ldap_servers, Host}), Bind_Eldap_ID = jlib:atom_to_binary(
LDAPBackups = case ejabberd_config:get_local_option({ldap_backups, Host}) of gen_mod:get_module_proc(Host, bind_ejabberd_auth_ldap)),
undefined -> []; UIDsTemp = eldap_utils:get_opt(
Backups -> Backups {ldap_uids, Host}, [],
end, fun(Us) ->
LDAPEncrypt = ejabberd_config:get_local_option({ldap_encrypt, Host}), lists:map(
LDAPTLSVerify = ejabberd_config:get_local_option({ldap_tls_verify, Host}), fun({U, P}) ->
LDAPTLSCAFile = ejabberd_config:get_local_option({ldap_tls_cacertfile, Host}), {iolist_to_binary(U),
LDAPTLSDepth = ejabberd_config:get_local_option({ldap_tls_depth, Host}), iolist_to_binary(P)};
LDAPPort = case ejabberd_config:get_local_option({ldap_port, Host}) of ({U}) ->
undefined -> case LDAPEncrypt of {iolist_to_binary(U)}
tls -> ?LDAPS_PORT; end, Us)
starttls -> ?LDAP_PORT; end, [{<<"uid">>, <<"%u">>}]),
_ -> ?LDAP_PORT UIDs = eldap_utils:uids_domain_subst(Host, UIDsTemp),
end; SubFilter = eldap_utils:generate_subfilter(UIDs),
P -> P UserFilter = case eldap_utils:get_opt(
end, {ldap_filter, Host}, [],
RootDN = case ejabberd_config:get_local_option({ldap_rootdn, Host}) of fun check_filter/1, <<"">>) of
undefined -> ""; <<"">> ->
RDN -> RDN SubFilter;
end,
Password = case ejabberd_config:get_local_option({ldap_password, Host}) of
undefined -> "";
Pass -> Pass
end,
UIDs = case ejabberd_config:get_local_option({ldap_uids, Host}) of
undefined -> [{"uid", "%u"}];
UI -> eldap_utils:uids_domain_subst(Host, UI)
end,
SubFilter = lists:flatten(eldap_utils:generate_subfilter(UIDs)),
UserFilter = case ejabberd_config:get_local_option({ldap_filter, Host}) of
undefined -> SubFilter;
"" -> SubFilter;
F -> F ->
eldap_utils:check_filter(F), <<"(&", SubFilter/binary, F/binary, ")">>
"(&" ++ SubFilter ++ F ++ ")"
end, end,
SearchFilter = eldap_filter:do_sub(UserFilter, [{"%u", "*"}]), SearchFilter = eldap_filter:do_sub(UserFilter,
LDAPBase = ejabberd_config:get_local_option({ldap_base, Host}), [{<<"%u">>, <<"*">>}]),
{DNFilter, DNFilterAttrs} = {DNFilter, DNFilterAttrs} =
case ejabberd_config:get_local_option({ldap_dn_filter, Host}) of eldap_utils:get_opt({ldap_dn_filter, Host}, [],
fun({DNF, DNFA}) ->
NewDNFA = case DNFA of
undefined -> undefined ->
{undefined, []}; [];
{DNF, undefined} -> _ ->
{DNF, []}; [iolist_to_binary(A)
{DNF, DNFA} -> || A <- DNFA]
{DNF, DNFA}
end, end,
eldap_utils:check_filter(DNFilter), NewDNF = check_filter(DNF),
LocalFilter = ejabberd_config:get_local_option({ldap_local_filter, Host}), {NewDNF, NewDNFA}
DerefAliases = case ejabberd_config:get_local_option( end, {undefined, []}),
{ldap_deref_aliases, Host}) of LocalFilter = eldap_utils:get_opt(
undefined -> never; {ldap_local_filter, Host}, [], fun(V) -> V end),
Val -> Val #state{host = Host, eldap_id = Eldap_ID,
end,
#state{host = Host,
eldap_id = Eldap_ID,
bind_eldap_id = Bind_Eldap_ID, bind_eldap_id = Bind_Eldap_ID,
servers = LDAPServers, servers = Cfg#eldap_config.servers,
backups = LDAPBackups, backups = Cfg#eldap_config.backups,
port = LDAPPort, port = Cfg#eldap_config.port,
tls_options = [{encrypt, LDAPEncrypt}, tls_options = Cfg#eldap_config.tls_options,
{tls_verify, LDAPTLSVerify}, dn = Cfg#eldap_config.dn,
{tls_cacertfile, LDAPTLSCAFile}, password = Cfg#eldap_config.password,
{tls_depth, LDAPTLSDepth}], base = Cfg#eldap_config.base,
dn = RootDN, deref_aliases = Cfg#eldap_config.deref_aliases,
password = Password, uids = UIDs, ufilter = UserFilter,
base = LDAPBase, sfilter = SearchFilter, lfilter = LocalFilter,
uids = UIDs, dn_filter = DNFilter, dn_filter_attrs = DNFilterAttrs}.
ufilter = UserFilter,
sfilter = SearchFilter, check_filter(F) ->
lfilter = LocalFilter, NewF = iolist_to_binary(F),
deref_aliases = DerefAliases, {ok, _} = eldap_filter:parse(NewF),
dn_filter = DNFilter, NewF.
dn_filter_attrs = DNFilterAttrs
}.

View File

@ -25,56 +25,46 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(ejabberd_auth_odbc). -module(ejabberd_auth_odbc).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(ejabberd_auth).
%% External exports %% External exports
-export([start/1, -export([start/1, set_password/3, check_password/3,
set_password/3, check_password/5, try_register/3,
check_password/3, dirty_get_registered_users/0, get_vh_registered_users/1,
check_password/5,
try_register/3,
dirty_get_registered_users/0,
get_vh_registered_users/1,
get_vh_registered_users/2, get_vh_registered_users/2,
get_vh_registered_users_number/1, get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_vh_registered_users_number/2, get_password/2,
get_password/2, get_password_s/2, is_user_exists/2, remove_user/2,
get_password_s/2, remove_user/3, store_type/0,
is_user_exists/2, plain_password_required/0]).
remove_user/2,
remove_user/3,
store_type/0,
plain_password_required/0
]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% API %%% API
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
start(_Host) -> start(_Host) -> ok.
ok.
plain_password_required() -> plain_password_required() -> false.
false.
store_type() -> store_type() -> plain.
plain.
%% @spec (User, Server, Password) -> true | false | {error, Error} %% @spec (User, Server, Password) -> true | false | {error, Error}
check_password(User, Server, Password) -> check_password(User, Server, Password) ->
case jlib:nodeprep(User) of case jlib:nodeprep(User) of
error -> error -> false;
false;
LUser -> LUser ->
Username = ejabberd_odbc:escape(LUser), Username = ejabberd_odbc:escape(LUser),
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
try odbc_queries:get_password(LServer, Username) of try odbc_queries:get_password(LServer, Username) of
{selected, ["password"], [{Password}]} -> {selected, [<<"password">>], [[Password]]} ->
Password /= ""; %% Password is correct, and not empty Password /= <<"">>;
{selected, ["password"], [{_Password2}]} -> {selected, [<<"password">>], [[_Password2]]} ->
false; %% Password is not correct false; %% Password is not correct
{selected, ["password"], []} -> {selected, [<<"password">>], []} ->
false; %% Account does not exist false; %% Account does not exist
{error, _Error} -> {error, _Error} ->
false %% Typical error is that table doesn't exist false %% Typical error is that table doesn't exist
@ -85,28 +75,24 @@ check_password(User, Server, Password) ->
end. end.
%% @spec (User, Server, Password, Digest, DigestGen) -> true | false | {error, Error} %% @spec (User, Server, Password, Digest, DigestGen) -> true | false | {error, Error}
check_password(User, Server, Password, Digest, DigestGen) -> check_password(User, Server, Password, Digest,
DigestGen) ->
case jlib:nodeprep(User) of case jlib:nodeprep(User) of
error -> error -> false;
false;
LUser -> LUser ->
Username = ejabberd_odbc:escape(LUser), Username = ejabberd_odbc:escape(LUser),
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
try odbc_queries:get_password(LServer, Username) of try odbc_queries:get_password(LServer, Username) of
%% Account exists, check if password is valid %% Account exists, check if password is valid
{selected, ["password"], [{Passwd}]} -> {selected, [<<"password">>], [[Passwd]]} ->
DigRes = if DigRes = if Digest /= <<"">> ->
Digest /= "" ->
Digest == DigestGen(Passwd); Digest == DigestGen(Passwd);
true -> true -> false
false
end, end,
if DigRes -> if DigRes -> true;
true; true -> (Passwd == Password) and (Password /= <<"">>)
true ->
(Passwd == Password) and (Password /= "")
end; end;
{selected, ["password"], []} -> {selected, [<<"password">>], []} ->
false; %% Account does not exist false; %% Account does not exist
{error, _Error} -> {error, _Error} ->
false %% Typical error is that table doesn't exist false %% Typical error is that table doesn't exist
@ -120,127 +106,115 @@ check_password(User, Server, Password, Digest, DigestGen) ->
%% ok | {error, invalid_jid} %% ok | {error, invalid_jid}
set_password(User, Server, Password) -> set_password(User, Server, Password) ->
case jlib:nodeprep(User) of case jlib:nodeprep(User) of
error -> error -> {error, invalid_jid};
{error, invalid_jid};
LUser -> LUser ->
Username = ejabberd_odbc:escape(LUser), Username = ejabberd_odbc:escape(LUser),
Pass = ejabberd_odbc:escape(Password), Pass = ejabberd_odbc:escape(Password),
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
case catch odbc_queries:set_password_t(LServer, Username, Pass) of case catch odbc_queries:set_password_t(LServer,
Username, Pass)
of
{atomic, ok} -> ok; {atomic, ok} -> ok;
Other -> {error, Other} Other -> {error, Other}
end end
end. end.
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} %% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid}
try_register(User, Server, Password) -> try_register(User, Server, Password) ->
case jlib:nodeprep(User) of case jlib:nodeprep(User) of
error -> error -> {error, invalid_jid};
{error, invalid_jid};
LUser -> LUser ->
Username = ejabberd_odbc:escape(LUser), Username = ejabberd_odbc:escape(LUser),
Pass = ejabberd_odbc:escape(Password), Pass = ejabberd_odbc:escape(Password),
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
case catch odbc_queries:add_user(LServer, Username, Pass) of case catch odbc_queries:add_user(LServer, Username,
{updated, 1} -> Pass)
{atomic, ok}; of
_ -> {updated, 1} -> {atomic, ok};
{atomic, exists} _ -> {atomic, exists}
end end
end. end.
dirty_get_registered_users() -> dirty_get_registered_users() ->
Servers = ejabberd_config:get_vh_by_auth_method(odbc), Servers = ejabberd_config:get_vh_by_auth_method(odbc),
lists:flatmap( lists:flatmap(fun (Server) ->
fun(Server) ->
get_vh_registered_users(Server) get_vh_registered_users(Server)
end, Servers). end,
Servers).
get_vh_registered_users(Server) -> get_vh_registered_users(Server) ->
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
case catch odbc_queries:list_users(LServer) of case catch odbc_queries:list_users(LServer) of
{selected, ["username"], Res} -> {selected, [<<"username">>], Res} ->
[{U, LServer} || {U} <- Res]; [{U, LServer} || [U] <- Res];
_ -> _ -> []
[]
end. end.
get_vh_registered_users(Server, Opts) -> get_vh_registered_users(Server, Opts) ->
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
case catch odbc_queries:list_users(LServer, Opts) of case catch odbc_queries:list_users(LServer, Opts) of
{selected, ["username"], Res} -> {selected, [<<"username">>], Res} ->
[{U, LServer} || {U} <- Res]; [{U, LServer} || [U] <- Res];
_ -> _ -> []
[]
end. end.
get_vh_registered_users_number(Server) -> get_vh_registered_users_number(Server) ->
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
case catch odbc_queries:users_number(LServer) of case catch odbc_queries:users_number(LServer) of
{selected, [_], [{Res}]} -> {selected, [_], [[Res]]} ->
list_to_integer(Res); jlib:binary_to_integer(Res);
_ -> _ -> 0
0
end. end.
get_vh_registered_users_number(Server, Opts) -> get_vh_registered_users_number(Server, Opts) ->
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
case catch odbc_queries:users_number(LServer, Opts) of case catch odbc_queries:users_number(LServer, Opts) of
{selected, [_], [{Res}]} -> {selected, [_], [[Res]]} ->
list_to_integer(Res); jlib:binary_to_integer(Res);
_Other -> _Other -> 0
0
end. end.
get_password(User, Server) -> get_password(User, Server) ->
case jlib:nodeprep(User) of case jlib:nodeprep(User) of
error -> error -> false;
false;
LUser -> LUser ->
Username = ejabberd_odbc:escape(LUser), Username = ejabberd_odbc:escape(LUser),
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
case catch odbc_queries:get_password(LServer, Username) of case catch odbc_queries:get_password(LServer, Username)
{selected, ["password"], [{Password}]} -> of
Password; {selected, [<<"password">>], [[Password]]} -> Password;
_ -> _ -> false
false
end end
end. end.
get_password_s(User, Server) -> get_password_s(User, Server) ->
case jlib:nodeprep(User) of case jlib:nodeprep(User) of
error -> error -> <<"">>;
"";
LUser -> LUser ->
Username = ejabberd_odbc:escape(LUser), Username = ejabberd_odbc:escape(LUser),
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
case catch odbc_queries:get_password(LServer, Username) of case catch odbc_queries:get_password(LServer, Username)
{selected, ["password"], [{Password}]} -> of
Password; {selected, [<<"password">>], [[Password]]} -> Password;
_ -> _ -> <<"">>
""
end end
end. end.
%% @spec (User, Server) -> true | false | {error, Error} %% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) -> is_user_exists(User, Server) ->
case jlib:nodeprep(User) of case jlib:nodeprep(User) of
error -> error -> false;
false;
LUser -> LUser ->
Username = ejabberd_odbc:escape(LUser), Username = ejabberd_odbc:escape(LUser),
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
try odbc_queries:get_password(LServer, Username) of try odbc_queries:get_password(LServer, Username) of
{selected, ["password"], [{_Password}]} -> {selected, [<<"password">>], [[_Password]]} ->
true; %% Account exists true; %% Account exists
{selected, ["password"], []} -> {selected, [<<"password">>], []} ->
false; %% Account does not exist false; %% Account does not exist
{error, Error} -> {error, Error} -> {error, Error}
{error, Error} %% Typical error is that table doesn't exist
catch catch
_:B -> _:B -> {error, B}
{error, B} %% Typical error is database not accessible
end end
end. end.
@ -249,8 +223,7 @@ is_user_exists(User, Server) ->
%% Note: it may return ok even if there was some problem removing the user. %% Note: it may return ok even if there was some problem removing the user.
remove_user(User, Server) -> remove_user(User, Server) ->
case jlib:nodeprep(User) of case jlib:nodeprep(User) of
error -> error -> error;
error;
LUser -> LUser ->
Username = ejabberd_odbc:escape(LUser), Username = ejabberd_odbc:escape(LUser),
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
@ -262,24 +235,22 @@ remove_user(User, Server) ->
%% @doc Remove user if the provided password is correct. %% @doc Remove user if the provided password is correct.
remove_user(User, Server, Password) -> remove_user(User, Server, Password) ->
case jlib:nodeprep(User) of case jlib:nodeprep(User) of
error -> error -> error;
error;
LUser -> LUser ->
Username = ejabberd_odbc:escape(LUser), Username = ejabberd_odbc:escape(LUser),
Pass = ejabberd_odbc:escape(Password), Pass = ejabberd_odbc:escape(Password),
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
F = fun () -> F = fun () ->
Result = odbc_queries:del_user_return_password( Result = odbc_queries:del_user_return_password(LServer,
LServer, Username, Pass), Username,
Pass),
case Result of case Result of
{selected, ["password"], [{Password}]} -> {selected, [<<"password">>], [[Password]]} -> ok;
ok; {selected, [<<"password">>], []} -> not_exists;
{selected, ["password"], []} -> _ -> not_allowed
not_exists;
_ ->
not_allowed
end end
end, end,
{atomic, Result} = odbc_queries:sql_transaction(LServer, F), {atomic, Result} = odbc_queries:sql_transaction(LServer,
F),
Result Result
end. end.

View File

@ -24,28 +24,24 @@
%%% %%%
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(ejabberd_auth_pam). -module(ejabberd_auth_pam).
-author('xram@jabber.ru'). -author('xram@jabber.ru').
%% External exports -behaviour(ejabberd_auth).
-export([start/1,
set_password/3,
check_password/3,
check_password/5,
try_register/3,
dirty_get_registered_users/0,
get_vh_registered_users/1,
get_password/2,
get_password_s/2,
is_user_exists/2,
remove_user/2,
remove_user/3,
store_type/0,
plain_password_required/0
]).
%% External exports
%%==================================================================== %%====================================================================
%% API %% API
%%==================================================================== %%====================================================================
-export([start/1, set_password/3, check_password/3,
check_password/5, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2, get_vh_registered_users_number/1,
get_vh_registered_users_number/2,
get_password/2, get_password_s/2, is_user_exists/2,
remove_user/2, remove_user/3, store_type/0,
plain_password_required/0]).
start(_Host) -> start(_Host) ->
case epam:start() of case epam:start() of
{ok, _} -> ok; {ok, _} -> ok;
@ -56,16 +52,19 @@ start(_Host) ->
set_password(_User, _Server, _Password) -> set_password(_User, _Server, _Password) ->
{error, not_allowed}. {error, not_allowed}.
check_password(User, Server, Password, _Digest, _DigestGen) -> check_password(User, Server, Password, _Digest,
_DigestGen) ->
check_password(User, Server, Password). check_password(User, Server, Password).
check_password(User, Host, Password) -> check_password(User, Host, Password) ->
Service = get_pam_service(Host), Service = get_pam_service(Host),
UserInfo = case get_pam_userinfotype(Host) of UserInfo = case get_pam_userinfotype(Host) of
username -> User; username -> User;
jid -> User++"@"++Host jid -> <<User/binary, "@", Host/binary>>
end, end,
case catch epam:authenticate(Service, UserInfo, Password) of case catch epam:authenticate(Service, UserInfo,
Password)
of
true -> true; true -> true;
_ -> false _ -> false
end. end.
@ -73,17 +72,19 @@ check_password(User, Host, Password) ->
try_register(_User, _Server, _Password) -> try_register(_User, _Server, _Password) ->
{error, not_allowed}. {error, not_allowed}.
dirty_get_registered_users() -> dirty_get_registered_users() -> [].
[].
get_vh_registered_users(_Host) -> get_vh_registered_users(_Host) -> [].
[].
get_password(_User, _Server) -> get_vh_registered_users(_Host, _) -> [].
false.
get_password_s(_User, _Server) -> get_vh_registered_users_number(_Host) -> 0.
"".
get_vh_registered_users_number(_Host, _) -> 0.
get_password(_User, _Server) -> false.
get_password_s(_User, _Server) -> <<"">>.
%% @spec (User, Server) -> true | false | {error, Error} %% @spec (User, Server) -> true | false | {error, Error}
%% TODO: Improve this function to return an error instead of 'false' when connection to PAM failed %% TODO: Improve this function to return an error instead of 'false' when connection to PAM failed
@ -91,35 +92,34 @@ is_user_exists(User, Host) ->
Service = get_pam_service(Host), Service = get_pam_service(Host),
UserInfo = case get_pam_userinfotype(Host) of UserInfo = case get_pam_userinfotype(Host) of
username -> User; username -> User;
jid -> User++"@"++Host jid -> <<User/binary, "@", Host/binary>>
end, end,
case catch epam:acct_mgmt(Service, UserInfo) of case catch epam:acct_mgmt(Service, UserInfo) of
true -> true; true -> true;
_ -> false _ -> false
end. end.
remove_user(_User, _Server) -> remove_user(_User, _Server) -> {error, not_allowed}.
{error, not_allowed}.
remove_user(_User, _Server, _Password) -> remove_user(_User, _Server, _Password) -> not_allowed.
not_allowed.
plain_password_required() -> plain_password_required() -> true.
true.
store_type() -> store_type() -> external.
external.
%%==================================================================== %%====================================================================
%% Internal functions %% Internal functions
%%==================================================================== %%====================================================================
get_pam_service(Host) -> get_pam_service(Host) ->
case ejabberd_config:get_local_option({pam_service, Host}) of ejabberd_config:get_local_option(
undefined -> "ejabberd"; {pam_service, Host},
Service -> Service fun iolist_to_binary/1,
end. <<"ejabberd">>).
get_pam_userinfotype(Host) -> get_pam_userinfotype(Host) ->
case ejabberd_config:get_local_option({pam_userinfotype, Host}) of ejabberd_config:get_local_option(
undefined -> username; {pam_userinfotype, Host},
Type -> Type fun(username) -> username;
end. (jid) -> jid
end,
username).

File diff suppressed because it is too large Load Diff

View File

@ -26,6 +26,7 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(ejabberd_c2s_config). -module(ejabberd_c2s_config).
-author('mremond@process-one.net'). -author('mremond@process-one.net').
-export([get_c2s_limits/0]). -export([get_c2s_limits/0]).
@ -33,28 +34,33 @@
%% Get first c2s configuration limitations to apply it to other c2s %% Get first c2s configuration limitations to apply it to other c2s
%% connectors. %% connectors.
get_c2s_limits() -> get_c2s_limits() ->
case ejabberd_config:get_local_option(listen) of case ejabberd_config:get_local_option(listen, fun(V) -> V end) of
undefined -> undefined -> [];
[];
C2SFirstListen -> C2SFirstListen ->
case lists:keysearch(ejabberd_c2s, 2, C2SFirstListen) of case lists:keysearch(ejabberd_c2s, 2, C2SFirstListen) of
false -> false -> [];
[];
{value, {_Port, ejabberd_c2s, Opts}} -> {value, {_Port, ejabberd_c2s, Opts}} ->
select_opts_values(Opts) select_opts_values(Opts)
end end
end. end.
%% Only get access, shaper and max_stanza_size values %% Only get access, shaper and max_stanza_size values
select_opts_values(Opts) -> select_opts_values(Opts) ->
select_opts_values(Opts, []). select_opts_values(Opts, []).
select_opts_values([], SelectedValues) -> select_opts_values([], SelectedValues) ->
SelectedValues; SelectedValues;
select_opts_values([{access,Value}|Opts], SelectedValues) -> select_opts_values([{access, Value} | Opts],
select_opts_values(Opts, [{access, Value}|SelectedValues]); SelectedValues) ->
select_opts_values([{shaper,Value}|Opts], SelectedValues) -> select_opts_values(Opts,
select_opts_values(Opts, [{shaper, Value}|SelectedValues]); [{access, Value} | SelectedValues]);
select_opts_values([{max_stanza_size,Value}|Opts], SelectedValues) -> select_opts_values([{shaper, Value} | Opts],
select_opts_values(Opts, [{max_stanza_size, Value}|SelectedValues]); SelectedValues) ->
select_opts_values(Opts,
[{shaper, Value} | SelectedValues]);
select_opts_values([{max_stanza_size, Value} | Opts],
SelectedValues) ->
select_opts_values(Opts,
[{max_stanza_size, Value} | SelectedValues]);
select_opts_values([_Opt | Opts], SelectedValues) -> select_opts_values([_Opt | Opts], SelectedValues) ->
select_opts_values(Opts, SelectedValues). select_opts_values(Opts, SelectedValues).

View File

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

View File

@ -31,8 +31,6 @@
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("ejabberd_config.hrl"). -include("ejabberd_config.hrl").
-compile([export_all]).
%% TODO: %% TODO:
%% We want to implement library checking at launch time to issue %% We want to implement library checking at launch time to issue
%% human readable user messages. %% human readable user messages.
@ -87,7 +85,7 @@ get_db_used() ->
fun([Domain, DB], Acc) -> fun([Domain, DB], Acc) ->
case check_odbc_option( case check_odbc_option(
ejabberd_config:get_local_option( ejabberd_config:get_local_option(
{auth_method, Domain})) of {auth_method, Domain}, fun(V) -> V end)) of
true -> [get_db_type(DB)|Acc]; true -> [get_db_type(DB)|Acc];
_ -> Acc _ -> Acc
end end

View File

@ -228,7 +228,8 @@ init() ->
ets:new(ejabberd_commands, [named_table, set, public, ets:new(ejabberd_commands, [named_table, set, public,
{keypos, #ejabberd_commands.name}]). {keypos, #ejabberd_commands.name}]).
%% @spec ([ejabberd_commands()]) -> ok -spec register_commands([ejabberd_commands()]) -> ok.
%% @doc Register ejabberd commands. %% @doc Register ejabberd commands.
%% If a command is already registered, a warning is printed and the old command is preserved. %% If a command is already registered, a warning is printed and the old command is preserved.
register_commands(Commands) -> register_commands(Commands) ->
@ -243,7 +244,8 @@ register_commands(Commands) ->
end, end,
Commands). Commands).
%% @spec ([ejabberd_commands()]) -> ok -spec unregister_commands([ejabberd_commands()]) -> ok.
%% @doc Unregister ejabberd commands. %% @doc Unregister ejabberd commands.
unregister_commands(Commands) -> unregister_commands(Commands) ->
lists:foreach( lists:foreach(
@ -252,7 +254,8 @@ unregister_commands(Commands) ->
end, end,
Commands). Commands).
%% @spec () -> [{Name::atom(), Args::[aterm()], Desc::string()}] -spec list_commands() -> [{atom(), [aterm()], string()}].
%% @doc Get a list of all the available commands, arguments and description. %% @doc Get a list of all the available commands, arguments and description.
list_commands() -> list_commands() ->
Commands = ets:match(ejabberd_commands, Commands = ets:match(ejabberd_commands,
@ -262,7 +265,8 @@ list_commands() ->
_ = '_'}), _ = '_'}),
[{A, B, C} || [A, B, C] <- Commands]. [{A, B, C} || [A, B, C] <- Commands].
%% @spec (Name::atom()) -> {Args::[aterm()], Result::rterm()} | {error, command_unknown} -spec get_command_format(atom()) -> {[aterm()], rterm()} | {error, command_unknown}.
%% @doc Get the format of arguments and result of a command. %% @doc Get the format of arguments and result of a command.
get_command_format(Name) -> get_command_format(Name) ->
Matched = ets:match(ejabberd_commands, Matched = ets:match(ejabberd_commands,
@ -277,7 +281,8 @@ get_command_format(Name) ->
{Args, Result} {Args, Result}
end. end.
%% @spec (Name::atom()) -> ejabberd_commands() | command_not_found -spec get_command_definition(atom()) -> ejabberd_commands() | command_not_found.
%% @doc Get the definition record of a command. %% @doc Get the definition record of a command.
get_command_definition(Name) -> get_command_definition(Name) ->
case ets:lookup(ejabberd_commands, Name) of case ets:lookup(ejabberd_commands, Name) of
@ -314,6 +319,8 @@ execute_command2(Command, Arguments) ->
?DEBUG("Executing command ~p:~p with Args=~p", [Module, Function, Arguments]), ?DEBUG("Executing command ~p:~p with Args=~p", [Module, Function, Arguments]),
apply(Module, Function, Arguments). apply(Module, Function, Arguments).
-spec get_tags_commands() -> [{string(), [string()]}].
%% @spec () -> [{Tag::string(), [CommandName::string()]}] %% @spec () -> [{Tag::string(), [CommandName::string()]}]
%% @doc Get all the tags and associated commands. %% @doc Get all the tags and associated commands.
get_tags_commands() -> get_tags_commands() ->
@ -377,6 +384,9 @@ check_access_commands(AccessCommands, Auth, Method, Command, Arguments) ->
L when is_list(L) -> ok L when is_list(L) -> ok
end. end.
-spec check_auth(noauth) -> noauth_provided;
({binary(), binary(), binary()}) -> {ok, binary(), binary()}.
check_auth(noauth) -> check_auth(noauth) ->
no_auth_provided; no_auth_provided;
check_auth({User, Server, Password}) -> check_auth({User, Server, Password}) ->
@ -391,7 +401,7 @@ check_access(all, _) ->
check_access(Access, Auth) -> check_access(Access, Auth) ->
{ok, User, Server} = check_auth(Auth), {ok, User, Server} = check_auth(Auth),
%% Check this user has access permission %% Check this user has access permission
case acl:match_rule(Server, Access, jlib:make_jid(User, Server, "")) of case acl:match_rule(Server, Access, jlib:make_jid(User, Server, <<"">>)) of
allow -> true; allow -> true;
deny -> false deny -> false
end. end.

View File

@ -19,10 +19,32 @@
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-record(ejabberd_commands, {name, tags = [], -type aterm() :: {atom(), atype()}.
desc = "", longdesc = "", -type atype() :: integer | string | binary |
module, function, {tuple, [aterm()]} | {list, aterm()}.
args = [], result = rescode}). -type rterm() :: {atom(), rtype()}.
-type rtype() :: integer | string | atom |
{tuple, [rterm()]} | {list, rterm()} |
rescode | restuple.
-record(ejabberd_commands,
{name :: atom(),
tags = [] :: [atom()] | '_' | '$2',
desc = "" :: string() | '_' | '$3',
longdesc = "" :: string() | '_',
module :: atom(),
function :: atom(),
args = [] :: [aterm()] | '_' | '$1' | '$2',
result = {res, rescode} :: rterm() | '_' | '$2'}).
-type ejabberd_commands() :: #ejabberd_commands{name :: atom(),
tags :: [atom()],
desc :: string(),
longdesc :: string(),
module :: atom(),
function :: atom(),
args :: [aterm()],
result :: rterm()}.
%% @type ejabberd_commands() = #ejabberd_commands{ %% @type ejabberd_commands() = #ejabberd_commands{
%% name = atom(), %% name = atom(),
@ -50,3 +72,4 @@
%% @type rterm() = {Name::atom(), Type::rtype()}. %% @type rterm() = {Name::atom(), Type::rtype()}.
%% A result term is a tuple with the term name and the term type. %% A result term is a tuple with the term name and the term type.

View File

@ -29,9 +29,13 @@
-export([start/0, load_file/1, -export([start/0, load_file/1,
add_global_option/2, add_local_option/2, add_global_option/2, add_local_option/2,
get_global_option/1, get_local_option/1]). get_global_option/2, get_local_option/2,
get_global_option/3, get_local_option/3]).
-export([get_vh_by_auth_method/1]). -export([get_vh_by_auth_method/1]).
-export([is_file_readable/1]). -export([is_file_readable/1]).
-export([get_version/0, get_myhosts/0, get_mylang/0]).
-export([prepare_opt_val/4]).
-export([convert_table_to_binary/5]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("ejabberd_config.hrl"). -include("ejabberd_config.hrl").
@ -96,11 +100,15 @@ load_file(File) ->
%% in which the options 'include_config_file' were parsed %% in which the options 'include_config_file' were parsed
%% and the terms in those files were included. %% and the terms in those files were included.
%% @spec(string()) -> [term()] %% @spec(string()) -> [term()]
%% @spec(iolist()) -> [term()]
get_plain_terms_file(File) when is_binary(File) ->
get_plain_terms_file(binary_to_list(File));
get_plain_terms_file(File1) -> get_plain_terms_file(File1) ->
File = get_absolute_path(File1), File = get_absolute_path(File1),
case file:consult(File) of case file:consult(File) of
{ok, Terms} -> {ok, Terms} ->
include_config_files(Terms); BinTerms = strings_to_binary(Terms),
include_config_files(BinTerms);
{error, {LineNumber, erl_parse, _ParseMessage} = Reason} -> {error, {LineNumber, erl_parse, _ParseMessage} = Reason} ->
ExitText = describe_config_problem(File, Reason, LineNumber), ExitText = describe_config_problem(File, Reason, LineNumber),
?ERROR_MSG(ExitText, []), ?ERROR_MSG(ExitText, []),
@ -159,7 +167,7 @@ normalize_hosts(Hosts) ->
normalize_hosts([], PrepHosts) -> normalize_hosts([], PrepHosts) ->
lists:reverse(PrepHosts); lists:reverse(PrepHosts);
normalize_hosts([Host|Hosts], PrepHosts) -> normalize_hosts([Host|Hosts], PrepHosts) ->
case jlib:nodeprep(Host) of case jlib:nodeprep(iolist_to_binary(Host)) of
error -> error ->
?ERROR_MSG("Can't load config file: " ?ERROR_MSG("Can't load config file: "
"invalid host name [~p]", [Host]), "invalid host name [~p]", [Host]),
@ -564,7 +572,6 @@ set_opts(State) ->
exit("Error reading Mnesia database") exit("Error reading Mnesia database")
end. end.
add_global_option(Opt, Val) -> add_global_option(Opt, Val) ->
mnesia:transaction(fun() -> mnesia:transaction(fun() ->
mnesia:write(#config{key = Opt, mnesia:write(#config{key = Opt,
@ -577,23 +584,63 @@ add_local_option(Opt, Val) ->
value = Val}) value = Val})
end). end).
-spec prepare_opt_val(any(), any(), check_fun(), any()) -> any().
get_global_option(Opt) -> prepare_opt_val(Opt, Val, F, Default) ->
Res = case F of
{Mod, Fun} ->
catch Mod:Fun(Val);
_ ->
catch F(Val)
end,
case Res of
{'EXIT', _} ->
?INFO_MSG("Configuration problem:~n"
"** Option: ~s~n"
"** Invalid value: ~s~n"
"** Using as fallback: ~s",
[format_term(Opt),
format_term(Val),
format_term(Default)]),
Default;
_ ->
Res
end.
-type check_fun() :: fun((any()) -> any()) | {module(), atom()}.
-spec get_global_option(any(), check_fun()) -> any().
get_global_option(Opt, F) ->
get_global_option(Opt, F, undefined).
-spec get_global_option(any(), check_fun(), any()) -> any().
get_global_option(Opt, F, Default) ->
case ets:lookup(config, Opt) of case ets:lookup(config, Opt) of
[#config{value = Val}] -> [#config{value = Val}] ->
Val; prepare_opt_val(Opt, Val, F, Default);
_ -> _ ->
undefined Default
end. end.
get_local_option(Opt) -> -spec get_local_option(any(), check_fun()) -> any().
get_local_option(Opt, F) ->
get_local_option(Opt, F, undefined).
-spec get_local_option(any(), check_fun(), any()) -> any().
get_local_option(Opt, F, Default) ->
case ets:lookup(local_config, Opt) of case ets:lookup(local_config, Opt) of
[#local_config{value = Val}] -> [#local_config{value = Val}] ->
Val; prepare_opt_val(Opt, Val, F, Default);
_ -> _ ->
undefined Default
end. end.
-spec get_vh_by_auth_method(atom()) -> [binary()].
%% Return the list of hosts handled by a given module %% Return the list of hosts handled by a given module
get_vh_by_auth_method(AuthMethod) -> get_vh_by_auth_method(AuthMethod) ->
mnesia:dirty_select(local_config, mnesia:dirty_select(local_config,
@ -613,8 +660,25 @@ is_file_readable(Path) ->
false false
end. end.
get_version() ->
list_to_binary(element(2, application:get_key(ejabberd, vsn))).
-spec get_myhosts() -> [binary()].
get_myhosts() ->
ejabberd_config:get_global_option(hosts, fun(V) -> V end).
-spec get_mylang() -> binary().
get_mylang() ->
ejabberd_config:get_global_option(
language,
fun iolist_to_binary/1,
<<"en">>).
replace_module(mod_announce_odbc) -> {mod_announce, odbc}; replace_module(mod_announce_odbc) -> {mod_announce, odbc};
replace_module(mod_blocking_odbc) -> {mod_blocking, odbc}; replace_module(mod_blocking_odbc) -> {mod_blocking, odbc};
replace_module(mod_caps_odbc) -> {mod_caps, odbc};
replace_module(mod_irc_odbc) -> {mod_irc, odbc}; replace_module(mod_irc_odbc) -> {mod_irc, odbc};
replace_module(mod_last_odbc) -> {mod_last, odbc}; replace_module(mod_last_odbc) -> {mod_last, odbc};
replace_module(mod_muc_odbc) -> {mod_muc, odbc}; replace_module(mod_muc_odbc) -> {mod_muc, odbc};
@ -632,10 +696,161 @@ replace_modules(Modules) ->
fun({Module, Opts}) -> fun({Module, Opts}) ->
case replace_module(Module) of case replace_module(Module) of
{NewModule, DBType} -> {NewModule, DBType} ->
emit_deprecation_warning(Module, NewModule, DBType),
NewOpts = [{db_type, DBType} | NewOpts = [{db_type, DBType} |
lists:keydelete(db_type, 1, Opts)], lists:keydelete(db_type, 1, Opts)],
{NewModule, NewOpts}; {NewModule, NewOpts};
NewModule -> NewModule ->
if Module /= NewModule ->
emit_deprecation_warning(Module, NewModule);
true ->
ok
end,
{NewModule, Opts} {NewModule, Opts}
end end
end, Modules). end, Modules).
strings_to_binary([]) ->
[];
strings_to_binary(L) when is_list(L) ->
case is_string(L) of
true ->
list_to_binary(L);
false ->
strings_to_binary1(L)
end;
strings_to_binary(T) when is_tuple(T) ->
list_to_tuple(strings_to_binary(tuple_to_list(T)));
strings_to_binary(X) ->
X.
strings_to_binary1([El|L]) ->
[strings_to_binary(El)|strings_to_binary1(L)];
strings_to_binary1([]) ->
[];
strings_to_binary1(T) ->
T.
is_string([C|T]) when (C >= 0) and (C =< 255) ->
is_string(T);
is_string([]) ->
true;
is_string(_) ->
false.
binary_to_strings(B) when is_binary(B) ->
binary_to_list(B);
binary_to_strings([H|T]) ->
[binary_to_strings(H)|binary_to_strings(T)];
binary_to_strings(T) when is_tuple(T) ->
list_to_tuple(binary_to_strings(tuple_to_list(T)));
binary_to_strings(T) ->
T.
format_term(Bin) when is_binary(Bin) ->
io_lib:format("\"~s\"", [Bin]);
format_term(S) when is_list(S), S /= [] ->
case lists:all(fun(C) -> (C>=0) and (C=<255) end, S) of
true ->
io_lib:format("\"~s\"", [S]);
false ->
io_lib:format("~p", [binary_to_strings(S)])
end;
format_term(T) ->
io_lib:format("~p", [binary_to_strings(T)]).
-spec convert_table_to_binary(atom(), [atom()], atom(),
fun(), fun()) -> ok.
convert_table_to_binary(Tab, Fields, Type, DetectFun, ConvertFun) ->
case is_table_still_list(Tab, DetectFun) of
true ->
?INFO_MSG("Converting '~s' table from strings to binaries.", [Tab]),
TmpTab = list_to_atom(atom_to_list(Tab) ++ "_tmp_table"),
catch mnesia:delete_table(TmpTab),
case mnesia:create_table(TmpTab,
[{disc_only_copies, [node()]},
{type, Type},
{local_content, true},
{record_name, Tab},
{attributes, Fields}]) of
{atomic, ok} ->
mnesia:transform_table(Tab, ignore, Fields),
case mnesia:transaction(
fun() ->
mnesia:write_lock_table(TmpTab),
mnesia:foldl(
fun(R, _) ->
NewR = ConvertFun(R),
mnesia:dirty_write(TmpTab, NewR)
end, ok, Tab)
end) of
{atomic, ok} ->
mnesia:clear_table(Tab),
case mnesia:transaction(
fun() ->
mnesia:write_lock_table(Tab),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, TmpTab)
end) of
{atomic, ok} ->
mnesia:delete_table(TmpTab);
Err ->
report_and_stop(Tab, Err)
end;
Err ->
report_and_stop(Tab, Err)
end;
Err ->
report_and_stop(Tab, Err)
end;
false ->
ok
end.
is_table_still_list(Tab, DetectFun) ->
is_table_still_list(Tab, DetectFun, mnesia:dirty_first(Tab)).
is_table_still_list(_Tab, _DetectFun, '$end_of_table') ->
false;
is_table_still_list(Tab, DetectFun, Key) ->
Rs = mnesia:dirty_read(Tab, Key),
Res = lists:foldl(fun(_, true) ->
true;
(_, false) ->
false;
(R, _) ->
case DetectFun(R) of
'$next' ->
'$next';
El ->
is_list(El)
end
end, '$next', Rs),
case Res of
true ->
true;
false ->
false;
'$next' ->
is_table_still_list(Tab, DetectFun, mnesia:dirty_next(Tab, Key))
end.
report_and_stop(Tab, Err) ->
ErrTxt = lists:flatten(
io_lib:format(
"Failed to convert '~s' table to binary: ~p",
[Tab, Err])),
?CRITICAL_MSG(ErrTxt, []),
timer:sleep(1000),
halt(string:substr(ErrTxt, 1, 199)).
emit_deprecation_warning(Module, NewModule, DBType) ->
?WARNING_MSG("Module ~s is deprecated, use {~s, [{db_type, ~s}, ...]}"
" instead", [Module, NewModule, DBType]).
emit_deprecation_warning(Module, NewModule) ->
?WARNING_MSG("Module ~s is deprecated, use ~s instead",
[Module, NewModule]).

View File

@ -19,10 +19,16 @@
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-record(config, {key, value}). -record(config, {key :: any(), value :: any()}).
-record(local_config, {key, value}).
-record(state, {opts = [], -record(local_config, {key :: any(), value :: any()}).
hosts = [],
override_local = false, -type config() :: #config{}.
override_global = false, -type local_config() :: #local_config{}.
override_acls = false}).
-record(state,
{opts = [] :: [acl:acl() | config() | local_config()],
hosts = [] :: [binary()],
override_local = false :: boolean(),
override_global = false :: boolean(),
override_acls = false :: boolean()}).

View File

@ -72,10 +72,10 @@ start() ->
_ -> _ ->
case net_kernel:longnames() of case net_kernel:longnames() of
true -> true ->
SNode ++ "@" ++ inet_db:gethostname() ++ lists:flatten([SNode, "@", inet_db:gethostname(),
"." ++ inet_db:res_option(domain); ".", inet_db:res_option(domain)]);
false -> false ->
SNode ++ "@" ++ inet_db:gethostname(); lists:flatten([SNode, "@", inet_db:gethostname()]);
_ -> _ ->
SNode SNode
end end
@ -124,6 +124,8 @@ unregister_commands(CmdDescs, Module, Function) ->
%% Process %% Process
%%----------------------------- %%-----------------------------
-spec process([string()]) -> non_neg_integer().
%% The commands status, stop and restart are defined here to ensure %% The commands status, stop and restart are defined here to ensure
%% they are usable even if ejabberd is completely stopped. %% they are usable even if ejabberd is completely stopped.
process(["status"]) -> process(["status"]) ->
@ -159,7 +161,7 @@ process(["mnesia", "info"]) ->
mnesia:info(), mnesia:info(),
?STATUS_SUCCESS; ?STATUS_SUCCESS;
process(["mnesia", Arg]) when is_list(Arg) -> process(["mnesia", Arg]) ->
case catch mnesia:system_info(list_to_atom(Arg)) of case catch mnesia:system_info(list_to_atom(Arg)) of
{'EXIT', Error} -> ?PRINT("Error: ~p~n", [Error]); {'EXIT', Error} -> ?PRINT("Error: ~p~n", [Error]);
Return -> ?PRINT("~p~n", [Return]) Return -> ?PRINT("~p~n", [Return])
@ -190,8 +192,9 @@ process(["help" | Mode]) ->
print_usage_help(MaxC, ShCode), print_usage_help(MaxC, ShCode),
?STATUS_SUCCESS; ?STATUS_SUCCESS;
[CmdString | _] -> [CmdString | _] ->
CmdStringU = ejabberd_regexp:greplace(CmdString, "-", "_"), CmdStringU = ejabberd_regexp:greplace(
print_usage_commands(CmdStringU, MaxC, ShCode), list_to_binary(CmdString), <<"-">>, <<"_">>),
print_usage_commands(binary_to_list(CmdStringU), MaxC, ShCode),
?STATUS_SUCCESS ?STATUS_SUCCESS
end; end;
@ -233,11 +236,8 @@ process2(Args, Auth, AccessCommands) ->
end. end.
get_accesscommands() -> get_accesscommands() ->
case ejabberd_config:get_local_option(ejabberdctl_access_commands) of ejabberd_config:get_local_option(ejabberdctl_access_commands,
ACs when is_list(ACs) -> ACs; fun(V) when is_list(V) -> V end, []).
_ -> []
end.
%%----------------------------- %%-----------------------------
%% Command calling %% Command calling
@ -281,8 +281,9 @@ try_call_command(Args, Auth, AccessCommands) ->
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()} | {error, ErrorType} %% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()} | {error, ErrorType}
call_command([CmdString | Args], Auth, AccessCommands) -> call_command([CmdString | Args], Auth, AccessCommands) ->
CmdStringU = ejabberd_regexp:greplace(CmdString, "-", "_"), CmdStringU = ejabberd_regexp:greplace(
Command = list_to_atom(CmdStringU), list_to_binary(CmdString), <<"-">>, <<"_">>),
Command = list_to_atom(binary_to_list(CmdStringU)),
case ejabberd_commands:get_command_format(Command) of case ejabberd_commands:get_command_format(Command) of
{error, command_unknown} -> {error, command_unknown} ->
{error, command_unknown}; {error, command_unknown};
@ -331,10 +332,12 @@ format_args(Args, ArgsFormat) ->
format_arg(Arg, integer) -> format_arg(Arg, integer) ->
format_arg2(Arg, "~d"); format_arg2(Arg, "~d");
format_arg(Arg, binary) ->
list_to_binary(format_arg(Arg, string));
format_arg("", string) -> format_arg("", string) ->
""; "";
format_arg(Arg, string) -> format_arg(Arg, string) ->
NumChars = integer_to_list(string:len(Arg)), NumChars = integer_to_list(length(Arg)),
Parse = "~" ++ NumChars ++ "c", Parse = "~" ++ NumChars ++ "c",
format_arg2(Arg, Parse). format_arg2(Arg, Parse).
@ -540,24 +543,25 @@ split_desc_segments(MaxL, Words) ->
join(L, Words) -> join(L, Words) ->
join(L, Words, 0, [], []). join(L, Words, 0, [], []).
join(_L, [], _LenLastSeg, LastSeg, ResSeg) -> join(_Len, [], _CurSegLen, CurSeg, AllSegs) ->
ResSeg2 = [lists:reverse(LastSeg) | ResSeg], lists:reverse([CurSeg | AllSegs]);
lists:reverse(ResSeg2); join(Len, [Word | Tail], CurSegLen, CurSeg, AllSegs) ->
join(L, [Word | Words], LenLastSeg, LastSeg, ResSeg) -> WordLen = length(Word),
LWord = length(Word), SegSize = WordLen + CurSegLen + 1,
case LWord + LenLastSeg < L of {NewCurSeg, NewAllSegs, NewCurSegLen} =
if SegSize < Len ->
{[CurSeg, " ", Word], AllSegs, SegSize};
true -> true ->
%% This word fits in the last segment {Word, [CurSeg | AllSegs], WordLen}
%% If this word ends with "\n", reset column counter end,
case string:str(Word, "\n") of NewLen = case string:str(Word, "\n") of
0 -> 0 ->
join(L, Words, LenLastSeg+LWord+1, [" ", Word | LastSeg], ResSeg); NewCurSegLen;
_ -> _ ->
join(L, Words, LWord+1, [" ", Word | LastSeg], ResSeg) 0
end; end,
false -> join(Len, Tail, NewLen, NewCurSeg, NewAllSegs).
join(L, Words, LWord, [" ", Word], [lists:reverse(LastSeg) | ResSeg])
end.
format_command_lines(CALD, MaxCmdLen, MaxC, ShCode, dual) format_command_lines(CALD, MaxCmdLen, MaxC, ShCode, dual)
when MaxC - MaxCmdLen < 40 -> when MaxC - MaxCmdLen < 40 ->
@ -568,7 +572,8 @@ format_command_lines(CALD, MaxCmdLen, MaxC, ShCode, dual) ->
lists:map( lists:map(
fun({Cmd, Args, CmdArgsL, Desc}) -> fun({Cmd, Args, CmdArgsL, Desc}) ->
DescFmt = prepare_description(MaxCmdLen+4, MaxC, Desc), DescFmt = prepare_description(MaxCmdLen+4, MaxC, Desc),
[" ", ?B(Cmd), " ", [[?U(Arg), " "] || Arg <- Args], string:chars($\s, MaxCmdLen - CmdArgsL + 1), [" ", ?B(Cmd), " ", [[?U(Arg), " "] || Arg <- Args],
string:chars($\s, MaxCmdLen - CmdArgsL + 1),
DescFmt, "\n"] DescFmt, "\n"]
end, CALD); end, CALD);
@ -608,7 +613,8 @@ print_usage_tags(Tag, MaxC, ShCode) ->
end, end,
CommandsList = lists:map( CommandsList = lists:map(
fun(NameString) -> fun(NameString) ->
C = ejabberd_commands:get_command_definition(list_to_atom(NameString)), C = ejabberd_commands:get_command_definition(
list_to_atom(NameString)),
#ejabberd_commands{name = Name, #ejabberd_commands{name = Name,
args = Args, args = Args,
desc = Desc} = C, desc = Desc} = C,
@ -689,10 +695,10 @@ filter_commands(All, SubString) ->
end. end.
filter_commands_regexp(All, Glob) -> filter_commands_regexp(All, Glob) ->
RegExp = ejabberd_regexp:sh_to_awk(Glob), RegExp = ejabberd_regexp:sh_to_awk(list_to_binary(Glob)),
lists:filter( lists:filter(
fun(Command) -> fun(Command) ->
case ejabberd_regexp:run(Command, RegExp) of case ejabberd_regexp:run(list_to_binary(Command), RegExp) of
match -> match ->
true; true;
nomatch -> nomatch ->

View File

@ -20,6 +20,9 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-define(STATUS_SUCCESS, 0). -define(STATUS_SUCCESS, 0).
-define(STATUS_ERROR, 1). -define(STATUS_ERROR, 1).
-define(STATUS_USAGE, 2). -define(STATUS_USAGE, 2).
-define(STATUS_BADRPC, 3). -define(STATUS_BADRPC, 3).

View File

@ -25,6 +25,7 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(ejabberd_frontend_socket). -module(ejabberd_frontend_socket).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(gen_server). -behaviour(gen_server).
@ -48,8 +49,8 @@
sockname/1, peername/1]). sockname/1, peername/1]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2,
terminate/2, code_change/3]). handle_info/2, terminate/2, code_change/3]).
-record(state, {sockmod, socket, receiver}). -record(state, {sockmod, socket, receiver}).
@ -69,17 +70,17 @@ start_link(Module, SockMod, Socket, Opts, Receiver) ->
start(Module, SockMod, Socket, Opts) -> start(Module, SockMod, Socket, Opts) ->
case Module:socket_type() of case Module:socket_type() of
xml_stream -> xml_stream ->
MaxStanzaSize = MaxStanzaSize = case lists:keysearch(max_stanza_size, 1,
case lists:keysearch(max_stanza_size, 1, Opts) of Opts)
of
{value, {_, Size}} -> Size; {value, {_, Size}} -> Size;
_ -> infinity _ -> infinity
end, end,
Receiver = ejabberd_receiver:start(Socket, SockMod, none, MaxStanzaSize), Receiver = ejabberd_receiver:start(Socket, SockMod,
none, MaxStanzaSize),
case SockMod:controlling_process(Socket, Receiver) of case SockMod:controlling_process(Socket, Receiver) of
ok -> ok -> ok;
ok; {error, _Reason} -> SockMod:close(Socket)
{error, _Reason} ->
SockMod:close(Socket)
end, end,
supervisor:start_child(ejabberd_frontend_socket_sup, supervisor:start_child(ejabberd_frontend_socket_sup,
[Module, SockMod, Socket, Opts, Receiver]); [Module, SockMod, Socket, Opts, Receiver]);
@ -108,8 +109,7 @@ compress(FsmRef) ->
FsmRef. FsmRef.
compress(FsmRef, Data) -> compress(FsmRef, Data) ->
gen_server:call(FsmRef, {compress, Data}), gen_server:call(FsmRef, {compress, Data}), FsmRef.
FsmRef.
reset_stream(FsmRef) -> reset_stream(FsmRef) ->
gen_server:call(FsmRef, reset_stream). gen_server:call(FsmRef, reset_stream).
@ -120,8 +120,7 @@ send(FsmRef, Data) ->
change_shaper(FsmRef, Shaper) -> change_shaper(FsmRef, Shaper) ->
gen_server:call(FsmRef, {change_shaper, Shaper}). gen_server:call(FsmRef, {change_shaper, Shaper}).
monitor(FsmRef) -> monitor(FsmRef) -> erlang:monitor(process, FsmRef).
erlang:monitor(process, FsmRef).
get_sockmod(FsmRef) -> get_sockmod(FsmRef) ->
gen_server:call(FsmRef, get_sockmod). gen_server:call(FsmRef, get_sockmod).
@ -132,11 +131,9 @@ get_peer_certificate(FsmRef) ->
get_verify_result(FsmRef) -> get_verify_result(FsmRef) ->
gen_server:call(FsmRef, get_verify_result). gen_server:call(FsmRef, get_verify_result).
close(FsmRef) -> close(FsmRef) -> gen_server:call(FsmRef, close).
gen_server:call(FsmRef, close).
sockname(FsmRef) -> sockname(FsmRef) -> gen_server:call(FsmRef, sockname).
gen_server:call(FsmRef, sockname).
peername(_FsmRef) -> peername(_FsmRef) ->
%% TODO: Frontend improvements planned by Aleksey %% TODO: Frontend improvements planned by Aleksey
@ -156,7 +153,6 @@ peername(_FsmRef) ->
%% Description: Initiates the server %% Description: Initiates the server
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([Module, SockMod, Socket, Opts, Receiver]) -> init([Module, SockMod, Socket, Opts, Receiver]) ->
%% TODO: monitor the receiver
Node = ejabberd_node_groups:get_closest_node(backend), Node = ejabberd_node_groups:get_closest_node(backend),
{SockMod2, Socket2} = check_starttls(SockMod, Socket, Receiver, Opts), {SockMod2, Socket2} = check_starttls(SockMod, Socket, Receiver, Opts),
{ok, Pid} = {ok, Pid} =
@ -188,7 +184,8 @@ handle_call({starttls, TLSOpts, Data}, _From, State) ->
catch (State#state.sockmod):send( catch (State#state.sockmod):send(
State#state.socket, Data), State#state.socket, Data),
Reply = ok, Reply = ok,
{reply, Reply, State#state{socket = TLSSocket, sockmod = tls}, {reply, Reply,
State#state{socket = TLSSocket, sockmod = tls},
?HIBERNATE_TIMEOUT}; ?HIBERNATE_TIMEOUT};
handle_call(compress, _From, State) -> handle_call(compress, _From, State) ->
@ -208,42 +205,35 @@ handle_call({compress, Data}, _From, State) ->
catch (State#state.sockmod):send( catch (State#state.sockmod):send(
State#state.socket, Data), State#state.socket, Data),
Reply = ok, Reply = ok,
{reply, Reply, State#state{socket = ZlibSocket, sockmod = ejabberd_zlib}, {reply, Reply,
State#state{socket = ZlibSocket, sockmod = ejabberd_zlib},
?HIBERNATE_TIMEOUT}; ?HIBERNATE_TIMEOUT};
handle_call(reset_stream, _From, State) -> handle_call(reset_stream, _From, State) ->
ejabberd_receiver:reset_stream(State#state.receiver), ejabberd_receiver:reset_stream(State#state.receiver),
Reply = ok, Reply = ok,
{reply, Reply, State, ?HIBERNATE_TIMEOUT}; {reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call({send, Data}, _From, State) -> handle_call({send, Data}, _From, State) ->
catch (State#state.sockmod):send( catch (State#state.sockmod):send(State#state.socket, Data),
State#state.socket, Data),
Reply = ok, Reply = ok,
{reply, Reply, State, ?HIBERNATE_TIMEOUT}; {reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call({change_shaper, Shaper}, _From, State) -> handle_call({change_shaper, Shaper}, _From, State) ->
ejabberd_receiver:change_shaper(State#state.receiver, Shaper), ejabberd_receiver:change_shaper(State#state.receiver,
Shaper),
Reply = ok, Reply = ok,
{reply, Reply, State, ?HIBERNATE_TIMEOUT}; {reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(get_sockmod, _From, State) -> handle_call(get_sockmod, _From, State) ->
Reply = State#state.sockmod, Reply = State#state.sockmod,
{reply, Reply, State, ?HIBERNATE_TIMEOUT}; {reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(get_peer_certificate, _From, State) -> handle_call(get_peer_certificate, _From, State) ->
Reply = tls:get_peer_certificate(State#state.socket), Reply = tls:get_peer_certificate(State#state.socket),
{reply, Reply, State, ?HIBERNATE_TIMEOUT}; {reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(get_verify_result, _From, State) -> handle_call(get_verify_result, _From, State) ->
Reply = tls:get_verify_result(State#state.socket), Reply = tls:get_verify_result(State#state.socket),
{reply, Reply, State, ?HIBERNATE_TIMEOUT}; {reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(close, _From, State) -> handle_call(close, _From, State) ->
ejabberd_receiver:close(State#state.receiver), ejabberd_receiver:close(State#state.receiver),
Reply = ok, Reply = ok,
{stop, normal, Reply, State}; {stop, normal, Reply, State};
handle_call(sockname, _From, State) -> handle_call(sockname, _From, State) ->
#state{sockmod = SockMod, socket = Socket} = State, #state{sockmod = SockMod, socket = Socket} = State,
Reply = Reply =
@ -254,21 +244,15 @@ handle_call(sockname, _From, State) ->
SockMod:sockname(Socket) SockMod:sockname(Socket)
end, end,
{reply, Reply, State, ?HIBERNATE_TIMEOUT}; {reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(peername, _From, State) -> handle_call(peername, _From, State) ->
#state{sockmod = SockMod, socket = Socket} = State, #state{sockmod = SockMod, socket = Socket} = State,
Reply = Reply = case SockMod of
case SockMod of gen_tcp -> inet:peername(Socket);
gen_tcp -> _ -> SockMod:peername(Socket)
inet:peername(Socket);
_ ->
SockMod:peername(Socket)
end, end,
{reply, Reply, State, ?HIBERNATE_TIMEOUT}; {reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(_Request, _From, State) -> handle_call(_Request, _From, State) ->
Reply = ok, Reply = ok, {reply, Reply, State, ?HIBERNATE_TIMEOUT}.
{reply, Reply, State, ?HIBERNATE_TIMEOUT}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} | %% Function: handle_cast(Msg, State) -> {noreply, State} |
@ -286,7 +270,8 @@ handle_cast(_Msg, State) ->
%% Description: Handling all non call/cast messages %% Description: Handling all non call/cast messages
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_info(timeout, State) -> handle_info(timeout, State) ->
proc_lib:hibernate(gen_server, enter_loop, [?MODULE, [], State]), proc_lib:hibernate(gen_server, enter_loop,
[?MODULE, [], State]),
{noreply, State, ?HIBERNATE_TIMEOUT}; {noreply, State, ?HIBERNATE_TIMEOUT};
handle_info(_Info, State) -> handle_info(_Info, State) ->
{noreply, State, ?HIBERNATE_TIMEOUT}. {noreply, State, ?HIBERNATE_TIMEOUT}.
@ -298,15 +283,13 @@ handle_info(_Info, State) ->
%% cleaning up. When it returns, the gen_server terminates with Reason. %% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored. %% The return value is ignored.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
terminate(_Reason, _State) -> terminate(_Reason, _State) -> ok.
ok.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed %% Description: Convert process state when code is changed
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) -> {ok, State}.
{ok, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%%% Internal functions %%% Internal functions

View File

@ -67,58 +67,76 @@
start_link() -> start_link() ->
gen_server:start_link({local, ejabberd_hooks}, ejabberd_hooks, [], []). gen_server:start_link({local, ejabberd_hooks}, ejabberd_hooks, [], []).
%% @spec (Hook::atom(), Function::function(), Seq::integer()) -> ok -spec add(atom(), fun(), number()) -> any().
%% @doc See add/4. %% @doc See add/4.
add(Hook, Function, Seq) when is_function(Function) -> add(Hook, Function, Seq) when is_function(Function) ->
add(Hook, global, undefined, Function, Seq). add(Hook, global, undefined, Function, Seq).
-spec add(atom(), binary() | atom(), fun() | atom() , number()) -> any().
add(Hook, Host, Function, Seq) when is_function(Function) -> add(Hook, Host, Function, Seq) when is_function(Function) ->
add(Hook, Host, undefined, Function, Seq); add(Hook, Host, undefined, Function, Seq);
%% @spec (Hook::atom(), Module::atom(), Function::atom(), Seq::integer()) -> ok
%% @doc Add a module and function to this hook. %% @doc Add a module and function to this hook.
%% The integer sequence is used to sort the calls: low number is called before high number. %% The integer sequence is used to sort the calls: low number is called before high number.
add(Hook, Module, Function, Seq) -> add(Hook, Module, Function, Seq) ->
add(Hook, global, Module, Function, Seq). add(Hook, global, Module, Function, Seq).
-spec add(atom(), binary() | global, atom(), atom() | fun(), number()) -> any().
add(Hook, Host, Module, Function, Seq) -> add(Hook, Host, Module, Function, Seq) ->
gen_server:call(ejabberd_hooks, {add, Hook, Host, Module, Function, Seq}). gen_server:call(ejabberd_hooks, {add, Hook, Host, Module, Function, Seq}).
-spec add_dist(atom(), atom(), atom(), atom() | fun(), number()) -> any().
add_dist(Hook, Node, Module, Function, Seq) -> add_dist(Hook, Node, Module, Function, Seq) ->
gen_server:call(ejabberd_hooks, {add, Hook, global, Node, Module, Function, Seq}). gen_server:call(ejabberd_hooks, {add, Hook, global, Node, Module, Function, Seq}).
-spec add_dist(atom(), binary() | global, atom(), atom(), atom() | fun(), number()) -> any().
add_dist(Hook, Host, Node, Module, Function, Seq) -> add_dist(Hook, Host, Node, Module, Function, Seq) ->
gen_server:call(ejabberd_hooks, {add, Hook, Host, Node, Module, Function, Seq}). gen_server:call(ejabberd_hooks, {add, Hook, Host, Node, Module, Function, Seq}).
%% @spec (Hook::atom(), Function::function(), Seq::integer()) -> ok -spec delete(atom(), fun(), number()) -> ok.
%% @doc See del/4. %% @doc See del/4.
delete(Hook, Function, Seq) when is_function(Function) -> delete(Hook, Function, Seq) when is_function(Function) ->
delete(Hook, global, undefined, Function, Seq). delete(Hook, global, undefined, Function, Seq).
-spec delete(atom(), binary() | atom(), atom() | fun(), number()) -> ok.
delete(Hook, Host, Function, Seq) when is_function(Function) -> delete(Hook, Host, Function, Seq) when is_function(Function) ->
delete(Hook, Host, undefined, Function, Seq); delete(Hook, Host, undefined, Function, Seq);
%% @spec (Hook::atom(), Module::atom(), Function::atom(), Seq::integer()) -> ok
%% @doc Delete a module and function from this hook. %% @doc Delete a module and function from this hook.
%% It is important to indicate exactly the same information than when the call was added. %% It is important to indicate exactly the same information than when the call was added.
delete(Hook, Module, Function, Seq) -> delete(Hook, Module, Function, Seq) ->
delete(Hook, global, Module, Function, Seq). delete(Hook, global, Module, Function, Seq).
-spec delete(atom(), binary() | global, atom(), atom() | fun(), number()) -> ok.
delete(Hook, Host, Module, Function, Seq) -> delete(Hook, Host, Module, Function, Seq) ->
gen_server:call(ejabberd_hooks, {delete, Hook, Host, Module, Function, Seq}). gen_server:call(ejabberd_hooks, {delete, Hook, Host, Module, Function, Seq}).
-spec delete_dist(atom(), atom(), atom(), atom() | fun(), number()) -> ok.
delete_dist(Hook, Node, Module, Function, Seq) -> delete_dist(Hook, Node, Module, Function, Seq) ->
delete_dist(Hook, global, Node, Module, Function, Seq). delete_dist(Hook, global, Node, Module, Function, Seq).
-spec delete_dist(atom(), binary() | global, atom(), atom(), atom() | fun(), number()) -> ok.
delete_dist(Hook, Host, Node, Module, Function, Seq) -> delete_dist(Hook, Host, Node, Module, Function, Seq) ->
gen_server:call(ejabberd_hooks, {delete, Hook, Host, Node, Module, Function, Seq}). gen_server:call(ejabberd_hooks, {delete, Hook, Host, Node, Module, Function, Seq}).
%% @spec (Hook::atom(), Args) -> ok -spec run(atom(), list()) -> ok.
%% @doc Run the calls of this hook in order, don't care about function results. %% @doc Run the calls of this hook in order, don't care about function results.
%% If a call returns stop, no more calls are performed. %% If a call returns stop, no more calls are performed.
run(Hook, Args) -> run(Hook, Args) ->
run(Hook, global, Args). run(Hook, global, Args).
-spec run(atom(), binary() | global, list()) -> ok.
run(Hook, Host, Args) -> run(Hook, Host, Args) ->
case ets:lookup(hooks, {Hook, Host}) of case ets:lookup(hooks, {Hook, Host}) of
[{_, Ls}] -> [{_, Ls}] ->
@ -127,7 +145,8 @@ run(Hook, Host, Args) ->
ok ok
end. end.
%% @spec (Hook::atom(), Val, Args) -> Val | stopped | NewVal -spec run_fold(atom(), any(), list()) -> any().
%% @doc Run the calls of this hook in order. %% @doc Run the calls of this hook in order.
%% The arguments passed to the function are: [Val | Args]. %% The arguments passed to the function are: [Val | Args].
%% The result of a call is used as Val for the next call. %% The result of a call is used as Val for the next call.
@ -136,6 +155,8 @@ run(Hook, Host, Args) ->
run_fold(Hook, Val, Args) -> run_fold(Hook, Val, Args) ->
run_fold(Hook, global, Val, Args). run_fold(Hook, global, Val, Args).
-spec run_fold(atom(), binary() | global, any(), list()) -> any().
run_fold(Hook, Host, Val, Args) -> run_fold(Hook, Host, Val, Args) ->
case ets:lookup(hooks, {Hook, Host}) of case ets:lookup(hooks, {Hook, Host}) of
[{_, Ls}] -> [{_, Ls}] ->

View File

@ -35,7 +35,8 @@
stop_listener/2, stop_listener/2,
parse_listener_portip/2, parse_listener_portip/2,
add_listener/3, add_listener/3,
delete_listener/2 delete_listener/2,
validate_cfg/1
]). ]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
@ -53,7 +54,7 @@ init(_) ->
{ok, {{one_for_one, 10, 1}, []}}. {ok, {{one_for_one, 10, 1}, []}}.
bind_tcp_ports() -> bind_tcp_ports() ->
case ejabberd_config:get_local_option(listen) of case ejabberd_config:get_local_option(listen, fun validate_cfg/1) of
undefined -> undefined ->
ignore; ignore;
Ls -> Ls ->
@ -77,7 +78,8 @@ bind_tcp_port(PortIP, Module, RawOpts) ->
udp -> ok; udp -> ok;
_ -> _ ->
ListenSocket = listen_tcp(PortIP, Module, SockOpts, Port, IPS), ListenSocket = listen_tcp(PortIP, Module, SockOpts, Port, IPS),
ets:insert(listen_sockets, {PortIP, ListenSocket}) ets:insert(listen_sockets, {PortIP, ListenSocket}),
ok
end end
catch catch
throw:{error, Error} -> throw:{error, Error} ->
@ -85,7 +87,7 @@ bind_tcp_port(PortIP, Module, RawOpts) ->
end. end.
start_listeners() -> start_listeners() ->
case ejabberd_config:get_local_option(listen) of case ejabberd_config:get_local_option(listen, fun validate_cfg/1) of
undefined -> undefined ->
ignore; ignore;
Ls -> Ls ->
@ -215,17 +217,17 @@ parse_listener_portip(PortIP, Opts) ->
case add_proto(PortIP, Opts) of case add_proto(PortIP, Opts) of
{P, Prot} -> {P, Prot} ->
T = get_ip_tuple(IPOpt, IPVOpt), T = get_ip_tuple(IPOpt, IPVOpt),
S = inet_parse:ntoa(T), S = jlib:ip_to_list(T),
{P, T, S, Prot}; {P, T, S, Prot};
{P, T, Prot} when is_integer(P) and is_tuple(T) -> {P, T, Prot} when is_integer(P) and is_tuple(T) ->
S = inet_parse:ntoa(T), S = jlib:ip_to_list(T),
{P, T, S, Prot}; {P, T, S, Prot};
{P, S, Prot} when is_integer(P) and is_list(S) -> {P, S, Prot} when is_integer(P) and is_binary(S) ->
[S | _] = string:tokens(S, "/"), [S | _] = str:tokens(S, <<"/">>),
{ok, T} = inet_parse:address(S), {ok, T} = inet_parse:address(binary_to_list(S)),
{P, T, S, Prot} {P, T, S, Prot}
end, end,
IPV = case size(IPT) of IPV = case tuple_size(IPT) of
4 -> inet; 4 -> inet;
8 -> inet6 8 -> inet6
end, end,
@ -337,7 +339,7 @@ start_listener2(Port, Module, Opts) ->
start_listener_sup(Port, Module, Opts). start_listener_sup(Port, Module, Opts).
start_module_sup(_Port, Module) -> start_module_sup(_Port, Module) ->
Proc1 = gen_mod:get_module_proc("sup", Module), Proc1 = gen_mod:get_module_proc(<<"sup">>, Module),
ChildSpec1 = ChildSpec1 =
{Proc1, {Proc1,
{ejabberd_tmp_sup, start_link, [Proc1, strip_frontend(Module)]}, {ejabberd_tmp_sup, start_link, [Proc1, strip_frontend(Module)]},
@ -357,7 +359,7 @@ start_listener_sup(Port, Module, Opts) ->
supervisor:start_child(ejabberd_listeners, ChildSpec). supervisor:start_child(ejabberd_listeners, ChildSpec).
stop_listeners() -> stop_listeners() ->
Ports = ejabberd_config:get_local_option(listen), Ports = ejabberd_config:get_local_option(listen, fun validate_cfg/1),
lists:foreach( lists:foreach(
fun({PortIpNetp, Module, _Opts}) -> fun({PortIpNetp, Module, _Opts}) ->
delete_listener(PortIpNetp, Module) delete_listener(PortIpNetp, Module)
@ -390,7 +392,8 @@ add_listener(PortIP, Module, Opts) ->
PortIP1 = {Port, IPT, Proto}, PortIP1 = {Port, IPT, Proto},
case start_listener(PortIP1, Module, Opts) of case start_listener(PortIP1, Module, Opts) of
{ok, _Pid} -> {ok, _Pid} ->
Ports = case ejabberd_config:get_local_option(listen) of Ports = case ejabberd_config:get_local_option(
listen, fun validate_cfg/1) of
undefined -> undefined ->
[]; [];
Ls -> Ls ->
@ -420,7 +423,8 @@ delete_listener(PortIP, Module) ->
delete_listener(PortIP, Module, Opts) -> delete_listener(PortIP, Module, Opts) ->
{Port, IPT, _, _, Proto, _} = parse_listener_portip(PortIP, Opts), {Port, IPT, _, _, Proto, _} = parse_listener_portip(PortIP, Opts),
PortIP1 = {Port, IPT, Proto}, PortIP1 = {Port, IPT, Proto},
Ports = case ejabberd_config:get_local_option(listen) of Ports = case ejabberd_config:get_local_option(
listen, fun validate_cfg/1) of
undefined -> undefined ->
[]; [];
Ls -> Ls ->
@ -430,11 +434,16 @@ delete_listener(PortIP, Module, Opts) ->
ejabberd_config:add_local_option(listen, Ports1), ejabberd_config:add_local_option(listen, Ports1),
stop_listener(PortIP1, Module). stop_listener(PortIP1, Module).
-spec is_frontend({frontend, module} | module()) -> boolean().
is_frontend({frontend, _Module}) -> true; is_frontend({frontend, _Module}) -> true;
is_frontend(_) -> false. is_frontend(_) -> false.
%% @doc(FrontMod) -> atom() %% @doc(FrontMod) -> atom()
%% where FrontMod = atom() | {frontend, atom()} %% where FrontMod = atom() | {frontend, atom()}
-spec strip_frontend({frontend, module()} | module()) -> module().
strip_frontend({frontend, Module}) -> Module; strip_frontend({frontend, Module}) -> Module;
strip_frontend(Module) when is_atom(Module) -> Module. strip_frontend(Module) when is_atom(Module) -> Module.
@ -505,7 +514,7 @@ socket_error(Reason, PortIP, Module, SockOpts, Port, IPS) ->
"IP address not available: " ++ IPS; "IP address not available: " ++ IPS;
eaddrinuse -> eaddrinuse ->
"IP address and port number already used: " "IP address and port number already used: "
++IPS++" "++integer_to_list(Port); ++binary_to_list(IPS)++" "++integer_to_list(Port);
_ -> _ ->
format_error(Reason) format_error(Reason)
end, end,
@ -520,3 +529,44 @@ format_error(Reason) ->
ReasonStr -> ReasonStr ->
ReasonStr ReasonStr
end. end.
-define(IS_CHAR(C), (is_integer(C) and (C >= 0) and (C =< 255))).
-define(IS_UINT(U), (is_integer(U) and (U >= 0) and (U =< 65535))).
-define(IS_PORT(P), (is_integer(P) and (P > 0) and (P =< 65535))).
-define(IS_TRANSPORT(T), ((T == tcp) or (T == udp))).
-type transport() :: udp | tcp.
-type port_ip_transport() :: inet:port_number() |
{inet:port_number(), transport()} |
{inet:port_number(), inet:ip_address()} |
{inet:port_number(), inet:ip_address(),
transport()}.
-spec validate_cfg(list()) -> [{port_ip_transport(), module(), list()}].
validate_cfg(L) ->
lists:map(
fun({PortIPTransport, Mod, Opts}) when is_atom(Mod), is_list(Opts) ->
case PortIPTransport of
Port when ?IS_PORT(Port) ->
{Port, Mod, Opts};
{Port, Trans} when ?IS_PORT(Port) and ?IS_TRANSPORT(Trans) ->
{{Port, Trans}, Mod, Opts};
{Port, IP} when ?IS_PORT(Port) ->
{{Port, prepare_ip(IP)}, Mod, Opts};
{Port, IP, Trans} when ?IS_PORT(Port) and ?IS_TRANSPORT(Trans) ->
{{Port, prepare_ip(IP), Trans}, Mod, Opts}
end
end, L).
prepare_ip({A, B, C, D} = IP)
when ?IS_CHAR(A) and ?IS_CHAR(B) and ?IS_CHAR(C) and ?IS_CHAR(D) ->
IP;
prepare_ip({A, B, C, D, E, F, G, H} = IP)
when ?IS_UINT(A) and ?IS_UINT(B) and ?IS_UINT(C) and ?IS_UINT(D)
and ?IS_UINT(E) and ?IS_UINT(F) and ?IS_UINT(G) and ?IS_UINT(H) ->
IP;
prepare_ip(IP) when is_list(IP) ->
{ok, Addr} = inet_parse:address(IP),
Addr;
prepare_ip(IP) when is_binary(IP) ->
prepare_ip(binary_to_list(IP)).

View File

@ -25,6 +25,7 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(ejabberd_local). -module(ejabberd_local).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(gen_server). -behaviour(gen_server).
@ -32,30 +33,27 @@
%% API %% API
-export([start_link/0]). -export([start_link/0]).
-export([route/3, -export([route/3, route_iq/4, route_iq/5,
route_iq/4, process_iq_reply/3, register_iq_handler/4,
route_iq/5, register_iq_handler/5, register_iq_response_handler/4,
process_iq_reply/3, register_iq_response_handler/5, unregister_iq_handler/2,
register_iq_handler/4, unregister_iq_response_handler/2, refresh_iq_handlers/0,
register_iq_handler/5, bounce_resource_packet/3]).
register_iq_response_handler/4,
register_iq_response_handler/5,
unregister_iq_handler/2,
unregister_iq_response_handler/2,
refresh_iq_handlers/0,
bounce_resource_packet/3
]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2,
terminate/2, code_change/3]). handle_info/2, terminate/2, code_change/3]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl"). -include("jlib.hrl").
-record(state, {}). -record(state, {}).
-record(iq_response, {id, module, function, timer}). -record(iq_response, {id = <<"">> :: binary(),
module :: atom(),
function :: atom() | fun(),
timer = make_ref() :: reference()}).
-define(IQTABLE, local_iqtable). -define(IQTABLE, local_iqtable).
@ -70,7 +68,8 @@
%% Description: Starts the server %% Description: Starts the server
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [],
[]).
process_iq(From, To, Packet) -> process_iq(From, To, Packet) ->
IQ = jlib:iq_query_info(Packet), IQ = jlib:iq_query_info(Packet),
@ -80,19 +79,16 @@ process_iq(From, To, Packet) ->
case ets:lookup(?IQTABLE, {XMLNS, Host}) of case ets:lookup(?IQTABLE, {XMLNS, Host}) of
[{_, Module, Function}] -> [{_, Module, Function}] ->
ResIQ = Module:Function(From, To, IQ), ResIQ = Module:Function(From, To, IQ),
if if ResIQ /= ignore ->
ResIQ /= ignore -> ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
ejabberd_router:route( true -> ok
To, From, jlib:iq_to_xml(ResIQ));
true ->
ok
end; end;
[{_, Module, Function, Opts}] -> [{_, Module, Function, Opts}] ->
gen_iq_handler:handle(Host, Module, Function, Opts, gen_iq_handler:handle(Host, Module, Function, Opts,
From, To, IQ); From, To, IQ);
[] -> [] ->
Err = jlib:make_error_reply( Err = jlib:make_error_reply(Packet,
Packet, ?ERR_FEATURE_NOT_IMPLEMENTED), ?ERR_FEATURE_NOT_IMPLEMENTED),
ejabberd_router:route(To, From, Err) ejabberd_router:route(To, From, Err)
end; end;
reply -> reply ->
@ -106,14 +102,10 @@ process_iq(From, To, Packet) ->
process_iq_reply(From, To, #iq{id = ID} = IQ) -> process_iq_reply(From, To, #iq{id = ID} = IQ) ->
case get_iq_callback(ID) of case get_iq_callback(ID) of
{ok, undefined, Function} -> {ok, undefined, Function} -> Function(IQ), ok;
Function(IQ),
ok;
{ok, Module, Function} -> {ok, Module, Function} ->
Module:Function(From, To, IQ), Module:Function(From, To, IQ), ok;
ok; _ -> nothing
_ ->
nothing
end. end.
route(From, To, Packet) -> route(From, To, Packet) ->
@ -121,14 +113,14 @@ route(From, To, Packet) ->
{'EXIT', Reason} -> {'EXIT', Reason} ->
?ERROR_MSG("~p~nwhen processing: ~p", ?ERROR_MSG("~p~nwhen processing: ~p",
[Reason, {From, To, Packet}]); [Reason, {From, To, Packet}]);
_ -> _ -> ok
ok
end. end.
route_iq(From, To, IQ, F) -> route_iq(From, To, IQ, F) ->
route_iq(From, To, IQ, F, undefined). route_iq(From, To, IQ, F, undefined).
route_iq(From, To, #iq{type = Type} = IQ, F, Timeout) when is_function(F) -> route_iq(From, To, #iq{type = Type} = IQ, F, Timeout)
when is_function(F) ->
Packet = if Type == set; Type == get -> Packet = if Type == set; Type == get ->
ID = randoms:get_string(), ID = randoms:get_string(),
Host = From#jid.lserver, Host = From#jid.lserver,
@ -139,15 +131,16 @@ route_iq(From, To, #iq{type = Type} = IQ, F, Timeout) when is_function(F) ->
end, end,
ejabberd_router:route(From, To, Packet). ejabberd_router:route(From, To, Packet).
register_iq_response_handler(Host, ID, Module, Function) -> register_iq_response_handler(Host, ID, Module,
register_iq_response_handler(Host, ID, Module, Function, undefined). Function) ->
register_iq_response_handler(Host, ID, Module, Function,
undefined).
register_iq_response_handler(_Host, ID, Module, Function, Timeout0) -> register_iq_response_handler(_Host, ID, Module,
Function, Timeout0) ->
Timeout = case Timeout0 of Timeout = case Timeout0 of
undefined -> undefined -> ?IQ_TIMEOUT;
?IQ_TIMEOUT; N when is_integer(N), N > 0 -> N
N when is_integer(N), N > 0 ->
N
end, end,
TRef = erlang:start_timer(Timeout, ejabberd_local, ID), TRef = erlang:start_timer(Timeout, ejabberd_local, ID),
mnesia:dirty_write(#iq_response{id = ID, mnesia:dirty_write(#iq_response{id = ID,
@ -156,14 +149,15 @@ register_iq_response_handler(_Host, ID, Module, Function, Timeout0) ->
timer = TRef}). timer = TRef}).
register_iq_handler(Host, XMLNS, Module, Fun) -> register_iq_handler(Host, XMLNS, Module, Fun) ->
ejabberd_local ! {register_iq_handler, Host, XMLNS, Module, Fun}. ejabberd_local !
{register_iq_handler, Host, XMLNS, Module, Fun}.
register_iq_handler(Host, XMLNS, Module, Fun, Opts) -> register_iq_handler(Host, XMLNS, Module, Fun, Opts) ->
ejabberd_local ! {register_iq_handler, Host, XMLNS, Module, Fun, Opts}. ejabberd_local !
{register_iq_handler, Host, XMLNS, Module, Fun, Opts}.
unregister_iq_response_handler(_Host, ID) -> unregister_iq_response_handler(_Host, ID) ->
catch get_iq_callback(ID), catch get_iq_callback(ID), ok.
ok.
unregister_iq_handler(Host, XMLNS) -> unregister_iq_handler(Host, XMLNS) ->
ejabberd_local ! {unregister_iq_handler, Host, XMLNS}. ejabberd_local ! {unregister_iq_handler, Host, XMLNS}.
@ -172,7 +166,8 @@ refresh_iq_handlers() ->
ejabberd_local ! refresh_iq_handlers. ejabberd_local ! refresh_iq_handlers.
bounce_resource_packet(From, To, Packet) -> bounce_resource_packet(From, To, Packet) ->
Err = jlib:make_error_reply(Packet, ?ERR_ITEM_NOT_FOUND), Err = jlib:make_error_reply(Packet,
?ERR_ITEM_NOT_FOUND),
ejabberd_router:route(To, From, Err), ejabberd_router:route(To, From, Err),
stop. stop.
@ -188,12 +183,15 @@ bounce_resource_packet(From, To, Packet) ->
%% Description: Initiates the server %% Description: Initiates the server
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([]) -> init([]) ->
lists:foreach( lists:foreach(fun (Host) ->
fun(Host) -> ejabberd_router:register_route(Host,
ejabberd_router:register_route(Host, {apply, ?MODULE, route}), {apply, ?MODULE,
route}),
ejabberd_hooks:add(local_send_to_resource_hook, Host, ejabberd_hooks:add(local_send_to_resource_hook, Host,
?MODULE, bounce_resource_packet, 100) ?MODULE, bounce_resource_packet,
end, ?MYHOSTS), 100)
end,
?MYHOSTS),
catch ets:new(?IQTABLE, [named_table, public]), catch ets:new(?IQTABLE, [named_table, public]),
update_table(), update_table(),
mnesia:create_table(iq_response, mnesia:create_table(iq_response,
@ -212,70 +210,68 @@ init([]) ->
%% Description: Handling call messages %% Description: Handling call messages
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_call(_Request, _From, State) -> handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} | %% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} | %% {noreply, State, Timeout} |
%% {stop, Reason, State} %% {stop, Reason, State}
%% Description: Handling cast messages %% Description: Handling cast messages
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} | %% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} | %% {noreply, State, Timeout} |
%% {stop, Reason, State} %% {stop, Reason, State}
%% Description: Handling all non call/cast messages %% Description: Handling all non call/cast messages
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
Reply = ok, {reply, Reply, State}.
handle_cast(_Msg, State) -> {noreply, State}.
handle_info({route, From, To, Packet}, State) -> handle_info({route, From, To, Packet}, State) ->
case catch do_route(From, To, Packet) of case catch do_route(From, To, Packet) of
{'EXIT', Reason} -> {'EXIT', Reason} ->
?ERROR_MSG("~p~nwhen processing: ~p", ?ERROR_MSG("~p~nwhen processing: ~p",
[Reason, {From, To, Packet}]); [Reason, {From, To, Packet}]);
_ -> _ -> ok
ok
end, end,
{noreply, State}; {noreply, State};
handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) -> handle_info({register_iq_handler, Host, XMLNS, Module,
Function},
State) ->
ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function}), ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function}),
catch mod_disco:register_feature(Host, XMLNS), catch mod_disco:register_feature(Host, XMLNS),
{noreply, State}; {noreply, State};
handle_info({register_iq_handler, Host, XMLNS, Module, Function, Opts}, State) -> handle_info({register_iq_handler, Host, XMLNS, Module,
ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function, Opts}), Function, Opts},
State) ->
ets:insert(?IQTABLE,
{{XMLNS, Host}, Module, Function, Opts}),
catch mod_disco:register_feature(Host, XMLNS), catch mod_disco:register_feature(Host, XMLNS),
{noreply, State}; {noreply, State};
handle_info({unregister_iq_handler, Host, XMLNS}, State) -> handle_info({unregister_iq_handler, Host, XMLNS},
State) ->
case ets:lookup(?IQTABLE, {XMLNS, Host}) of case ets:lookup(?IQTABLE, {XMLNS, Host}) of
[{_, Module, Function, Opts}] -> [{_, Module, Function, Opts}] ->
gen_iq_handler:stop_iq_handler(Module, Function, Opts); gen_iq_handler:stop_iq_handler(Module, Function, Opts);
_ -> _ -> ok
ok
end, end,
ets:delete(?IQTABLE, {XMLNS, Host}), ets:delete(?IQTABLE, {XMLNS, Host}),
catch mod_disco:unregister_feature(Host, XMLNS), catch mod_disco:unregister_feature(Host, XMLNS),
{noreply, State}; {noreply, State};
handle_info(refresh_iq_handlers, State) -> handle_info(refresh_iq_handlers, State) ->
lists:foreach( lists:foreach(fun (T) ->
fun(T) ->
case T of case T of
{{XMLNS, Host}, _Module, _Function, _Opts} -> {{XMLNS, Host}, _Module, _Function, _Opts} ->
catch mod_disco:register_feature(Host, XMLNS); catch mod_disco:register_feature(Host, XMLNS);
{{XMLNS, Host}, _Module, _Function} -> {{XMLNS, Host}, _Module, _Function} ->
catch mod_disco:register_feature(Host, XMLNS); catch mod_disco:register_feature(Host, XMLNS);
_ -> _ -> ok
ok
end end
end, ets:tab2list(?IQTABLE)), end,
ets:tab2list(?IQTABLE)),
{noreply, State}; {noreply, State};
handle_info({timeout, _TRef, ID}, State) -> handle_info({timeout, _TRef, ID}, State) ->
process_iq_timeout(ID), process_iq_timeout(ID),
{noreply, State}; {noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void() %% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to %% Description: This function is called by a gen_server when it is about to
@ -283,46 +279,41 @@ handle_info(_Info, State) ->
%% cleaning up. When it returns, the gen_server terminates with Reason. %% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored. %% The return value is ignored.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed %% Description: Convert process state when code is changed
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%%% Internal functions %%% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, _State) -> ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
do_route(From, To, Packet) -> do_route(From, To, Packet) ->
?DEBUG("local route~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n", ?DEBUG("local route~n\tfrom ~p~n\tto ~p~n\tpacket "
"~P~n",
[From, To, Packet, 8]), [From, To, Packet, 8]),
if if To#jid.luser /= <<"">> ->
To#jid.luser /= "" ->
ejabberd_sm:route(From, To, Packet); ejabberd_sm:route(From, To, Packet);
To#jid.lresource == "" -> To#jid.lresource == <<"">> ->
{xmlelement, Name, _Attrs, _Els} = Packet, #xmlel{name = Name} = Packet,
case Name of case Name of
"iq" -> <<"iq">> -> process_iq(From, To, Packet);
process_iq(From, To, Packet); <<"message">> -> ok;
"message" -> <<"presence">> -> ok;
ok; _ -> ok
"presence" ->
ok;
_ ->
ok
end; end;
true -> true ->
{xmlelement, _Name, Attrs, _Els} = Packet, #xmlel{attrs = Attrs} = Packet,
case xml:get_attr_s("type", Attrs) of case xml:get_attr_s(<<"type">>, Attrs) of
"error" -> ok; <<"error">> -> ok;
"result" -> ok; <<"result">> -> ok;
_ -> _ ->
ejabberd_hooks:run(local_send_to_resource_hook, ejabberd_hooks:run(local_send_to_resource_hook,
To#jid.lserver, To#jid.lserver, [From, To, Packet])
[From, To, Packet])
end end
end. end.
@ -366,12 +357,6 @@ process_iq_timeout() ->
cancel_timer(TRef) -> cancel_timer(TRef) ->
case erlang:cancel_timer(TRef) of case erlang:cancel_timer(TRef) of
false -> false ->
receive receive {timeout, TRef, _} -> ok after 0 -> ok end;
{timeout, TRef, _} -> _ -> ok
ok
after 0 ->
ok
end;
_ ->
ok
end. end.

View File

@ -60,20 +60,20 @@ start_link() ->
join(Name) -> join(Name) ->
PG = {?MODULE, Name}, PG = {?MODULE, Name},
?PG2:create(PG), pg2:create(PG),
?PG2:join(PG, whereis(?MODULE)). pg2:join(PG, whereis(?MODULE)).
leave(Name) -> leave(Name) ->
PG = {?MODULE, Name}, PG = {?MODULE, Name},
?PG2:leave(PG, whereis(?MODULE)). pg2:leave(PG, whereis(?MODULE)).
get_members(Name) -> get_members(Name) ->
PG = {?MODULE, Name}, PG = {?MODULE, Name},
[node(P) || P <- ?PG2:get_members(PG)]. [node(P) || P <- pg2:get_members(PG)].
get_closest_node(Name) -> get_closest_node(Name) ->
PG = {?MODULE, Name}, PG = {?MODULE, Name},
node(?PG2:get_closest_pid(PG)). node(pg2:get_closest_pid(PG)).
%%==================================================================== %%====================================================================
%% gen_server callbacks %% gen_server callbacks
@ -88,7 +88,7 @@ get_closest_node(Name) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([]) -> init([]) ->
{FE, BE} = {FE, BE} =
case ejabberd_config:get_local_option(node_type) of case ejabberd_config:get_local_option(node_type, fun(N) -> N end) of
frontend -> frontend ->
{true, false}; {true, false};
backend -> backend ->

File diff suppressed because it is too large Load Diff

View File

@ -25,54 +25,51 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(ejabberd_rdbms). -module(ejabberd_rdbms).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-export([start/0]). -export([start/0]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
start() -> start() ->
%% Check if ejabberd has been compiled with ODBC
case catch ejabberd_odbc_sup:module_info() of case catch ejabberd_odbc_sup:module_info() of
{'EXIT', {undef, _}} -> {'EXIT', {undef, _}} ->
?INFO_MSG("ejabberd has not been compiled with relational database support. Skipping database startup.", []); ?INFO_MSG("ejabberd has not been compiled with "
_ -> "relational database support. Skipping "
%% If compiled with ODBC, start ODBC on the needed host "database startup.",
start_hosts() []);
_ -> start_hosts()
end. end.
%% Start relationnal DB module on the nodes where it is needed %% Start relationnal DB module on the nodes where it is needed
start_hosts() -> start_hosts() ->
lists:foreach( lists:foreach(fun (Host) ->
fun(Host) ->
case needs_odbc(Host) of case needs_odbc(Host) of
true -> start_odbc(Host); true -> start_odbc(Host);
false -> ok false -> ok
end end
end, ?MYHOSTS). end,
?MYHOSTS).
%% Start the ODBC module on the given host %% Start the ODBC module on the given host
start_odbc(Host) -> start_odbc(Host) ->
Supervisor_name = gen_mod:get_module_proc(Host, ejabberd_odbc_sup), Supervisor_name = gen_mod:get_module_proc(Host,
ChildSpec = ejabberd_odbc_sup),
{Supervisor_name, ChildSpec = {Supervisor_name,
{ejabberd_odbc_sup, start_link, [Host]}, {ejabberd_odbc_sup, start_link, [Host]}, transient,
transient, infinity, supervisor, [ejabberd_odbc_sup]},
infinity,
supervisor,
[ejabberd_odbc_sup]},
case supervisor:start_child(ejabberd_sup, ChildSpec) of case supervisor:start_child(ejabberd_sup, ChildSpec) of
{ok, _PID} -> {ok, _PID} -> ok;
ok;
_Error -> _Error ->
?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying...~n", [Supervisor_name, _Error]), ?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying."
"..~n",
[Supervisor_name, _Error]),
start_odbc(Host) start_odbc(Host)
end. end.
%% Returns true if we have configured odbc_server for the given host %% Returns true if we have configured odbc_server for the given host
needs_odbc(Host) -> needs_odbc(Host) ->
LHost = jlib:nameprep(Host), LHost = jlib:nameprep(Host),
case ejabberd_config:get_local_option({odbc_server, LHost}) of ejabberd_config:get_local_option(
undefined -> {odbc_server, LHost}, fun(_) -> true end, false).
false;
_ -> true
end.

View File

@ -25,6 +25,7 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(ejabberd_receiver). -module(ejabberd_receiver).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(gen_server). -behaviour(gen_server).
@ -41,18 +42,19 @@
close/1]). close/1]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2,
terminate/2, code_change/3]). handle_info/2, terminate/2, code_change/3]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-record(state, {socket, -record(state,
sock_mod, {socket :: inet:socket() | tls:tls_socket() | ejabberd_zlib:zlib_socket(),
shaper_state, sock_mod = gen_tcp :: gen_tcp | tls | ejabberd_zlib,
c2s_pid, shaper_state = none :: shaper:shaper(),
max_stanza_size, c2s_pid :: pid(),
xml_stream_state, max_stanza_size = infinity :: non_neg_integer() | infinity,
timeout}). xml_stream_state :: xml_stream:xml_stream_state(),
timeout = infinity:: timeout()}).
-define(HIBERNATE_TIMEOUT, 90000). -define(HIBERNATE_TIMEOUT, 90000).
@ -63,9 +65,16 @@
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error} %% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server %% Description: Starts the server
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec start_link(inet:socket(), atom(), shaper:shaper(),
non_neg_integer() | infinity) -> ignore |
{error, any()} |
{ok, pid()}.
start_link(Socket, SockMod, Shaper, MaxStanzaSize) -> start_link(Socket, SockMod, Shaper, MaxStanzaSize) ->
gen_server:start_link( gen_server:start_link(?MODULE,
?MODULE, [Socket, SockMod, Shaper, MaxStanzaSize], []). [Socket, SockMod, Shaper, MaxStanzaSize], []).
-spec start(inet:socket(), atom(), shaper:shaper()) -> undefined | pid().
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: start() -> {ok,Pid} | ignore | {error,Error} %% Function: start() -> {ok,Pid} | ignore | {error,Error}
@ -74,30 +83,46 @@ start_link(Socket, SockMod, Shaper, MaxStanzaSize) ->
start(Socket, SockMod, Shaper) -> start(Socket, SockMod, Shaper) ->
start(Socket, SockMod, Shaper, infinity). start(Socket, SockMod, Shaper, infinity).
-spec start(inet:socket(), atom(), shaper:shaper(),
non_neg_integer() | infinity) -> undefined | pid().
start(Socket, SockMod, Shaper, MaxStanzaSize) -> start(Socket, SockMod, Shaper, MaxStanzaSize) ->
{ok, Pid} = supervisor:start_child( {ok, Pid} =
ejabberd_receiver_sup, supervisor:start_child(ejabberd_receiver_sup,
[Socket, SockMod, Shaper, MaxStanzaSize]), [Socket, SockMod, Shaper, MaxStanzaSize]),
Pid. Pid.
-spec change_shaper(pid(), shaper:shaper()) -> ok.
change_shaper(Pid, Shaper) -> change_shaper(Pid, Shaper) ->
gen_server:cast(Pid, {change_shaper, Shaper}). gen_server:cast(Pid, {change_shaper, Shaper}).
reset_stream(Pid) -> -spec reset_stream(pid()) -> ok | {error, any()}.
do_call(Pid, reset_stream).
reset_stream(Pid) -> do_call(Pid, reset_stream).
-spec starttls(pid(), iodata()) -> {ok, tls:tls_socket()} | {error, any()}.
starttls(Pid, TLSSocket) -> starttls(Pid, TLSSocket) ->
do_call(Pid, {starttls, TLSSocket}). do_call(Pid, {starttls, TLSSocket}).
-spec compress(pid(), iodata() | undefined) -> {error, any()} |
{ok, ejabberd_zlib:zlib_socket()}.
compress(Pid, ZlibSocket) -> compress(Pid, ZlibSocket) ->
do_call(Pid, {compress, ZlibSocket}). do_call(Pid, {compress, ZlibSocket}).
-spec become_controller(pid(), pid()) -> ok | {error, any()}.
become_controller(Pid, C2SPid) -> become_controller(Pid, C2SPid) ->
do_call(Pid, {become_controller, C2SPid}). do_call(Pid, {become_controller, C2SPid}).
-spec close(pid()) -> ok.
close(Pid) -> close(Pid) ->
gen_server:cast(Pid, close). gen_server:cast(Pid, close).
%%==================================================================== %%====================================================================
%% gen_server callbacks %% gen_server callbacks
%%==================================================================== %%====================================================================
@ -112,16 +137,13 @@ close(Pid) ->
init([Socket, SockMod, Shaper, MaxStanzaSize]) -> init([Socket, SockMod, Shaper, MaxStanzaSize]) ->
ShaperState = shaper:new(Shaper), ShaperState = shaper:new(Shaper),
Timeout = case SockMod of Timeout = case SockMod of
ssl -> ssl -> 20;
20; _ -> infinity
_ ->
infinity
end, end,
{ok, #state{socket = Socket, {ok,
sock_mod = SockMod, #state{socket = Socket, sock_mod = SockMod,
shaper_state = ShaperState, shaper_state = ShaperState,
max_stanza_size = MaxStanzaSize, max_stanza_size = MaxStanzaSize, timeout = Timeout}}.
timeout = Timeout}}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
@ -137,11 +159,12 @@ handle_call({starttls, TLSSocket}, _From,
c2s_pid = C2SPid, c2s_pid = C2SPid,
max_stanza_size = MaxStanzaSize} = State) -> max_stanza_size = MaxStanzaSize} = State) ->
close_stream(XMLStreamState), close_stream(XMLStreamState),
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize), NewXMLStreamState = xml_stream:new(C2SPid,
MaxStanzaSize),
NewState = State#state{socket = TLSSocket, NewState = State#state{socket = TLSSocket,
sock_mod = tls, sock_mod = tls,
xml_stream_state = NewXMLStreamState}, xml_stream_state = NewXMLStreamState},
case tls:recv_data(TLSSocket, "") of case tls:recv_data(TLSSocket, <<"">>) of
{ok, TLSData} -> {ok, TLSData} ->
{reply, ok, process_data(TLSData, NewState), ?HIBERNATE_TIMEOUT}; {reply, ok, process_data(TLSData, NewState), ?HIBERNATE_TIMEOUT};
{error, _Reason} -> {error, _Reason} ->
@ -152,11 +175,12 @@ handle_call({compress, ZlibSocket}, _From,
c2s_pid = C2SPid, c2s_pid = C2SPid,
max_stanza_size = MaxStanzaSize} = State) -> max_stanza_size = MaxStanzaSize} = State) ->
close_stream(XMLStreamState), close_stream(XMLStreamState),
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize), NewXMLStreamState = xml_stream:new(C2SPid,
MaxStanzaSize),
NewState = State#state{socket = ZlibSocket, NewState = State#state{socket = ZlibSocket,
sock_mod = ejabberd_zlib, sock_mod = ejabberd_zlib,
xml_stream_state = NewXMLStreamState}, xml_stream_state = NewXMLStreamState},
case ejabberd_zlib:recv_data(ZlibSocket, "") of case ejabberd_zlib:recv_data(ZlibSocket, <<"">>) of
{ok, ZlibData} -> {ok, ZlibData} ->
{reply, ok, process_data(ZlibData, NewState), ?HIBERNATE_TIMEOUT}; {reply, ok, process_data(ZlibData, NewState), ?HIBERNATE_TIMEOUT};
{error, _Reason} -> {error, _Reason} ->
@ -164,12 +188,14 @@ handle_call({compress, ZlibSocket}, _From,
end; end;
handle_call(reset_stream, _From, handle_call(reset_stream, _From,
#state{xml_stream_state = XMLStreamState, #state{xml_stream_state = XMLStreamState,
c2s_pid = C2SPid, c2s_pid = C2SPid, max_stanza_size = MaxStanzaSize} =
max_stanza_size = MaxStanzaSize} = State) -> State) ->
close_stream(XMLStreamState), close_stream(XMLStreamState),
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize), NewXMLStreamState = xml_stream:new(C2SPid,
MaxStanzaSize),
Reply = ok, Reply = ok,
{reply, Reply, State#state{xml_stream_state = NewXMLStreamState}, {reply, Reply,
State#state{xml_stream_state = NewXMLStreamState},
?HIBERNATE_TIMEOUT}; ?HIBERNATE_TIMEOUT};
handle_call({become_controller, C2SPid}, _From, State) -> handle_call({become_controller, C2SPid}, _From, State) ->
XMLStreamState = xml_stream:new(C2SPid, State#state.max_stanza_size), XMLStreamState = xml_stream:new(C2SPid, State#state.max_stanza_size),
@ -179,8 +205,7 @@ handle_call({become_controller, C2SPid}, _From, State) ->
Reply = ok, Reply = ok,
{reply, Reply, NewState, ?HIBERNATE_TIMEOUT}; {reply, Reply, NewState, ?HIBERNATE_TIMEOUT};
handle_call(_Request, _From, State) -> handle_call(_Request, _From, State) ->
Reply = ok, Reply = ok, {reply, Reply, State, ?HIBERNATE_TIMEOUT}.
{reply, Reply, State, ?HIBERNATE_TIMEOUT}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} | %% Function: handle_cast(Msg, State) -> {noreply, State} |
@ -190,9 +215,9 @@ handle_call(_Request, _From, State) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_cast({change_shaper, Shaper}, State) -> handle_cast({change_shaper, Shaper}, State) ->
NewShaperState = shaper:new(Shaper), NewShaperState = shaper:new(Shaper),
{noreply, State#state{shaper_state = NewShaperState}, ?HIBERNATE_TIMEOUT}; {noreply, State#state{shaper_state = NewShaperState},
handle_cast(close, State) -> ?HIBERNATE_TIMEOUT};
{stop, normal, State}; handle_cast(close, State) -> {stop, normal, State};
handle_cast(_Msg, State) -> handle_cast(_Msg, State) ->
{noreply, State, ?HIBERNATE_TIMEOUT}. {noreply, State, ?HIBERNATE_TIMEOUT}.
@ -203,25 +228,23 @@ handle_cast(_Msg, State) ->
%% Description: Handling all non call/cast messages %% Description: Handling all non call/cast messages
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_info({Tag, _TCPSocket, Data}, handle_info({Tag, _TCPSocket, Data},
#state{socket = Socket, #state{socket = Socket, sock_mod = SockMod} = State)
sock_mod = SockMod} = State) when (Tag == tcp) or (Tag == ssl) or
when (Tag == tcp) or (Tag == ssl) or (Tag == ejabberd_xml) -> (Tag == ejabberd_xml) ->
case SockMod of case SockMod of
tls -> tls ->
case tls:recv_data(Socket, Data) of case tls:recv_data(Socket, Data) of
{ok, TLSData} -> {ok, TLSData} ->
{noreply, process_data(TLSData, State), {noreply, process_data(TLSData, State),
?HIBERNATE_TIMEOUT}; ?HIBERNATE_TIMEOUT};
{error, _Reason} -> {error, _Reason} -> {stop, normal, State}
{stop, normal, State}
end; end;
ejabberd_zlib -> ejabberd_zlib ->
case ejabberd_zlib:recv_data(Socket, Data) of case ejabberd_zlib:recv_data(Socket, Data) of
{ok, ZlibData} -> {ok, ZlibData} ->
{noreply, process_data(ZlibData, State), {noreply, process_data(ZlibData, State),
?HIBERNATE_TIMEOUT}; ?HIBERNATE_TIMEOUT};
{error, _Reason} -> {error, _Reason} -> {stop, normal, State}
{stop, normal, State}
end; end;
_ -> _ ->
{noreply, process_data(Data, State), ?HIBERNATE_TIMEOUT} {noreply, process_data(Data, State), ?HIBERNATE_TIMEOUT}
@ -232,16 +255,15 @@ handle_info({Tag, _TCPSocket}, State)
handle_info({Tag, _TCPSocket, Reason}, State) handle_info({Tag, _TCPSocket, Reason}, State)
when (Tag == tcp_error) or (Tag == ssl_error) -> when (Tag == tcp_error) or (Tag == ssl_error) ->
case Reason of case Reason of
timeout -> timeout -> {noreply, State, ?HIBERNATE_TIMEOUT};
{noreply, State, ?HIBERNATE_TIMEOUT}; _ -> {stop, normal, State}
_ ->
{stop, normal, State}
end; end;
handle_info({timeout, _Ref, activate}, State) -> handle_info({timeout, _Ref, activate}, State) ->
activate_socket(State), activate_socket(State),
{noreply, State, ?HIBERNATE_TIMEOUT}; {noreply, State, ?HIBERNATE_TIMEOUT};
handle_info(timeout, State) -> handle_info(timeout, State) ->
proc_lib:hibernate(gen_server, enter_loop, [?MODULE, [], State]), proc_lib:hibernate(gen_server, enter_loop,
[?MODULE, [], State]),
{noreply, State, ?HIBERNATE_TIMEOUT}; {noreply, State, ?HIBERNATE_TIMEOUT};
handle_info(_Info, State) -> handle_info(_Info, State) ->
{noreply, State, ?HIBERNATE_TIMEOUT}. {noreply, State, ?HIBERNATE_TIMEOUT}.
@ -253,14 +275,14 @@ handle_info(_Info, State) ->
%% cleaning up. When it returns, the gen_server terminates with Reason. %% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored. %% The return value is ignored.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
terminate(_Reason, #state{xml_stream_state = XMLStreamState, terminate(_Reason,
c2s_pid = C2SPid} = State) -> #state{xml_stream_state = XMLStreamState,
c2s_pid = C2SPid} =
State) ->
close_stream(XMLStreamState), close_stream(XMLStreamState),
if if C2SPid /= undefined ->
C2SPid /= undefined ->
gen_fsm:send_event(C2SPid, closed); gen_fsm:send_event(C2SPid, closed);
true -> true -> ok
ok
end, end,
catch (State#state.sock_mod):close(State#state.socket), catch (State#state.sock_mod):close(State#state.socket),
ok. ok.
@ -269,8 +291,7 @@ terminate(_Reason, #state{xml_stream_state = XMLStreamState,
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed %% Description: Convert process state when code is changed
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) -> {ok, State}.
{ok, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%%% Internal functions %%% Internal functions
@ -278,8 +299,7 @@ code_change(_OldVsn, State, _Extra) ->
activate_socket(#state{socket = Socket, activate_socket(#state{socket = Socket,
sock_mod = SockMod}) -> sock_mod = SockMod}) ->
PeerName = PeerName = case SockMod of
case SockMod of
gen_tcp -> gen_tcp ->
inet:setopts(Socket, [{active, once}]), inet:setopts(Socket, [{active, once}]),
inet:peername(Socket); inet:peername(Socket);
@ -288,38 +308,35 @@ activate_socket(#state{socket = Socket,
SockMod:peername(Socket) SockMod:peername(Socket)
end, end,
case PeerName of case PeerName of
{error, _Reason} -> {error, _Reason} -> self() ! {tcp_closed, Socket};
self() ! {tcp_closed, Socket}; {ok, _} -> ok
{ok, _} ->
ok
end. end.
%% Data processing for connectors directly generating xmlelement in %% Data processing for connectors directly generating xmlelement in
%% Erlang data structure. %% Erlang data structure.
%% WARNING: Shaper does not work with Erlang data structure. %% WARNING: Shaper does not work with Erlang data structure.
process_data([], State) -> process_data([], State) ->
activate_socket(State), activate_socket(State), State;
State; process_data([Element | Els],
process_data([Element|Els], #state{c2s_pid = C2SPid} = State) #state{c2s_pid = C2SPid} = State)
when element(1, Element) == xmlelement; when element(1, Element) == xmlel;
element(1, Element) == xmlstreamstart; element(1, Element) == xmlstreamstart;
element(1, Element) == xmlstreamelement; element(1, Element) == xmlstreamelement;
element(1, Element) == xmlstreamend -> element(1, Element) == xmlstreamend ->
if if C2SPid == undefined -> State;
C2SPid == undefined ->
State;
true -> true ->
catch gen_fsm:send_event(C2SPid, element_wrapper(Element)), catch gen_fsm:send_event(C2SPid,
element_wrapper(Element)),
process_data(Els, State) process_data(Els, State)
end; end;
%% Data processing for connectors receivind data as string. %% Data processing for connectors receivind data as string.
process_data(Data, process_data(Data,
#state{xml_stream_state = XMLStreamState, #state{xml_stream_state = XMLStreamState,
shaper_state = ShaperState, shaper_state = ShaperState, c2s_pid = C2SPid} =
c2s_pid = C2SPid} = State) -> State) ->
?DEBUG("Received XML on stream = ~p", [binary_to_list(Data)]), ?DEBUG("Received XML on stream = ~p", [(Data)]),
XMLStreamState1 = xml_stream:parse(XMLStreamState, Data), XMLStreamState1 = xml_stream:parse(XMLStreamState, Data),
{NewShaperState, Pause} = shaper:update(ShaperState, size(Data)), {NewShaperState, Pause} = shaper:update(ShaperState, byte_size(Data)),
if if
C2SPid == undefined -> C2SPid == undefined ->
ok; ok;
@ -336,20 +353,16 @@ process_data(Data,
%% speaking directly Erlang XML), we wrap it inside the same %% speaking directly Erlang XML), we wrap it inside the same
%% xmlstreamelement coming from the XML parser. %% xmlstreamelement coming from the XML parser.
element_wrapper(XMLElement) element_wrapper(XMLElement)
when element(1, XMLElement) == xmlelement -> when element(1, XMLElement) == xmlel ->
{xmlstreamelement, XMLElement}; {xmlstreamelement, XMLElement};
element_wrapper(Element) -> element_wrapper(Element) -> Element.
Element.
close_stream(undefined) -> close_stream(undefined) -> ok;
ok;
close_stream(XMLStreamState) -> close_stream(XMLStreamState) ->
xml_stream:close(XMLStreamState). xml_stream:close(XMLStreamState).
do_call(Pid, Msg) -> do_call(Pid, Msg) ->
case catch gen_server:call(Pid, Msg) of case catch gen_server:call(Pid, Msg) of
{'EXIT', Why} -> {'EXIT', Why} -> {error, Why};
{error, Why}; Res -> Res
Res ->
Res
end. end.

View File

@ -25,19 +25,22 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(ejabberd_regexp). -module(ejabberd_regexp).
-compile([export_all]). -compile([export_all]).
exec(ReM, ReF, ReA, RgM, RgF, RgA) -> exec({ReM, ReF, ReA}, {RgM, RgF, RgA}) ->
try apply(ReM, ReF, ReA) try apply(ReM, ReF, ReA) catch
catch error:undef -> apply(RgM, RgF, RgA);
error:undef -> A:B -> {error, {A, B}}
apply(RgM, RgF, RgA);
A:B ->
{error, {A, B}}
end. end.
-spec run(binary(), binary()) -> match | nomatch | {error, any()}.
run(String, Regexp) -> run(String, Regexp) ->
case exec(re, run, [String, Regexp, [{capture, none}]], regexp, first_match, [String, Regexp]) of case exec({re, run, [String, Regexp, [{capture, none}]]},
{regexp, first_match, [binary_to_list(String),
binary_to_list(Regexp)]})
of
{match, _, _} -> match; {match, _, _} -> match;
{match, _} -> match; {match, _} -> match;
match -> match; match -> match;
@ -45,28 +48,49 @@ run(String, Regexp) ->
{error, Error} -> {error, Error} {error, Error} -> {error, Error}
end. end.
-spec split(binary(), binary()) -> [binary()].
split(String, Regexp) -> split(String, Regexp) ->
case exec(re, split, [String, Regexp, [{return, list}]], regexp, split, [String, Regexp]) of case exec({re, split, [String, Regexp, [{return, binary}]]},
{ok, FieldList} -> FieldList; {regexp, split, [binary_to_list(String),
binary_to_list(Regexp)]})
of
{ok, FieldList} -> [iolist_to_binary(F) || F <- FieldList];
{error, Error} -> throw(Error); {error, Error} -> throw(Error);
A -> A A -> A
end. end.
-spec replace(binary(), binary(), binary()) -> binary().
replace(String, Regexp, New) -> replace(String, Regexp, New) ->
case exec(re, replace, [String, Regexp, New, [{return, list}]], regexp, sub, [String, Regexp, New]) of case exec({re, replace, [String, Regexp, New, [{return, binary}]]},
{ok, NewString, _RepCount} -> NewString; {regexp, sub, [binary_to_list(String),
binary_to_list(Regexp),
binary_to_list(New)]})
of
{ok, NewString, _RepCount} -> iolist_to_binary(NewString);
{error, Error} -> throw(Error); {error, Error} -> throw(Error);
A -> A A -> A
end. end.
-spec greplace(binary(), binary(), binary()) -> binary().
greplace(String, Regexp, New) -> greplace(String, Regexp, New) ->
case exec(re, replace, [String, Regexp, New, [global, {return, list}]], regexp, sub, [String, Regexp, New]) of case exec({re, replace, [String, Regexp, New, [global, {return, binary}]]},
{ok, NewString, _RepCount} -> NewString; {regexp, sub, [binary_to_list(String),
binary_to_list(Regexp),
binary_to_list(New)]})
of
{ok, NewString, _RepCount} -> iolist_to_binary(NewString);
{error, Error} -> throw(Error); {error, Error} -> throw(Error);
A -> A A -> A
end. end.
-spec sh_to_awk(binary()) -> binary().
sh_to_awk(ShRegExp) -> sh_to_awk(ShRegExp) ->
case exec(xmerl_regexp, sh_to_awk, [ShRegExp], regexp, sh_to_awk, [ShRegExp]) of case exec({xmerl_regexp, sh_to_awk, [binary_to_list(ShRegExp)]},
A -> A {regexp, sh_to_awk, [binary_to_list(ShRegExp)]})
of
A -> iolist_to_binary(A)
end. end.

View File

@ -25,6 +25,7 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(ejabberd_router). -module(ejabberd_router).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(gen_server). -behaviour(gen_server).
@ -44,13 +45,17 @@
-export([start_link/0]). -export([start_link/0]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2,
terminate/2, code_change/3]). handle_info/2, terminate/2, code_change/3]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl"). -include("jlib.hrl").
-type local_hint() :: undefined | integer() | {apply, atom(), atom()}.
-record(route, {domain, pid, local_hint}). -record(route, {domain, pid, local_hint}).
-record(state, {}). -record(state, {}).
%%==================================================================== %%====================================================================
@ -63,6 +68,7 @@
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec route(jid(), jid(), xmlel()) -> ok.
route(From, To, Packet) -> route(From, To, Packet) ->
case catch do_route(From, To, Packet) of case catch do_route(From, To, Packet) of
@ -75,29 +81,31 @@ route(From, To, Packet) ->
%% Route the error packet only if the originating packet is not an error itself. %% Route the error packet only if the originating packet is not an error itself.
%% RFC3920 9.3.1 %% RFC3920 9.3.1
-spec route_error(jid(), jid(), xmlel(), xmlel()) -> ok.
route_error(From, To, ErrPacket, OrigPacket) -> route_error(From, To, ErrPacket, OrigPacket) ->
{xmlelement, _Name, Attrs, _Els} = OrigPacket, #xmlel{attrs = Attrs} = OrigPacket,
case "error" == xml:get_attr_s("type", Attrs) of case <<"error">> == xml:get_attr_s(<<"type">>, Attrs) of
false -> false -> route(From, To, ErrPacket);
route(From, To, ErrPacket); true -> ok
true ->
ok
end. end.
-spec register_route(binary()) -> term().
register_route(Domain) -> register_route(Domain) ->
register_route(Domain, undefined). register_route(Domain, undefined).
-spec register_route(binary(), local_hint()) -> term().
register_route(Domain, LocalHint) -> register_route(Domain, LocalHint) ->
case jlib:nameprep(Domain) of case jlib:nameprep(Domain) of
error -> error -> erlang:error({invalid_domain, Domain});
erlang:error({invalid_domain, Domain});
LDomain -> LDomain ->
Pid = self(), Pid = self(),
case get_component_number(LDomain) of case get_component_number(LDomain) of
undefined -> undefined ->
F = fun () -> F = fun () ->
mnesia:write(#route{domain = LDomain, mnesia:write(#route{domain = LDomain, pid = Pid,
pid = Pid,
local_hint = LocalHint}) local_hint = LocalHint})
end, end,
mnesia:transaction(F); mnesia:transaction(F);
@ -105,89 +113,99 @@ register_route(Domain, LocalHint) ->
F = fun () -> F = fun () ->
case mnesia:wread({route, LDomain}) of case mnesia:wread({route, LDomain}) of
[] -> [] ->
mnesia:write( mnesia:write(#route{domain = LDomain,
#route{domain = LDomain,
pid = Pid, pid = Pid,
local_hint = 1}), local_hint = 1}),
lists:foreach( lists:foreach(fun (I) ->
fun(I) -> mnesia:write(#route{domain
mnesia:write( =
#route{domain = LDomain, LDomain,
pid = undefined, pid
local_hint = I}) =
end, lists:seq(2, N)); undefined,
local_hint
=
I})
end,
lists:seq(2, N));
Rs -> Rs ->
lists:any( lists:any(fun (#route{pid = undefined,
fun(#route{pid = undefined, local_hint = I} =
local_hint = I} = R) -> R) ->
mnesia:write( mnesia:write(#route{domain =
#route{domain = LDomain, LDomain,
pid = Pid, pid =
local_hint = I}), Pid,
local_hint
=
I}),
mnesia:delete_object(R), mnesia:delete_object(R),
true; true;
(_) -> (_) -> false
false end,
end, Rs) Rs)
end end
end, end,
mnesia:transaction(F) mnesia:transaction(F)
end end
end. end.
-spec register_routes([binary()]) -> ok.
register_routes(Domains) -> register_routes(Domains) ->
lists:foreach(fun(Domain) -> lists:foreach(fun (Domain) -> register_route(Domain)
register_route(Domain) end,
end, Domains). Domains).
-spec unregister_route(binary()) -> term().
unregister_route(Domain) -> unregister_route(Domain) ->
case jlib:nameprep(Domain) of case jlib:nameprep(Domain) of
error -> error -> erlang:error({invalid_domain, Domain});
erlang:error({invalid_domain, Domain});
LDomain -> LDomain ->
Pid = self(), Pid = self(),
case get_component_number(LDomain) of case get_component_number(LDomain) of
undefined -> undefined ->
F = fun () -> F = fun () ->
case mnesia:match_object( case mnesia:match_object(#route{domain = LDomain,
#route{domain = LDomain, pid = Pid, _ = '_'})
pid = Pid, of
_ = '_'}) of [R] -> mnesia:delete_object(R);
[R] -> _ -> ok
mnesia:delete_object(R);
_ ->
ok
end end
end, end,
mnesia:transaction(F); mnesia:transaction(F);
_ -> _ ->
F = fun () -> F = fun () ->
case mnesia:match_object(#route{domain = LDomain, case mnesia:match_object(#route{domain = LDomain,
pid = Pid, pid = Pid, _ = '_'})
_ = '_'}) of of
[R] -> [R] ->
I = R#route.local_hint, I = R#route.local_hint,
mnesia:write( mnesia:write(#route{domain = LDomain,
#route{domain = LDomain,
pid = undefined, pid = undefined,
local_hint = I}), local_hint = I}),
mnesia:delete_object(R); mnesia:delete_object(R);
_ -> _ -> ok
ok
end end
end, end,
mnesia:transaction(F) mnesia:transaction(F)
end end
end. end.
unregister_routes(Domains) -> -spec unregister_routes([binary()]) -> ok.
lists:foreach(fun(Domain) ->
unregister_route(Domain)
end, Domains).
unregister_routes(Domains) ->
lists:foreach(fun (Domain) -> unregister_route(Domain)
end,
Domains).
-spec dirty_get_all_routes() -> [binary()].
dirty_get_all_routes() -> dirty_get_all_routes() ->
lists:usort(mnesia:dirty_all_keys(route)) -- ?MYHOSTS. lists:usort(mnesia:dirty_all_keys(route)) -- (?MYHOSTS).
-spec dirty_get_all_domains() -> [binary()].
dirty_get_all_domains() -> dirty_get_all_domains() ->
lists:usort(mnesia:dirty_all_keys(route)). lists:usort(mnesia:dirty_all_keys(route)).
@ -207,17 +225,14 @@ dirty_get_all_domains() ->
init([]) -> init([]) ->
update_tables(), update_tables(),
mnesia:create_table(route, mnesia:create_table(route,
[{ram_copies, [node()]}, [{ram_copies, [node()]}, {type, bag},
{type, bag}, {attributes, record_info(fields, route)}]),
{attributes,
record_info(fields, route)}]),
mnesia:add_table_copy(route, node(), ram_copies), mnesia:add_table_copy(route, node(), ram_copies),
mnesia:subscribe({table, route, simple}), mnesia:subscribe({table, route, simple}),
lists:foreach( lists:foreach(fun (Pid) -> erlang:monitor(process, Pid)
fun(Pid) ->
erlang:monitor(process, Pid)
end, end,
mnesia:dirty_select(route, [{{route, '_', '$1', '_'}, [], ['$1']}])), mnesia:dirty_select(route,
[{{route, '_', '$1', '_'}, [], ['$1']}])),
{ok, #state{}}. {ok, #state{}}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -230,8 +245,7 @@ init([]) ->
%% Description: Handling call messages %% Description: Handling call messages
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_call(_Request, _From, State) -> handle_call(_Request, _From, State) ->
Reply = ok, Reply = ok, {reply, Reply, State}.
{reply, Reply, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} | %% Function: handle_cast(Msg, State) -> {noreply, State} |
@ -239,8 +253,7 @@ handle_call(_Request, _From, State) ->
%% {stop, Reason, State} %% {stop, Reason, State}
%% Description: Handling cast messages %% Description: Handling cast messages
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_cast(_Msg, State) -> handle_cast(_Msg, State) -> {noreply, State}.
{noreply, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} | %% Function: handle_info(Info, State) -> {noreply, State} |
@ -253,36 +266,32 @@ handle_info({route, From, To, Packet}, State) ->
{'EXIT', Reason} -> {'EXIT', Reason} ->
?ERROR_MSG("~p~nwhen processing: ~p", ?ERROR_MSG("~p~nwhen processing: ~p",
[Reason, {From, To, Packet}]); [Reason, {From, To, Packet}]);
_ -> _ -> ok
ok
end, end,
{noreply, State}; {noreply, State};
handle_info({mnesia_table_event, {write, #route{pid = Pid}, _ActivityId}}, handle_info({mnesia_table_event,
{write, #route{pid = Pid}, _ActivityId}},
State) -> State) ->
erlang:monitor(process, Pid), erlang:monitor(process, Pid), {noreply, State};
{noreply, State};
handle_info({'DOWN', _Ref, _Type, Pid, _Info}, State) -> handle_info({'DOWN', _Ref, _Type, Pid, _Info}, State) ->
F = fun () -> F = fun () ->
Es = mnesia:select( Es = mnesia:select(route,
route, [{#route{pid = Pid, _ = '_'}, [], ['$_']}]),
[{#route{pid = Pid, _ = '_'}, lists:foreach(fun (E) ->
[], if is_integer(E#route.local_hint) ->
['$_']}]),
lists:foreach(
fun(E) ->
if
is_integer(E#route.local_hint) ->
LDomain = E#route.domain, LDomain = E#route.domain,
I = E#route.local_hint, I = E#route.local_hint,
mnesia:write( mnesia:write(#route{domain =
#route{domain = LDomain, LDomain,
pid = undefined, pid =
local_hint = I}), undefined,
local_hint =
I}),
mnesia:delete_object(E); mnesia:delete_object(E);
true -> true -> mnesia:delete_object(E)
mnesia:delete_object(E)
end end
end, Es) end,
Es)
end, end,
mnesia:transaction(F), mnesia:transaction(F),
{noreply, State}; {noreply, State};
@ -310,107 +319,93 @@ code_change(_OldVsn, State, _Extra) ->
%%% Internal functions %%% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
do_route(OrigFrom, OrigTo, OrigPacket) -> do_route(OrigFrom, OrigTo, OrigPacket) ->
?DEBUG("route~n\tfrom ~p~n\tto ~p~n\tpacket ~p~n", ?DEBUG("route~n\tfrom ~p~n\tto ~p~n\tpacket "
"~p~n",
[OrigFrom, OrigTo, OrigPacket]), [OrigFrom, OrigTo, OrigPacket]),
case ejabberd_hooks:run_fold(filter_packet, case ejabberd_hooks:run_fold(filter_packet,
{OrigFrom, OrigTo, OrigPacket}, []) of {OrigFrom, OrigTo, OrigPacket}, [])
of
{From, To, Packet} -> {From, To, Packet} ->
LDstDomain = To#jid.lserver, LDstDomain = To#jid.lserver,
case mnesia:dirty_read(route, LDstDomain) of case mnesia:dirty_read(route, LDstDomain) of
[] -> [] -> ejabberd_s2s:route(From, To, Packet);
ejabberd_s2s:route(From, To, Packet);
[R] -> [R] ->
Pid = R#route.pid, Pid = R#route.pid,
if if node(Pid) == node() ->
node(Pid) == node() ->
case R#route.local_hint of case R#route.local_hint of
{apply, Module, Function} -> {apply, Module, Function} ->
Module:Function(From, To, Packet); Module:Function(From, To, Packet);
_ -> _ -> Pid ! {route, From, To, Packet}
Pid ! {route, From, To, Packet}
end; end;
is_pid(Pid) -> is_pid(Pid) -> Pid ! {route, From, To, Packet};
Pid ! {route, From, To, Packet}; true -> drop
true ->
drop
end; end;
Rs -> Rs ->
Value = case ejabberd_config:get_local_option( Value = case
{domain_balancing, LDstDomain}) of ejabberd_config:get_local_option({domain_balancing,
LDstDomain}, fun(D) when is_atom(D) -> D end)
of
undefined -> now(); undefined -> now();
random -> now(); random -> now();
source -> jlib:jid_tolower(From); source -> jlib:jid_tolower(From);
destination -> jlib:jid_tolower(To); destination -> jlib:jid_tolower(To);
bare_source -> bare_source ->
jlib:jid_remove_resource( jlib:jid_remove_resource(jlib:jid_tolower(From));
jlib:jid_tolower(From));
bare_destination -> bare_destination ->
jlib:jid_remove_resource( jlib:jid_remove_resource(jlib:jid_tolower(To))
jlib:jid_tolower(To))
end, end,
case get_component_number(LDstDomain) of case get_component_number(LDstDomain) of
undefined -> undefined ->
case [R || R <- Rs, node(R#route.pid) == node()] of case [R || R <- Rs, node(R#route.pid) == node()] of
[] -> [] ->
R = lists:nth(erlang:phash(Value, length(Rs)), Rs), R = lists:nth(erlang:phash(Value, str:len(Rs)), Rs),
Pid = R#route.pid, Pid = R#route.pid,
if if is_pid(Pid) -> Pid ! {route, From, To, Packet};
is_pid(Pid) -> true -> drop
Pid ! {route, From, To, Packet};
true ->
drop
end; end;
LRs -> LRs ->
R = lists:nth(erlang:phash(Value, length(LRs)), LRs), R = lists:nth(erlang:phash(Value, str:len(LRs)),
LRs),
Pid = R#route.pid, Pid = R#route.pid,
case R#route.local_hint of case R#route.local_hint of
{apply, Module, Function} -> {apply, Module, Function} ->
Module:Function(From, To, Packet); Module:Function(From, To, Packet);
_ -> _ -> Pid ! {route, From, To, Packet}
Pid ! {route, From, To, Packet}
end end
end; end;
_ -> _ ->
SRs = lists:ukeysort(#route.local_hint, Rs), SRs = lists:ukeysort(#route.local_hint, Rs),
R = lists:nth(erlang:phash(Value, length(SRs)), SRs), R = lists:nth(erlang:phash(Value, str:len(SRs)), SRs),
Pid = R#route.pid, Pid = R#route.pid,
if if is_pid(Pid) -> Pid ! {route, From, To, Packet};
is_pid(Pid) -> true -> drop
Pid ! {route, From, To, Packet};
true ->
drop
end end
end end
end; end;
drop -> drop -> ok
ok
end. end.
get_component_number(LDomain) -> get_component_number(LDomain) ->
case ejabberd_config:get_local_option( case
{domain_balancing_component_number, LDomain}) of ejabberd_config:get_local_option({domain_balancing_component_number,
N when is_integer(N), LDomain}, fun(D) -> D end)
N > 1 -> of
N; N when is_integer(N), N > 1 -> N;
_ -> _ -> undefined
undefined
end. end.
update_tables() -> update_tables() ->
case catch mnesia:table_info(route, attributes) of case catch mnesia:table_info(route, attributes) of
[domain, node, pid] -> [domain, node, pid] -> mnesia:delete_table(route);
mnesia:delete_table(route); [domain, pid] -> mnesia:delete_table(route);
[domain, pid] -> [domain, pid, local_hint] -> ok;
mnesia:delete_table(route); {'EXIT', _} -> ok
[domain, pid, local_hint] ->
ok;
{'EXIT', _} ->
ok
end, end,
case lists:member(local_route, mnesia:system_info(tables)) of case lists:member(local_route,
true -> mnesia:system_info(tables))
mnesia:delete_table(local_route); of
false -> true -> mnesia:delete_table(local_route);
ok false -> ok
end. end.

View File

@ -25,49 +25,52 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(ejabberd_s2s). -module(ejabberd_s2s).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(gen_server). -behaviour(gen_server).
%% API %% API
-export([start_link/0, -export([start_link/0, route/3, have_connection/1,
route/3, has_key/2, get_connections_pids/1, try_register/1,
have_connection/1, remove_connection/3, find_connection/2,
has_key/2, dirty_get_connections/0, allow_host/2,
get_connections_pids/1, incoming_s2s_number/0, outgoing_s2s_number/0,
try_register/1,
remove_connection/3,
find_connection/2,
dirty_get_connections/0,
allow_host/2,
incoming_s2s_number/0,
outgoing_s2s_number/0,
clean_temporarily_blocked_table/0, clean_temporarily_blocked_table/0,
list_temporarily_blocked_hosts/0, list_temporarily_blocked_hosts/0,
external_host_overloaded/1, external_host_overloaded/1, is_temporarly_blocked/1]).
is_temporarly_blocked/1
]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2,
terminate/2, code_change/3]). handle_info/2, terminate/2, code_change/3]).
%% ejabberd API %% ejabberd API
-export([get_info_s2s_connections/1]). -export([get_info_s2s_connections/1]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl"). -include("jlib.hrl").
-include("ejabberd_commands.hrl"). -include("ejabberd_commands.hrl").
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER, 1). -define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER, 1).
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE, 1). -define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE, 1).
-define(S2S_OVERLOAD_BLOCK_PERIOD, 60). -define(S2S_OVERLOAD_BLOCK_PERIOD, 60).
%% once a server is temporarly blocked, it stay blocked for 60 seconds %% once a server is temporarly blocked, it stay blocked for 60 seconds
-record(s2s, {fromto, pid, key}). -record(s2s, {fromto = {<<"">>, <<"">>} :: {binary(), binary()},
pid = self() :: pid() | '_',
key = <<"">> :: binary() | '_'}).
-record(state, {}). -record(state, {}).
-record(temporarily_blocked, {host, timestamp}). -record(temporarily_blocked, {host = <<"">> :: binary(),
timestamp = now() :: erlang:timestamp()}).
-type temporarily_blocked() :: #temporarily_blocked{}.
%%==================================================================== %%====================================================================
%% API %% API
@ -77,57 +80,73 @@
%% Description: Starts the server %% Description: Starts the server
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [],
[]).
-spec route(jid(), jid(), xmlel()) -> ok.
route(From, To, Packet) -> route(From, To, Packet) ->
case catch do_route(From, To, Packet) of case catch do_route(From, To, Packet) of
{'EXIT', Reason} -> {'EXIT', Reason} ->
?ERROR_MSG("~p~nwhen processing: ~p", ?ERROR_MSG("~p~nwhen processing: ~p",
[Reason, {From, To, Packet}]); [Reason, {From, To, Packet}]);
_ -> _ -> ok
ok
end. end.
clean_temporarily_blocked_table() -> clean_temporarily_blocked_table() ->
mnesia:clear_table(temporarily_blocked). mnesia:clear_table(temporarily_blocked).
-spec list_temporarily_blocked_hosts() -> [temporarily_blocked()].
list_temporarily_blocked_hosts() -> list_temporarily_blocked_hosts() ->
ets:tab2list(temporarily_blocked). ets:tab2list(temporarily_blocked).
-spec external_host_overloaded(binary()) -> {aborted, any()} | {atomic, ok}.
external_host_overloaded(Host) -> external_host_overloaded(Host) ->
?INFO_MSG("Disabling connections from ~s for ~p seconds", [Host, ?S2S_OVERLOAD_BLOCK_PERIOD]), ?INFO_MSG("Disabling connections from ~s for ~p "
"seconds",
[Host, ?S2S_OVERLOAD_BLOCK_PERIOD]),
mnesia:transaction(fun () -> mnesia:transaction(fun () ->
mnesia:write(#temporarily_blocked{host = Host, timestamp = now()}) mnesia:write(#temporarily_blocked{host = Host,
timestamp =
now()})
end). end).
-spec is_temporarly_blocked(binary()) -> boolean().
is_temporarly_blocked(Host) -> is_temporarly_blocked(Host) ->
case mnesia:dirty_read(temporarily_blocked, Host) of case mnesia:dirty_read(temporarily_blocked, Host) of
[] -> false; [] -> false;
[#temporarily_blocked{timestamp = T} = Entry] -> [#temporarily_blocked{timestamp = T} = Entry] ->
case timer:now_diff(now(), T) of case timer:now_diff(now(), T) of
N when N > ?S2S_OVERLOAD_BLOCK_PERIOD * 1000 * 1000 -> N when N > (?S2S_OVERLOAD_BLOCK_PERIOD) * 1000 * 1000 ->
mnesia:dirty_delete_object(Entry), mnesia:dirty_delete_object(Entry), false;
false; _ -> true
_ ->
true
end end
end. end.
-spec remove_connection({binary(), binary()},
pid(), binary()) -> {atomic, ok} |
ok |
{aborted, any()}.
remove_connection(FromTo, Pid, Key) -> remove_connection(FromTo, Pid, Key) ->
case catch mnesia:dirty_match_object(s2s, #s2s{fromto = FromTo, case catch mnesia:dirty_match_object(s2s,
pid = Pid, #s2s{fromto = FromTo, pid = Pid,
_ = '_'}) of _ = '_'})
of
[#s2s{pid = Pid, key = Key}] -> [#s2s{pid = Pid, key = Key}] ->
F = fun () -> F = fun () ->
mnesia:delete_object(#s2s{fromto = FromTo, mnesia:delete_object(#s2s{fromto = FromTo, pid = Pid,
pid = Pid,
key = Key}) key = Key})
end, end,
mnesia:transaction(F); mnesia:transaction(F);
_ -> _ -> ok
ok
end. end.
-spec have_connection({binary(), binary()}) -> boolean().
have_connection(FromTo) -> have_connection(FromTo) ->
case catch mnesia:dirty_read(s2s, FromTo) of case catch mnesia:dirty_read(s2s, FromTo) of
[_] -> [_] ->
@ -136,6 +155,8 @@ have_connection(FromTo) ->
false false
end. end.
-spec has_key({binary(), binary()}, binary()) -> boolean().
has_key(FromTo, Key) -> has_key(FromTo, Key) ->
case mnesia:dirty_select(s2s, case mnesia:dirty_select(s2s,
[{#s2s{fromto = FromTo, key = Key, _ = '_'}, [{#s2s{fromto = FromTo, key = Key, _ = '_'},
@ -147,6 +168,8 @@ has_key(FromTo, Key) ->
true true
end. end.
-spec get_connections_pids({binary(), binary()}) -> [pid()].
get_connections_pids(FromTo) -> get_connections_pids(FromTo) ->
case catch mnesia:dirty_read(s2s, FromTo) of case catch mnesia:dirty_read(s2s, FromTo) of
L when is_list(L) -> L when is_list(L) ->
@ -155,6 +178,8 @@ get_connections_pids(FromTo) ->
[] []
end. end.
-spec try_register({binary(), binary()}) -> {key, binary()} | false.
try_register(FromTo) -> try_register(FromTo) ->
Key = randoms:get_string(), Key = randoms:get_string(),
MaxS2SConnectionsNumber = max_s2s_connections_number(FromTo), MaxS2SConnectionsNumber = max_s2s_connections_number(FromTo),
@ -162,26 +187,23 @@ try_register(FromTo) ->
max_s2s_connections_number_per_node(FromTo), max_s2s_connections_number_per_node(FromTo),
F = fun () -> F = fun () ->
L = mnesia:read({s2s, FromTo}), L = mnesia:read({s2s, FromTo}),
NeededConnections = needed_connections_number( NeededConnections = needed_connections_number(L,
L, MaxS2SConnectionsNumber, MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode), MaxS2SConnectionsNumberPerNode),
if if NeededConnections > 0 ->
NeededConnections > 0 -> mnesia:write(#s2s{fromto = FromTo, pid = self(),
mnesia:write(#s2s{fromto = FromTo,
pid = self(),
key = Key}), key = Key}),
{key, Key}; {key, Key};
true -> true -> false
false
end end
end, end,
case mnesia:transaction(F) of case mnesia:transaction(F) of
{atomic, Res} -> {atomic, Res} -> Res;
Res; _ -> false
_ ->
false
end. end.
-spec dirty_get_connections() -> [{binary(), binary()}].
dirty_get_connections() -> dirty_get_connections() ->
mnesia:dirty_all_keys(s2s). mnesia:dirty_all_keys(s2s).
@ -242,12 +264,10 @@ handle_info({route, From, To, Packet}, State) ->
{'EXIT', Reason} -> {'EXIT', Reason} ->
?ERROR_MSG("~p~nwhen processing: ~p", ?ERROR_MSG("~p~nwhen processing: ~p",
[Reason, {From, To, Packet}]); [Reason, {From, To, Packet}]);
_ -> _ -> ok
ok
end, end,
{noreply, State}; {noreply, State};
handle_info(_Info, State) -> handle_info(_Info, State) -> {noreply, State}.
{noreply, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void() %% Function: terminate(Reason, State) -> void()
@ -284,72 +304,75 @@ clean_table_from_bad_node(Node) ->
mnesia:async_dirty(F). mnesia:async_dirty(F).
do_route(From, To, Packet) -> do_route(From, To, Packet) ->
?DEBUG("s2s manager~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n", ?DEBUG("s2s manager~n\tfrom ~p~n\tto ~p~n\tpacket "
"~P~n",
[From, To, Packet, 8]), [From, To, Packet, 8]),
case find_connection(From, To) of case find_connection(From, To) of
{atomic, Pid} when is_pid(Pid) -> {atomic, Pid} when is_pid(Pid) ->
?DEBUG("sending to process ~p~n", [Pid]), ?DEBUG("sending to process ~p~n", [Pid]),
{xmlelement, Name, Attrs, Els} = Packet, #xmlel{name = Name, attrs = Attrs, children = Els} =
NewAttrs = jlib:replace_from_to_attrs(jlib:jid_to_string(From), Packet,
jlib:jid_to_string(To), NewAttrs =
Attrs), jlib:replace_from_to_attrs(jlib:jid_to_string(From),
jlib:jid_to_string(To), Attrs),
#jid{lserver = MyServer} = From, #jid{lserver = MyServer} = From,
ejabberd_hooks:run( ejabberd_hooks:run(s2s_send_packet, MyServer,
s2s_send_packet,
MyServer,
[From, To, Packet]), [From, To, Packet]),
send_element(Pid, {xmlelement, Name, NewAttrs, Els}), send_element(Pid,
#xmlel{name = Name, attrs = NewAttrs, children = Els}),
ok; ok;
{aborted, _Reason} -> {aborted, _Reason} ->
case xml:get_tag_attr_s("type", Packet) of case xml:get_tag_attr_s(<<"type">>, Packet) of
"error" -> ok; <<"error">> -> ok;
"result" -> ok; <<"result">> -> ok;
_ -> _ ->
Err = jlib:make_error_reply( Err = jlib:make_error_reply(Packet,
Packet, ?ERR_SERVICE_UNAVAILABLE), ?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(To, From, Err) ejabberd_router:route(To, From, Err)
end, end,
false false
end. end.
-spec find_connection(jid(), jid()) -> {aborted, any()} | {atomic, pid()}.
find_connection(From, To) -> find_connection(From, To) ->
#jid{lserver = MyServer} = From, #jid{lserver = MyServer} = From,
#jid{lserver = Server} = To, #jid{lserver = Server} = To,
FromTo = {MyServer, Server}, FromTo = {MyServer, Server},
MaxS2SConnectionsNumber = max_s2s_connections_number(FromTo), MaxS2SConnectionsNumber =
max_s2s_connections_number(FromTo),
MaxS2SConnectionsNumberPerNode = MaxS2SConnectionsNumberPerNode =
max_s2s_connections_number_per_node(FromTo), max_s2s_connections_number_per_node(FromTo),
?DEBUG("Finding connection for ~p~n", [FromTo]), ?DEBUG("Finding connection for ~p~n", [FromTo]),
case catch mnesia:dirty_read(s2s, FromTo) of case catch mnesia:dirty_read(s2s, FromTo) of
{'EXIT', Reason} -> {'EXIT', Reason} -> {aborted, Reason};
{aborted, Reason};
[] -> [] ->
%% We try to establish all the connections if the host is not a %% We try to establish all the connections if the host is not a
%% service and if the s2s host is not blacklisted or %% service and if the s2s host is not blacklisted or
%% is in whitelist: %% is in whitelist:
case not is_service(From, To) andalso allow_host(MyServer, Server) of case not is_service(From, To) andalso
allow_host(MyServer, Server)
of
true -> true ->
NeededConnections = needed_connections_number( NeededConnections = needed_connections_number([],
[], MaxS2SConnectionsNumber, MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode), MaxS2SConnectionsNumberPerNode),
open_several_connections( open_several_connections(NeededConnections, MyServer,
NeededConnections, MyServer,
Server, From, FromTo, Server, From, FromTo,
MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode); MaxS2SConnectionsNumber,
false -> MaxS2SConnectionsNumberPerNode);
{aborted, error} false -> {aborted, error}
end; end;
L when is_list(L) -> L when is_list(L) ->
NeededConnections = needed_connections_number( NeededConnections = needed_connections_number(L,
L, MaxS2SConnectionsNumber, MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode), MaxS2SConnectionsNumberPerNode),
if if NeededConnections > 0 ->
NeededConnections > 0 ->
%% We establish the missing connections for this pair. %% We establish the missing connections for this pair.
open_several_connections( open_several_connections(NeededConnections, MyServer,
NeededConnections, MyServer,
Server, From, FromTo, Server, From, FromTo,
MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode); MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode);
true -> true ->
%% We choose a connexion from the pool of opened ones. %% We choose a connexion from the pool of opened ones.
{atomic, choose_connection(From, L)} {atomic, choose_connection(From, L)}
@ -364,26 +387,23 @@ choose_pid(From, Pids) ->
[] -> Pids; [] -> Pids;
Ps -> Ps Ps -> Ps
end, end,
% Use sticky connections based on the JID of the sender (whithout Pid =
% the resource to ensure that a muc room always uses the same lists:nth(erlang:phash(jlib:jid_remove_resource(From),
% connection) length(Pids1)),
Pid = lists:nth(erlang:phash(jlib:jid_remove_resource(From), length(Pids1)),
Pids1), Pids1),
?DEBUG("Using ejabberd_s2s_out ~p~n", [Pid]), ?DEBUG("Using ejabberd_s2s_out ~p~n", [Pid]),
Pid. Pid.
open_several_connections(N, MyServer, Server, From, FromTo, open_several_connections(N, MyServer, Server, From,
MaxS2SConnectionsNumber, FromTo, MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode) -> MaxS2SConnectionsNumberPerNode) ->
ConnectionsResult = ConnectionsResult = [new_connection(MyServer, Server,
[new_connection(MyServer, Server, From, FromTo, From, FromTo, MaxS2SConnectionsNumber,
MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode) MaxS2SConnectionsNumberPerNode)
|| _N <- lists:seq(1, N)], || _N <- lists:seq(1, N)],
case [PID || {atomic, PID} <- ConnectionsResult] of case [PID || {atomic, PID} <- ConnectionsResult] of
[] -> [] -> hd(ConnectionsResult);
hd(ConnectionsResult); PIDs -> {atomic, choose_pid(From, PIDs)}
PIDs ->
{atomic, choose_pid(From, PIDs)}
end. end.
new_connection(MyServer, Server, From, FromTo, new_connection(MyServer, Server, From, FromTo,
@ -393,39 +413,36 @@ new_connection(MyServer, Server, From, FromTo,
MyServer, Server, {new, Key}), MyServer, Server, {new, Key}),
F = fun() -> F = fun() ->
L = mnesia:read({s2s, FromTo}), L = mnesia:read({s2s, FromTo}),
NeededConnections = needed_connections_number( NeededConnections = needed_connections_number(L,
L, MaxS2SConnectionsNumber, MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode), MaxS2SConnectionsNumberPerNode),
if if NeededConnections > 0 ->
NeededConnections > 0 -> mnesia:write(#s2s{fromto = FromTo, pid = Pid,
mnesia:write(#s2s{fromto = FromTo,
pid = Pid,
key = Key}), key = Key}),
?INFO_MSG("New s2s connection started ~p", [Pid]), ?INFO_MSG("New s2s connection started ~p", [Pid]),
Pid; Pid;
true -> true -> choose_connection(From, L)
choose_connection(From, L)
end end
end, end,
TRes = mnesia:transaction(F), TRes = mnesia:transaction(F),
case TRes of case TRes of
{atomic, Pid} -> {atomic, Pid} -> ejabberd_s2s_out:start_connection(Pid);
ejabberd_s2s_out:start_connection(Pid); _ -> ejabberd_s2s_out:stop_connection(Pid)
_ ->
ejabberd_s2s_out:stop_connection(Pid)
end, end,
TRes. TRes.
max_s2s_connections_number({From, To}) -> max_s2s_connections_number({From, To}) ->
case acl:match_rule( case acl:match_rule(From, max_s2s_connections,
From, max_s2s_connections, jlib:make_jid("", To, "")) of jlib:make_jid(<<"">>, To, <<"">>))
of
Max when is_integer(Max) -> Max; Max when is_integer(Max) -> Max;
_ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER _ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER
end. end.
max_s2s_connections_number_per_node({From, To}) -> max_s2s_connections_number_per_node({From, To}) ->
case acl:match_rule( case acl:match_rule(From, max_s2s_connections_per_node,
From, max_s2s_connections_per_node, jlib:make_jid("", To, "")) of jlib:make_jid(<<"">>, To, <<"">>))
of
Max when is_integer(Max) -> Max; Max when is_integer(Max) -> Max;
_ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE _ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE
end. end.
@ -443,45 +460,46 @@ needed_connections_number(Ls, MaxS2SConnectionsNumber,
%% -------------------------------------------------------------------- %% --------------------------------------------------------------------
is_service(From, To) -> is_service(From, To) ->
LFromDomain = From#jid.lserver, LFromDomain = From#jid.lserver,
case ejabberd_config:get_local_option({route_subdomains, LFromDomain}) of case ejabberd_config:get_local_option(
{route_subdomains, LFromDomain},
fun(s2s) -> s2s end) of
s2s -> % bypass RFC 3920 10.3 s2s -> % bypass RFC 3920 10.3
false; false;
_ -> undefined ->
Hosts = ?MYHOSTS, Hosts = (?MYHOSTS),
P = fun(ParentDomain) -> lists:member(ParentDomain, Hosts) end, P = fun (ParentDomain) ->
lists:member(ParentDomain, Hosts)
end,
lists:any(P, parent_domains(To#jid.lserver)) lists:any(P, parent_domains(To#jid.lserver))
end. end.
parent_domains(Domain) -> parent_domains(Domain) ->
lists:foldl( lists:foldl(fun (Label, []) -> [Label];
fun(Label, []) ->
[Label];
(Label, [Head | Tail]) -> (Label, [Head | Tail]) ->
[Label ++ "." ++ Head, Head | Tail] [<<Label/binary, ".", Head/binary>>, Head | Tail]
end, [], lists:reverse(string:tokens(Domain, "."))). end,
[], lists:reverse(str:tokens(Domain, <<".">>))).
send_element(Pid, El) ->
Pid ! {send_element, El}.
send_element(Pid, El) -> Pid ! {send_element, El}.
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% ejabberd commands %%% ejabberd commands
commands() -> commands() ->
[ [#ejabberd_commands{name = incoming_s2s_number,
#ejabberd_commands{name = incoming_s2s_number,
tags = [stats, s2s], tags = [stats, s2s],
desc = "Number of incoming s2s connections on the node", desc =
"Number of incoming s2s connections on "
"the node",
module = ?MODULE, function = incoming_s2s_number, module = ?MODULE, function = incoming_s2s_number,
args = [], args = [], result = {s2s_incoming, integer}},
result = {s2s_incoming, integer}},
#ejabberd_commands{name = outgoing_s2s_number, #ejabberd_commands{name = outgoing_s2s_number,
tags = [stats, s2s], tags = [stats, s2s],
desc = "Number of outgoing s2s connections on the node", desc =
"Number of outgoing s2s connections on "
"the node",
module = ?MODULE, function = outgoing_s2s_number, module = ?MODULE, function = outgoing_s2s_number,
args = [], args = [], result = {s2s_outgoing, integer}}].
result = {s2s_outgoing, integer}}
].
incoming_s2s_number() -> incoming_s2s_number() ->
length(supervisor:which_children(ejabberd_s2s_in_sup)). length(supervisor:which_children(ejabberd_s2s_in_sup)).
@ -489,28 +507,21 @@ incoming_s2s_number() ->
outgoing_s2s_number() -> outgoing_s2s_number() ->
length(supervisor:which_children(ejabberd_s2s_out_sup)). length(supervisor:which_children(ejabberd_s2s_out_sup)).
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% Update Mnesia tables %%% Update Mnesia tables
update_tables() -> update_tables() ->
case catch mnesia:table_info(s2s, type) of case catch mnesia:table_info(s2s, type) of
bag -> bag -> ok;
ok; {'EXIT', _} -> ok;
{'EXIT', _} -> _ -> mnesia:delete_table(s2s)
ok;
_ ->
% XXX TODO convert it ?
mnesia:delete_table(s2s)
end, end,
case catch mnesia:table_info(s2s, attributes) of case catch mnesia:table_info(s2s, attributes) of
[fromto, node, key] -> [fromto, node, key] ->
mnesia:transform_table(s2s, ignore, [fromto, pid, key]), mnesia:transform_table(s2s, ignore, [fromto, pid, key]),
mnesia:clear_table(s2s); mnesia:clear_table(s2s);
[fromto, pid, key] -> [fromto, pid, key] -> ok;
ok; {'EXIT', _} -> ok
{'EXIT', _} ->
ok
end, end,
case lists:member(local_s2s, mnesia:system_info(tables)) of case lists:member(local_s2s, mnesia:system_info(tables)) of
true -> true ->
@ -521,30 +532,37 @@ update_tables() ->
%% Check if host is in blacklist or white list %% Check if host is in blacklist or white list
allow_host(MyServer, S2SHost) -> allow_host(MyServer, S2SHost) ->
allow_host2(MyServer, S2SHost) andalso (not is_temporarly_blocked(S2SHost)). allow_host2(MyServer, S2SHost) andalso
not is_temporarly_blocked(S2SHost).
allow_host2(MyServer, S2SHost) -> allow_host2(MyServer, S2SHost) ->
Hosts = ?MYHOSTS, Hosts = (?MYHOSTS),
case lists:dropwhile( case lists:dropwhile(fun (ParentDomain) ->
fun(ParentDomain) ->
not lists:member(ParentDomain, Hosts) not lists:member(ParentDomain, Hosts)
end, parent_domains(MyServer)) of end,
[MyHost|_] -> parent_domains(MyServer))
allow_host1(MyHost, S2SHost); of
[] -> [MyHost | _] -> allow_host1(MyHost, S2SHost);
allow_host1(MyServer, S2SHost) [] -> allow_host1(MyServer, S2SHost)
end. end.
allow_host1(MyHost, S2SHost) -> allow_host1(MyHost, S2SHost) ->
case ejabberd_config:get_local_option({{s2s_host, S2SHost}, MyHost}) of case ejabberd_config:get_local_option(
{{s2s_host, S2SHost}, MyHost},
fun(deny) -> deny; (allow) -> allow end)
of
deny -> false; deny -> false;
allow -> true; allow -> true;
_ -> undefined ->
case ejabberd_config:get_local_option({s2s_default_policy, MyHost}) of case ejabberd_config:get_local_option(
{s2s_default_policy, MyHost},
fun(deny) -> deny; (allow) -> allow end)
of
deny -> false; deny -> false;
_ -> _ ->
case ejabberd_hooks:run_fold(s2s_allow_host, MyHost, case ejabberd_hooks:run_fold(s2s_allow_host, MyHost,
allow, [MyHost, S2SHost]) of allow, [MyHost, S2SHost])
of
deny -> false; deny -> false;
allow -> true; allow -> true;
_ -> true _ -> true
@ -556,6 +574,7 @@ allow_host1(MyHost, S2SHost) ->
%% @spec (Type) -> [Info] %% @spec (Type) -> [Info]
%% where Type = in | out %% where Type = in | out
%% Info = [{InfoName::atom(), InfoValue::any()}] %% Info = [{InfoName::atom(), InfoValue::any()}]
get_info_s2s_connections(Type) -> get_info_s2s_connections(Type) ->
ChildType = case Type of ChildType = case Type of
in -> ejabberd_s2s_in_sup; in -> ejabberd_s2s_in_sup;
@ -566,15 +585,19 @@ get_info_s2s_connections(Type) ->
get_s2s_info(Connections, Type) -> get_s2s_info(Connections, Type) ->
complete_s2s_info(Connections, Type, []). complete_s2s_info(Connections, Type, []).
complete_s2s_info([],_,Result)->
Result; complete_s2s_info([], _, Result) -> Result;
complete_s2s_info([Connection | T], Type, Result) -> complete_s2s_info([Connection | T], Type, Result) ->
{_, PID, _, _} = Connection, {_, PID, _, _} = Connection,
State = get_s2s_state(PID), State = get_s2s_state(PID),
complete_s2s_info(T, Type, [State | Result]). complete_s2s_info(T, Type, [State | Result]).
-spec get_s2s_state(pid()) -> [{status, open | closed | error} | {s2s_pid, pid()}].
get_s2s_state(S2sPid) -> get_s2s_state(S2sPid) ->
Infos = case gen_fsm:sync_send_all_state_event(S2sPid,get_state_infos) of Infos = case gen_fsm:sync_send_all_state_event(S2sPid,
get_state_infos)
of
{state_infos, Is} -> [{status, open} | Is]; {state_infos, Is} -> [{status, open} | Is];
{noproc, _} -> [{status, closed}]; %% Connection closed {noproc, _} -> [{status, closed}]; %% Connection closed
{badrpc, _} -> [{status, error}] {badrpc, _} -> [{status, error}]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -25,6 +25,7 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(ejabberd_service). -module(ejabberd_service).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-define(GEN_FSM, p1_fsm). -define(GEN_FSM, p1_fsm).
@ -32,82 +33,78 @@
-behaviour(?GEN_FSM). -behaviour(?GEN_FSM).
%% External exports %% External exports
-export([start/2, -export([start/2, start_link/2, send_text/2,
start_link/2, send_element/2, socket_type/0]).
send_text/2,
send_element/2,
socket_type/0]).
%% gen_fsm callbacks %% gen_fsm callbacks
-export([init/1, -export([init/1, wait_for_stream/2,
wait_for_stream/2, wait_for_handshake/2, stream_established/2,
wait_for_handshake/2, handle_event/3, handle_sync_event/4, code_change/4,
stream_established/2, handle_info/3, terminate/3, print_state/1]).
handle_event/3,
handle_sync_event/4,
code_change/4,
handle_info/3,
terminate/3,
print_state/1]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl"). -include("jlib.hrl").
-record(state, {socket, sockmod, streamid, -record(state,
hosts, password, access, {socket :: ejabberd_socket:socket_state(),
check_from}). sockmod = ejabberd_socket :: ejabberd_socket | ejabberd_frontend_socket,
streamid = <<"">> :: binary(),
hosts = [] :: [binary()],
password = <<"">> :: binary(),
access :: atom(),
check_from = true :: boolean()}).
%-define(DBGFSM, true). %-define(DBGFSM, true).
-ifdef(DBGFSM). -ifdef(DBGFSM).
-define(FSMOPTS, [{debug, [trace]}]). -define(FSMOPTS, [{debug, [trace]}]).
-else. -else.
-define(FSMOPTS, []). -define(FSMOPTS, []).
-endif. -endif.
-define(STREAM_HEADER, -define(STREAM_HEADER,
"<?xml version='1.0'?>" <<"<?xml version='1.0'?><stream:stream "
"<stream:stream " "xmlns:stream='http://etherx.jabber.org/stream"
"xmlns:stream='http://etherx.jabber.org/streams' " "s' xmlns='jabber:component:accept' id='~s' "
"xmlns='jabber:component:accept' " "from='~s'>">>).
"id='~s' from='~s'>"
).
-define(STREAM_TRAILER, "</stream:stream>"). -define(STREAM_TRAILER, <<"</stream:stream>">>).
-define(INVALID_HEADER_ERR, -define(INVALID_HEADER_ERR,
"<stream:stream " <<"<stream:stream xmlns:stream='http://etherx.ja"
"xmlns:stream='http://etherx.jabber.org/streams'>" "bber.org/streams'><stream:error>Invalid "
"<stream:error>Invalid Stream Header</stream:error>" "Stream Header</stream:error></stream:stream>">>).
"</stream:stream>"
).
-define(INVALID_HANDSHAKE_ERR, -define(INVALID_HANDSHAKE_ERR,
"<stream:error>" <<"<stream:error><not-authorized xmlns='urn:ietf"
"<not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>" ":params:xml:ns:xmpp-streams'/><text "
"<text xmlns='urn:ietf:params:xml:ns:xmpp-streams' xml:lang='en'>" "xmlns='urn:ietf:params:xml:ns:xmpp-streams' "
"Invalid Handshake</text>" "xml:lang='en'>Invalid Handshake</text></strea"
"</stream:error>" "m:error></stream:stream>">>).
"</stream:stream>"
).
-define(INVALID_XML_ERR, -define(INVALID_XML_ERR,
xml:element_to_string(?SERR_XML_NOT_WELL_FORMED)). xml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
-define(INVALID_NS_ERR, -define(INVALID_NS_ERR,
xml:element_to_string(?SERR_INVALID_NAMESPACE)). xml:element_to_binary(?SERR_INVALID_NAMESPACE)).
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% API %%% API
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
start(SockData, Opts) -> start(SockData, Opts) ->
supervisor:start_child(ejabberd_service_sup, [SockData, Opts]). supervisor:start_child(ejabberd_service_sup,
[SockData, Opts]).
start_link(SockData, Opts) -> start_link(SockData, Opts) ->
?GEN_FSM:start_link(ejabberd_service, [SockData, Opts], (?GEN_FSM):start_link(ejabberd_service,
fsm_limit_opts(Opts) ++ ?FSMOPTS). [SockData, Opts], fsm_limit_opts(Opts) ++ (?FSMOPTS)).
socket_type() -> socket_type() -> xml_stream.
xml_stream.
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm %%% Callback functions from gen_fsm
@ -126,12 +123,11 @@ init([{SockMod, Socket}, Opts]) ->
{value, {_, A}} -> A; {value, {_, A}} -> A;
_ -> all _ -> all
end, end,
{Hosts, Password} = {Hosts, Password} = case lists:keysearch(hosts, 1, Opts)
case lists:keysearch(hosts, 1, Opts) of of
{value, {_, Hs, HOpts}} -> {value, {_, Hs, HOpts}} ->
case lists:keysearch(password, 1, HOpts) of case lists:keysearch(password, 1, HOpts) of
{value, {_, P}} -> {value, {_, P}} -> {Hs, P};
{Hs, P};
_ -> _ ->
% TODO: generate error % TODO: generate error
false false
@ -140,8 +136,7 @@ init([{SockMod, Socket}, Opts]) ->
case lists:keysearch(host, 1, Opts) of case lists:keysearch(host, 1, Opts) of
{value, {_, H, HOpts}} -> {value, {_, H, HOpts}} ->
case lists:keysearch(password, 1, HOpts) of case lists:keysearch(password, 1, HOpts) of
{value, {_, P}} -> {value, {_, P}} -> {[H], P};
{[H], P};
_ -> _ ->
% TODO: generate error % TODO: generate error
false false
@ -155,19 +150,17 @@ init([{SockMod, Socket}, Opts]) ->
{value, {_, S}} -> S; {value, {_, S}} -> S;
_ -> none _ -> none
end, end,
CheckFrom = case lists:keysearch(service_check_from, 1, Opts) of CheckFrom = case lists:keysearch(service_check_from, 1,
Opts)
of
{value, {_, CF}} -> CF; {value, {_, CF}} -> CF;
_ -> true _ -> true
end, end,
SockMod:change_shaper(Socket, Shaper), SockMod:change_shaper(Socket, Shaper),
{ok, wait_for_stream, #state{socket = Socket, {ok, wait_for_stream,
sockmod = SockMod, #state{socket = Socket, sockmod = SockMod,
streamid = new_id(), streamid = new_id(), hosts = Hosts, password = Password,
hosts = Hosts, access = Access, check_from = CheckFrom}}.
password = Password,
access = Access,
check_from = CheckFrom
}}.
%%---------------------------------------------------------------------- %%----------------------------------------------------------------------
%% Func: StateName/2 %% Func: StateName/2
@ -176,14 +169,11 @@ init([{SockMod, Socket}, Opts]) ->
%% {stop, Reason, NewStateData} %% {stop, Reason, NewStateData}
%%---------------------------------------------------------------------- %%----------------------------------------------------------------------
wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> wait_for_stream({xmlstreamstart, _Name, Attrs},
case xml:get_attr_s("xmlns", Attrs) of StateData) ->
"jabber:component:accept" -> case xml:get_attr_s(<<"xmlns">>, Attrs) of
%% Note: XEP-0114 requires to check that destination is a Jabber <<"jabber:component:accept">> ->
%% component served by this Jabber server. To = xml:get_attr_s(<<"to">>, Attrs),
%% However several transports don't respect that,
%% so ejabberd doesn't check 'to' attribute (EJAB-717)
To = xml:get_attr_s("to", Attrs),
Header = io_lib:format(?STREAM_HEADER, Header = io_lib:format(?STREAM_HEADER,
[StateData#state.streamid, xml:crypt(To)]), [StateData#state.streamid, xml:crypt(To)]),
send_text(StateData, Header), send_text(StateData, Header),
@ -192,55 +182,52 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
send_text(StateData, ?INVALID_HEADER_ERR), send_text(StateData, ?INVALID_HEADER_ERR),
{stop, normal, StateData} {stop, normal, StateData}
end; end;
wait_for_stream({xmlstreamerror, _}, StateData) -> wait_for_stream({xmlstreamerror, _}, StateData) ->
Header = io_lib:format(?STREAM_HEADER, Header = io_lib:format(?STREAM_HEADER,
["none", ?MYNAME]), [<<"none">>, ?MYNAME]),
send_text(StateData, send_text(StateData,
Header ++ ?INVALID_XML_ERR ++ ?STREAM_TRAILER), <<(list_to_binary(Header))/binary, (?INVALID_XML_ERR)/binary,
(?STREAM_TRAILER)/binary>>),
{stop, normal, StateData}; {stop, normal, StateData};
wait_for_stream(closed, StateData) -> wait_for_stream(closed, StateData) ->
{stop, normal, StateData}. {stop, normal, StateData}.
wait_for_handshake({xmlstreamelement, El}, StateData) -> wait_for_handshake({xmlstreamelement, El}, StateData) ->
{xmlelement, Name, _Attrs, Els} = El, #xmlel{name = Name, children = Els} = El,
case {Name, xml:get_cdata(Els)} of case {Name, xml:get_cdata(Els)} of
{"handshake", Digest} -> {<<"handshake">>, Digest} ->
case sha:sha(StateData#state.streamid ++ case sha:sha(<<(StateData#state.streamid)/binary,
StateData#state.password) of (StateData#state.password)/binary>>)
of
Digest -> Digest ->
send_text(StateData, "<handshake/>"), send_text(StateData, <<"<handshake/>">>),
lists:foreach( lists:foreach(fun (H) ->
fun(H) ->
ejabberd_router:register_route(H), ejabberd_router:register_route(H),
?INFO_MSG("Route registered for service ~p~n", [H]) ?INFO_MSG("Route registered for service ~p~n",
end, StateData#state.hosts), [H])
end,
StateData#state.hosts),
{next_state, stream_established, StateData}; {next_state, stream_established, StateData};
_ -> _ ->
send_text(StateData, ?INVALID_HANDSHAKE_ERR), send_text(StateData, ?INVALID_HANDSHAKE_ERR),
{stop, normal, StateData} {stop, normal, StateData}
end; end;
_ -> _ -> {next_state, wait_for_handshake, StateData}
{next_state, wait_for_handshake, StateData}
end; end;
wait_for_handshake({xmlstreamend, _Name}, StateData) -> wait_for_handshake({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData}; {stop, normal, StateData};
wait_for_handshake({xmlstreamerror, _}, StateData) -> wait_for_handshake({xmlstreamerror, _}, StateData) ->
send_text(StateData, ?INVALID_XML_ERR ++ ?STREAM_TRAILER), send_text(StateData,
<<(?INVALID_XML_ERR)/binary,
(?STREAM_TRAILER)/binary>>),
{stop, normal, StateData}; {stop, normal, StateData};
wait_for_handshake(closed, StateData) -> wait_for_handshake(closed, StateData) ->
{stop, normal, StateData}. {stop, normal, StateData}.
stream_established({xmlstreamelement, El}, StateData) -> stream_established({xmlstreamelement, El}, StateData) ->
NewEl = jlib:remove_attr("xmlns", El), NewEl = jlib:remove_attr(<<"xmlns">>, El),
{xmlelement, Name, Attrs, _Els} = NewEl, #xmlel{name = Name, attrs = Attrs} = NewEl,
From = xml:get_attr_s("from", Attrs), From = xml:get_attr_s(<<"from">>, Attrs),
FromJID = case StateData#state.check_from of FromJID = case StateData#state.check_from of
%% If the admin does not want to check the from field %% If the admin does not want to check the from field
%% when accept packets from any address. %% when accept packets from any address.
@ -259,15 +246,15 @@ stream_established({xmlstreamelement, El}, StateData) ->
_ -> error _ -> error
end end
end, end,
To = xml:get_attr_s("to", Attrs), To = xml:get_attr_s(<<"to">>, Attrs),
ToJID = case To of ToJID = case To of
"" -> error; <<"">> -> error;
_ -> jlib:string_to_jid(To) _ -> jlib:string_to_jid(To)
end, end,
if ((Name == "iq") or if ((Name == <<"iq">>) or (Name == <<"message">>) or
(Name == "message") or (Name == <<"presence">>))
(Name == "presence")) and and (ToJID /= error)
(ToJID /= error) and (FromJID /= error) -> and (FromJID /= error) ->
ejabberd_router:route(FromJID, ToJID, NewEl); ejabberd_router:route(FromJID, ToJID, NewEl);
true -> true ->
Err = jlib:make_error_reply(NewEl, ?ERR_BAD_REQUEST), Err = jlib:make_error_reply(NewEl, ?ERR_BAD_REQUEST),
@ -275,21 +262,16 @@ stream_established({xmlstreamelement, El}, StateData) ->
error error
end, end,
{next_state, stream_established, StateData}; {next_state, stream_established, StateData};
stream_established({xmlstreamend, _Name}, StateData) -> stream_established({xmlstreamend, _Name}, StateData) ->
% TODO
{stop, normal, StateData}; {stop, normal, StateData};
stream_established({xmlstreamerror, _}, StateData) -> stream_established({xmlstreamerror, _}, StateData) ->
send_text(StateData, ?INVALID_XML_ERR ++ ?STREAM_TRAILER), send_text(StateData,
<<(?INVALID_XML_ERR)/binary,
(?STREAM_TRAILER)/binary>>),
{stop, normal, StateData}; {stop, normal, StateData};
stream_established(closed, StateData) -> stream_established(closed, StateData) ->
% TODO
{stop, normal, StateData}. {stop, normal, StateData}.
%%---------------------------------------------------------------------- %%----------------------------------------------------------------------
%% Func: StateName/3 %% Func: StateName/3
%% Returns: {next_state, NextStateName, NextStateData} | %% Returns: {next_state, NextStateName, NextStateData} |
@ -321,9 +303,9 @@ handle_event(_Event, StateName, StateData) ->
%% {stop, Reason, NewStateData} | %% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData} %% {stop, Reason, Reply, NewStateData}
%%---------------------------------------------------------------------- %%----------------------------------------------------------------------
handle_sync_event(_Event, _From, StateName, StateData) -> handle_sync_event(_Event, _From, StateName,
Reply = ok, StateData) ->
{reply, Reply, StateName, StateData}. Reply = ok, {reply, Reply, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) -> code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}. {ok, StateName, StateData}.
@ -340,14 +322,19 @@ handle_info({send_text, Text}, StateName, StateData) ->
handle_info({send_element, El}, StateName, StateData) -> handle_info({send_element, El}, StateName, StateData) ->
send_element(StateData, El), send_element(StateData, El),
{next_state, StateName, StateData}; {next_state, StateName, StateData};
handle_info({route, From, To, Packet}, StateName, StateData) -> handle_info({route, From, To, Packet}, StateName,
case acl:match_rule(global, StateData#state.access, From) of StateData) ->
case acl:match_rule(global, StateData#state.access,
From)
of
allow -> allow ->
{xmlelement, Name, Attrs, Els} = Packet, #xmlel{name = Name, attrs = Attrs, children = Els} =
Attrs2 = jlib:replace_from_to_attrs(jlib:jid_to_string(From), Packet,
jlib:jid_to_string(To), Attrs2 =
Attrs), jlib:replace_from_to_attrs(jlib:jid_to_string(From),
Text = xml:element_to_binary({xmlelement, Name, Attrs2, Els}), jlib:jid_to_string(To), Attrs),
Text = xml:element_to_binary(#xmlel{name = Name,
attrs = Attrs2, children = Els}),
send_text(StateData, Text); send_text(StateData, Text);
deny -> deny ->
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED), Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
@ -358,7 +345,6 @@ handle_info(Info, StateName, StateData) ->
?ERROR_MSG("Unexpected info: ~p", [Info]), ?ERROR_MSG("Unexpected info: ~p", [Info]),
{next_state, StateName, StateData}. {next_state, StateName, StateData}.
%%---------------------------------------------------------------------- %%----------------------------------------------------------------------
%% Func: terminate/3 %% Func: terminate/3
%% Purpose: Shutdown the fsm %% Purpose: Shutdown the fsm
@ -368,12 +354,11 @@ terminate(Reason, StateName, StateData) ->
?INFO_MSG("terminated: ~p", [Reason]), ?INFO_MSG("terminated: ~p", [Reason]),
case StateName of case StateName of
stream_established -> stream_established ->
lists:foreach( lists:foreach(fun (H) ->
fun(H) ->
ejabberd_router:unregister_route(H) ejabberd_router:unregister_route(H)
end, StateData#state.hosts); end,
_ -> StateData#state.hosts);
ok _ -> ok
end, end,
(StateData#state.sockmod):close(StateData#state.socket), (StateData#state.sockmod):close(StateData#state.socket),
ok. ok.
@ -383,31 +368,30 @@ terminate(Reason, StateName, StateData) ->
%% Purpose: Prepare the state to be printed on error log %% Purpose: Prepare the state to be printed on error log
%% Returns: State to print %% Returns: State to print
%%---------------------------------------------------------------------- %%----------------------------------------------------------------------
print_state(State) -> print_state(State) -> State.
State.
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% Internal functions %%% Internal functions
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
send_text(StateData, Text) -> send_text(StateData, Text) ->
(StateData#state.sockmod):send(StateData#state.socket, Text). (StateData#state.sockmod):send(StateData#state.socket,
Text).
send_element(StateData, El) -> send_element(StateData, El) ->
send_text(StateData, xml:element_to_binary(El)). send_text(StateData, xml:element_to_binary(El)).
new_id() -> new_id() -> randoms:get_string().
randoms:get_string().
fsm_limit_opts(Opts) -> fsm_limit_opts(Opts) ->
case lists:keysearch(max_fsm_queue, 1, Opts) of case lists:keysearch(max_fsm_queue, 1, Opts) of
{value, {_, N}} when is_integer(N) -> {value, {_, N}} when is_integer(N) ->
[{max_queue, N}]; [{max_queue, N}];
_ -> _ ->
case ejabberd_config:get_local_option(max_fsm_queue) of case ejabberd_config:get_local_option(
N when is_integer(N) -> max_fsm_queue,
[{max_queue, N}]; fun(I) when is_integer(I), I > 0 -> I end) of
_ -> undefined -> [];
[] N -> [{max_queue, N}]
end end
end. end.

View File

@ -25,6 +25,7 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(ejabberd_sm). -module(ejabberd_sm).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(gen_server). -behaviour(gen_server).
@ -58,12 +59,15 @@
]). ]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2,
terminate/2, code_change/3]). handle_info/2, terminate/2, code_change/3]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl"). -include("jlib.hrl").
-include("ejabberd_commands.hrl"). -include("ejabberd_commands.hrl").
-include("mod_privacy.hrl"). -include("mod_privacy.hrl").
-record(session, {sid, usr, us, priority, info}). -record(session, {sid, usr, us, priority, info}).
@ -80,26 +84,40 @@
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error} %% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server %% Description: Starts the server
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-type sid() :: {erlang:timestamp(), pid()}.
-type ip() :: {inet:ip_address(), inet:port_number()} | undefined.
-type info() :: [{conn, atom()} | {ip, ip()} | {node, atom()}
| {oor, boolean()} | {auth_module, atom()}].
-type prio() :: undefined | integer().
-export_type([sid/0]).
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [],
[]).
-spec route(jid(), jid(), xmlel() | broadcast()) -> ok.
route(From, To, Packet) -> route(From, To, Packet) ->
case catch do_route(From, To, Packet) of case catch do_route(From, To, Packet) of
{'EXIT', Reason} -> {'EXIT', Reason} ->
?ERROR_MSG("~p~nwhen processing: ~p", ?ERROR_MSG("~p~nwhen processing: ~p",
[Reason, {From, To, Packet}]); [Reason, {From, To, Packet}]);
_ -> _ -> ok
ok
end. end.
-spec open_session(sid(), binary(), binary(), binary(), info()) -> ok.
open_session(SID, User, Server, Resource, Info) -> open_session(SID, User, Server, Resource, Info) ->
set_session(SID, User, Server, Resource, undefined, Info), set_session(SID, User, Server, Resource, undefined, Info),
mnesia:dirty_update_counter(session_counter, mnesia:dirty_update_counter(session_counter,
jlib:nameprep(Server), 1), jlib:nameprep(Server), 1),
check_for_sessions_to_replace(User, Server, Resource), check_for_sessions_to_replace(User, Server, Resource),
JID = jlib:make_jid(User, Server, Resource), JID = jlib:make_jid(User, Server, Resource),
ejabberd_hooks:run(sm_register_connection_hook, JID#jid.lserver, ejabberd_hooks:run(sm_register_connection_hook,
[SID, JID, Info]). JID#jid.lserver, [SID, JID, Info]).
-spec close_session(sid(), binary(), binary(), binary()) -> ok.
close_session(SID, User, Server, Resource) -> close_session(SID, User, Server, Resource) ->
Info = case mnesia:dirty_read({session, SID}) of Info = case mnesia:dirty_read({session, SID}) of
@ -113,27 +131,29 @@ close_session(SID, User, Server, Resource) ->
end, end,
mnesia:sync_dirty(F), mnesia:sync_dirty(F),
JID = jlib:make_jid(User, Server, Resource), JID = jlib:make_jid(User, Server, Resource),
ejabberd_hooks:run(sm_remove_connection_hook, JID#jid.lserver, ejabberd_hooks:run(sm_remove_connection_hook,
[SID, JID, Info]). JID#jid.lserver, [SID, JID, Info]).
check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) -> check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
case ejabberd_auth:is_user_exists(User, Server) of case ejabberd_auth:is_user_exists(User, Server) of
true -> true -> Acc;
Acc; false -> {stop, false}
false ->
{stop, false}
end. end.
-spec bounce_offline_message(jid(), jid(), xmlel()) -> stop.
bounce_offline_message(From, To, Packet) -> bounce_offline_message(From, To, Packet) ->
Err = jlib:make_error_reply(Packet, ?ERR_SERVICE_UNAVAILABLE), Err = jlib:make_error_reply(Packet,
?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(To, From, Err), ejabberd_router:route(To, From, Err),
stop. stop.
-spec disconnect_removed_user(binary(), binary()) -> ok.
disconnect_removed_user(User, Server) -> disconnect_removed_user(User, Server) ->
ejabberd_sm:route(jlib:make_jid("", "", ""), ejabberd_sm:route(jlib:make_jid(<<"">>, <<"">>, <<"">>),
jlib:make_jid(User, Server, ""), jlib:make_jid(User, Server, <<"">>),
{xmlelement, "broadcast", [], {broadcast, {exit, <<"User removed">>}}).
[{exit, "User removed"}]}).
get_user_resources(User, Server) -> get_user_resources(User, Server) ->
LUser = jlib:nodeprep(User), LUser = jlib:nodeprep(User),
@ -146,6 +166,8 @@ get_user_resources(User, Server) ->
[element(3, S#session.usr) || S <- clean_session_list(Ss)] [element(3, S#session.usr) || S <- clean_session_list(Ss)]
end. end.
-spec get_user_ip(binary(), binary(), binary()) -> ip().
get_user_ip(User, Server, Resource) -> get_user_ip(User, Server, Resource) ->
LUser = jlib:nodeprep(User), LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
@ -159,6 +181,8 @@ get_user_ip(User, Server, Resource) ->
proplists:get_value(ip, Session#session.info) proplists:get_value(ip, Session#session.info)
end. end.
-spec get_user_info(binary(), binary(), binary()) -> info() | offline.
get_user_info(User, Server, Resource) -> get_user_info(User, Server, Resource) ->
LUser = jlib:nodeprep(User), LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
@ -175,21 +199,40 @@ get_user_info(User, Server, Resource) ->
[{node, Node}, {conn, Conn}, {ip, IP}] [{node, Node}, {conn, Conn}, {ip, IP}]
end. end.
set_presence(SID, User, Server, Resource, Priority, Presence, Info) -> -spec set_presence(sid(), binary(), binary(), binary(),
set_session(SID, User, Server, Resource, Priority, Info), prio(), xmlel(), info()) -> ok.
ejabberd_hooks:run(set_presence_hook, jlib:nameprep(Server),
set_presence(SID, User, Server, Resource, Priority,
Presence, Info) ->
set_session(SID, User, Server, Resource, Priority,
Info),
ejabberd_hooks:run(set_presence_hook,
jlib:nameprep(Server),
[User, Server, Resource, Presence]). [User, Server, Resource, Presence]).
unset_presence(SID, User, Server, Resource, Status, Info) -> -spec unset_presence(sid(), binary(), binary(),
set_session(SID, User, Server, Resource, undefined, Info), binary(), binary(), info()) -> ok.
ejabberd_hooks:run(unset_presence_hook, jlib:nameprep(Server),
unset_presence(SID, User, Server, Resource, Status,
Info) ->
set_session(SID, User, Server, Resource, undefined,
Info),
ejabberd_hooks:run(unset_presence_hook,
jlib:nameprep(Server),
[User, Server, Resource, Status]). [User, Server, Resource, Status]).
close_session_unset_presence(SID, User, Server, Resource, Status) -> -spec close_session_unset_presence(sid(), binary(), binary(),
binary(), binary()) -> ok.
close_session_unset_presence(SID, User, Server,
Resource, Status) ->
close_session(SID, User, Server, Resource), close_session(SID, User, Server, Resource),
ejabberd_hooks:run(unset_presence_hook, jlib:nameprep(Server), ejabberd_hooks:run(unset_presence_hook,
jlib:nameprep(Server),
[User, Server, Resource, Status]). [User, Server, Resource, Status]).
-spec get_session_pid(binary(), binary(), binary()) -> none | pid().
get_session_pid(User, Server, Resource) -> get_session_pid(User, Server, Resource) ->
LUser = jlib:nodeprep(User), LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
@ -200,6 +243,8 @@ get_session_pid(User, Server, Resource) ->
_ -> none _ -> none
end. end.
-spec dirty_get_sessions_list() -> [ljid()].
dirty_get_sessions_list() -> dirty_get_sessions_list() ->
mnesia:dirty_select( mnesia:dirty_select(
session, session,
@ -216,11 +261,11 @@ dirty_get_my_sessions_list() ->
get_vh_session_list(Server) -> get_vh_session_list(Server) ->
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
mnesia:dirty_select( mnesia:dirty_select(session,
session,
[{#session{usr = '$1', _ = '_'}, [{#session{usr = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, LServer}], [{'==', {element, 2, '$1'}, LServer}], ['$1']}]).
['$1']}]).
-spec get_vh_session_list(binary()) -> [ljid()].
get_vh_session_number(Server) -> get_vh_session_number(Server) ->
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
@ -236,10 +281,16 @@ get_vh_session_number(Server) ->
end. end.
register_iq_handler(Host, XMLNS, Module, Fun) -> register_iq_handler(Host, XMLNS, Module, Fun) ->
ejabberd_sm ! {register_iq_handler, Host, XMLNS, Module, Fun}. ejabberd_sm !
{register_iq_handler, Host, XMLNS, Module, Fun}.
-spec register_iq_handler(binary(), binary(), atom(), atom(), list()) -> any().
register_iq_handler(Host, XMLNS, Module, Fun, Opts) -> register_iq_handler(Host, XMLNS, Module, Fun, Opts) ->
ejabberd_sm ! {register_iq_handler, Host, XMLNS, Module, Fun, Opts}. ejabberd_sm !
{register_iq_handler, Host, XMLNS, Module, Fun, Opts}.
-spec unregister_iq_handler(binary(), binary()) -> any().
unregister_iq_handler(Host, XMLNS) -> unregister_iq_handler(Host, XMLNS) ->
ejabberd_sm ! {unregister_iq_handler, Host, XMLNS}. ejabberd_sm ! {unregister_iq_handler, Host, XMLNS}.
@ -293,8 +344,7 @@ init([]) ->
%% Description: Handling call messages %% Description: Handling call messages
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_call(_Request, _From, State) -> handle_call(_Request, _From, State) ->
Reply = ok, Reply = ok, {reply, Reply, State}.
{reply, Reply, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} | %% Function: handle_cast(Msg, State) -> {noreply, State} |
@ -302,8 +352,7 @@ handle_call(_Request, _From, State) ->
%% {stop, Reason, State} %% {stop, Reason, State}
%% Description: Handling cast messages %% Description: Handling cast messages
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_cast(_Msg, State) -> handle_cast(_Msg, State) -> {noreply, State}.
{noreply, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} | %% Function: handle_info(Info, State) -> {noreply, State} |
@ -326,20 +375,22 @@ handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) -> handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) ->
ets:insert(sm_iqtable, {{XMLNS, Host}, Module, Function}), ets:insert(sm_iqtable, {{XMLNS, Host}, Module, Function}),
{noreply, State}; {noreply, State};
handle_info({register_iq_handler, Host, XMLNS, Module, Function, Opts}, State) -> handle_info({register_iq_handler, Host, XMLNS, Module,
ets:insert(sm_iqtable, {{XMLNS, Host}, Module, Function, Opts}), Function, Opts},
State) ->
ets:insert(sm_iqtable,
{{XMLNS, Host}, Module, Function, Opts}),
{noreply, State}; {noreply, State};
handle_info({unregister_iq_handler, Host, XMLNS}, State) -> handle_info({unregister_iq_handler, Host, XMLNS},
State) ->
case ets:lookup(sm_iqtable, {XMLNS, Host}) of case ets:lookup(sm_iqtable, {XMLNS, Host}) of
[{_, Module, Function, Opts}] -> [{_, Module, Function, Opts}] ->
gen_iq_handler:stop_iq_handler(Module, Function, Opts); gen_iq_handler:stop_iq_handler(Module, Function, Opts);
_ -> _ -> ok
ok
end, end,
ets:delete(sm_iqtable, {XMLNS, Host}), ets:delete(sm_iqtable, {XMLNS, Host}),
{noreply, State}; {noreply, State};
handle_info(_Info, State) -> handle_info(_Info, State) -> {noreply, State}.
{noreply, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void() %% Function: terminate(Reason, State) -> void()
@ -356,8 +407,7 @@ terminate(_Reason, _State) ->
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed %% Description: Convert process state when code is changed
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) -> {ok, State}.
{ok, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%%% Internal functions %%% Internal functions
@ -370,11 +420,8 @@ set_session(SID, User, Server, Resource, Priority, Info) ->
US = {LUser, LServer}, US = {LUser, LServer},
USR = {LUser, LServer, LResource}, USR = {LUser, LServer, LResource},
F = fun () -> F = fun () ->
mnesia:write(#session{sid = SID, mnesia:write(#session{sid = SID, usr = USR, us = US,
usr = USR, priority = Priority, info = Info})
us = US,
priority = Priority,
info = Info})
end, end,
mnesia:sync_dirty(F). mnesia:sync_dirty(F).
@ -408,101 +455,100 @@ recount_session_table(Node) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
do_route(From, To, Packet) -> do_route(From, To, Packet) ->
?DEBUG("session manager~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n", ?DEBUG("session manager~n\tfrom ~p~n\tto ~p~n\tpacket "
"~P~n",
[From, To, Packet, 8]), [From, To, Packet, 8]),
#jid{user = User, server = Server, #jid{user = User, server = Server,
luser = LUser, lserver = LServer, lresource = LResource} = To, luser = LUser, lserver = LServer, lresource = LResource} = To,
{xmlelement, Name, Attrs, _Els} = Packet, #xmlel{name = Name, attrs = Attrs} = Packet,
case LResource of case LResource of
"" -> <<"">> ->
case Name of case Name of
"presence" -> <<"presence">> ->
{Pass, _Subsc} = {Pass, _Subsc} = case xml:get_attr_s(<<"type">>, Attrs)
case xml:get_attr_s("type", Attrs) of of
"subscribe" -> <<"subscribe">> ->
Reason = xml:get_path_s( Reason = xml:get_path_s(Packet,
Packet, [{elem,
[{elem, "status"}, cdata]), <<"status">>},
{is_privacy_allow(From, To, Packet) andalso cdata]),
ejabberd_hooks:run_fold( {is_privacy_allow(From, To, Packet)
roster_in_subscription, andalso
ejabberd_hooks:run_fold(roster_in_subscription,
LServer, LServer,
false, false,
[User, Server, From, subscribe, Reason]), [User, Server,
From,
subscribe,
Reason]),
true}; true};
"subscribed" -> <<"subscribed">> ->
{is_privacy_allow(From, To, Packet) andalso {is_privacy_allow(From, To, Packet)
ejabberd_hooks:run_fold( andalso
roster_in_subscription, ejabberd_hooks:run_fold(roster_in_subscription,
LServer, LServer,
false, false,
[User, Server, From, subscribed, ""]), [User, Server,
From,
subscribed,
<<"">>]),
true}; true};
"unsubscribe" -> <<"unsubscribe">> ->
{is_privacy_allow(From, To, Packet) andalso {is_privacy_allow(From, To, Packet)
ejabberd_hooks:run_fold( andalso
roster_in_subscription, ejabberd_hooks:run_fold(roster_in_subscription,
LServer, LServer,
false, false,
[User, Server, From, unsubscribe, ""]), [User, Server,
From,
unsubscribe,
<<"">>]),
true}; true};
"unsubscribed" -> <<"unsubscribed">> ->
{is_privacy_allow(From, To, Packet) andalso {is_privacy_allow(From, To, Packet)
ejabberd_hooks:run_fold( andalso
roster_in_subscription, ejabberd_hooks:run_fold(roster_in_subscription,
LServer, LServer,
false, false,
[User, Server, From, unsubscribed, ""]), [User, Server,
From,
unsubscribed,
<<"">>]),
true}; true};
_ -> _ -> {true, false}
{true, false}
end, end,
if Pass -> if Pass ->
PResources = get_user_present_resources( PResources = get_user_present_resources(LUser, LServer),
LUser, LServer), lists:foreach(fun ({_, R}) ->
lists:foreach(
fun({_, R}) ->
do_route(
From,
jlib:jid_replace_resource(To, R),
Packet)
end, PResources);
true ->
ok
end;
"message" ->
route_message(From, To, Packet);
"iq" ->
process_iq(From, To, Packet);
"broadcast" ->
lists:foreach(
fun(R) ->
do_route(From, do_route(From,
jlib:jid_replace_resource(To, R), jlib:jid_replace_resource(To,
R),
Packet) Packet)
end, get_user_resources(User, Server)); end,
_ -> PResources);
ok true -> ok
end;
<<"message">> -> route_message(From, To, Packet);
<<"iq">> -> process_iq(From, To, Packet);
_ -> ok
end; end;
_ -> _ ->
USR = {LUser, LServer, LResource}, USR = {LUser, LServer, LResource},
case mnesia:dirty_index_read(session, USR, #session.usr) of case mnesia:dirty_index_read(session, USR, #session.usr)
of
[] -> [] ->
case Name of case Name of
"message" -> <<"message">> -> route_message(From, To, Packet);
route_message(From, To, Packet); <<"iq">> ->
"iq" -> case xml:get_attr_s(<<"type">>, Attrs) of
case xml:get_attr_s("type", Attrs) of <<"error">> -> ok;
"error" -> ok; <<"result">> -> ok;
"result" -> ok;
_ -> _ ->
Err = Err = jlib:make_error_reply(Packet,
jlib:make_error_reply( ?ERR_SERVICE_UNAVAILABLE),
Packet, ?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(To, From, Err) ejabberd_router:route(To, From, Err)
end; end;
_ -> _ -> ?DEBUG("packet droped~n", [])
?DEBUG("packet droped~n", [])
end; end;
Ss -> Ss ->
Session = lists:max(Ss), Session = lists:max(Ss),
@ -519,7 +565,8 @@ do_route(From, To, Packet) ->
is_privacy_allow(From, To, Packet) -> is_privacy_allow(From, To, Packet) ->
User = To#jid.user, User = To#jid.user,
Server = To#jid.server, Server = To#jid.server,
PrivacyList = ejabberd_hooks:run_fold(privacy_get_user_list, Server, PrivacyList =
ejabberd_hooks:run_fold(privacy_get_user_list, Server,
#userlist{}, [User, Server]), #userlist{}, [User, Server]),
is_privacy_allow(From, To, Packet, PrivacyList). is_privacy_allow(From, To, Packet, PrivacyList).
@ -528,13 +575,10 @@ is_privacy_allow(From, To, Packet) ->
is_privacy_allow(From, To, Packet, PrivacyList) -> is_privacy_allow(From, To, Packet, PrivacyList) ->
User = To#jid.user, User = To#jid.user,
Server = To#jid.server, Server = To#jid.server,
allow == ejabberd_hooks:run_fold( allow ==
privacy_check_packet, Server, ejabberd_hooks:run_fold(privacy_check_packet, Server,
allow, allow,
[User, [User, Server, PrivacyList, {From, To, Packet},
Server,
PrivacyList,
{From, To, Packet},
in]). in]).
route_message(From, To, Packet) -> route_message(From, To, Packet) ->
@ -542,14 +586,14 @@ route_message(From, To, Packet) ->
LServer = To#jid.lserver, LServer = To#jid.lserver,
PrioRes = get_user_present_resources(LUser, LServer), PrioRes = get_user_present_resources(LUser, LServer),
case catch lists:max(PrioRes) of case catch lists:max(PrioRes) of
{Priority, _R} when is_integer(Priority), Priority >= 0 -> {Priority, _R}
lists:foreach( when is_integer(Priority), Priority >= 0 ->
%% Route messages to all priority that equals the max, if lists:foreach(fun ({P, R}) when P == Priority ->
%% positive
fun({P, R}) when P == Priority ->
LResource = jlib:resourceprep(R), LResource = jlib:resourceprep(R),
USR = {LUser, LServer, LResource}, USR = {LUser, LServer, LResource},
case mnesia:dirty_index_read(session, USR, #session.usr) of case mnesia:dirty_index_read(session, USR,
#session.usr)
of
[] -> [] ->
ok; % Race condition ok; % Race condition
Ss -> Ss ->
@ -559,71 +603,61 @@ route_message(From, To, Packet) ->
Pid ! {route, From, To, Packet} Pid ! {route, From, To, Packet}
end; end;
%% Ignore other priority: %% Ignore other priority:
({_Prio, _Res}) -> ({_Prio, _Res}) -> ok
ok
end, end,
PrioRes); PrioRes);
_ -> _ ->
case xml:get_tag_attr_s("type", Packet) of case xml:get_tag_attr_s(<<"type">>, Packet) of
"error" -> <<"error">> -> ok;
ok; <<"groupchat">> ->
"groupchat" ->
bounce_offline_message(From, To, Packet); bounce_offline_message(From, To, Packet);
"headline" -> <<"headline">> ->
bounce_offline_message(From, To, Packet); bounce_offline_message(From, To, Packet);
_ -> _ ->
case ejabberd_auth:is_user_exists(LUser, LServer) of case ejabberd_auth:is_user_exists(LUser, LServer) of
true -> true ->
case is_privacy_allow(From, To, Packet) of case is_privacy_allow(From, To, Packet) of
true -> true ->
ejabberd_hooks:run(offline_message_hook, ejabberd_hooks:run(offline_message_hook, LServer,
LServer,
[From, To, Packet]); [From, To, Packet]);
false -> false -> ok
ok
end; end;
_ -> _ ->
Err = jlib:make_error_reply( Err = jlib:make_error_reply(Packet,
Packet, ?ERR_SERVICE_UNAVAILABLE), ?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(To, From, Err) ejabberd_router:route(To, From, Err)
end end
end end
end. end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
clean_session_list(Ss) -> clean_session_list(Ss) ->
clean_session_list(lists:keysort(#session.usr, Ss), []). clean_session_list(lists:keysort(#session.usr, Ss), []).
clean_session_list([], Res) -> clean_session_list([], Res) -> Res;
Res; clean_session_list([S], Res) -> [S | Res];
clean_session_list([S], Res) ->
[S | Res];
clean_session_list([S1, S2 | Rest], Res) -> clean_session_list([S1, S2 | Rest], Res) ->
if if S1#session.usr == S2#session.usr ->
S1#session.usr == S2#session.usr -> if S1#session.sid > S2#session.sid ->
if
S1#session.sid > S2#session.sid ->
clean_session_list([S1 | Rest], Res); clean_session_list([S1 | Rest], Res);
true -> true -> clean_session_list([S2 | Rest], Res)
clean_session_list([S2 | Rest], Res)
end; end;
true -> true -> clean_session_list([S2 | Rest], [S1 | Res])
clean_session_list([S2 | Rest], [S1 | Res])
end. end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
get_user_present_resources(LUser, LServer) -> get_user_present_resources(LUser, LServer) ->
US = {LUser, LServer}, US = {LUser, LServer},
case catch mnesia:dirty_index_read(session, US, #session.us) of case catch mnesia:dirty_index_read(session, US,
{'EXIT', _Reason} -> #session.us)
[]; of
{'EXIT', _Reason} -> [];
Ss -> Ss ->
[{S#session.priority, element(3, S#session.usr)} || [{S#session.priority, element(3, S#session.usr)}
S <- clean_session_list(Ss), is_integer(S#session.priority)] || S <- clean_session_list(Ss),
is_integer(S#session.priority)]
end. end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@ -633,59 +667,51 @@ check_for_sessions_to_replace(User, Server, Resource) ->
LUser = jlib:nodeprep(User), LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
LResource = jlib:resourceprep(Resource), LResource = jlib:resourceprep(Resource),
%% TODO: Depending on how this is executed, there could be an unneeded
%% replacement for max_sessions. We need to check this at some point.
check_existing_resources(LUser, LServer, LResource), check_existing_resources(LUser, LServer, LResource),
check_max_sessions(LUser, LServer). check_max_sessions(LUser, LServer).
check_existing_resources(LUser, LServer, LResource) -> check_existing_resources(LUser, LServer, LResource) ->
SIDs = get_resource_sessions(LUser, LServer, LResource), SIDs = get_resource_sessions(LUser, LServer, LResource),
if if SIDs == [] -> ok;
SIDs == [] -> ok;
true -> true ->
%% A connection exist with the same resource. We replace it:
MaxSID = lists:max(SIDs), MaxSID = lists:max(SIDs),
lists:foreach( lists:foreach(fun ({_, Pid} = S) when S /= MaxSID ->
fun({_, Pid} = S) when S /= MaxSID ->
Pid ! replaced; Pid ! replaced;
(_) -> ok (_) -> ok
end, SIDs) end,
SIDs)
end. end.
-spec is_existing_resource(binary(), binary(), binary()) -> boolean().
is_existing_resource(LUser, LServer, LResource) -> is_existing_resource(LUser, LServer, LResource) ->
[] /= get_resource_sessions(LUser, LServer, LResource). [] /= get_resource_sessions(LUser, LServer, LResource).
get_resource_sessions(User, Server, Resource) -> get_resource_sessions(User, Server, Resource) ->
USR = {jlib:nodeprep(User), jlib:nameprep(Server), jlib:resourceprep(Resource)}, USR = {jlib:nodeprep(User), jlib:nameprep(Server),
mnesia:dirty_select( jlib:resourceprep(Resource)},
session, mnesia:dirty_select(session,
[{#session{sid = '$1', usr = USR, _ = '_'}, [], ['$1']}]). [{#session{sid = '$1', usr = USR, _ = '_'}, [],
['$1']}]).
check_max_sessions(LUser, LServer) -> check_max_sessions(LUser, LServer) ->
%% If the max number of sessions for a given is reached, we replace the SIDs = mnesia:dirty_select(session,
%% first one [{#session{sid = '$1', us = {LUser, LServer},
SIDs = mnesia:dirty_select( _ = '_'},
session, [], ['$1']}]),
[{#session{sid = '$1', us = {LUser, LServer}, _ = '_'}, [],
['$1']}]),
MaxSessions = get_max_user_sessions(LUser, LServer), MaxSessions = get_max_user_sessions(LUser, LServer),
if if length(SIDs) =< MaxSessions -> ok;
length(SIDs) =< MaxSessions -> true -> {_, Pid} = lists:min(SIDs), Pid ! replaced
ok;
true ->
{_, Pid} = lists:min(SIDs),
Pid ! replaced
end. end.
%% Get the user_max_session setting %% Get the user_max_session setting
%% This option defines the max number of time a given users are allowed to %% This option defines the max number of time a given users are allowed to
%% log in %% log in
%% Defaults to infinity %% Defaults to infinity
get_max_user_sessions(LUser, Host) -> get_max_user_sessions(LUser, Host) ->
case acl:match_rule( case acl:match_rule(Host, max_user_sessions,
Host, max_user_sessions, jlib:make_jid(LUser, Host, "")) of jlib:make_jid(LUser, Host, <<"">>))
of
Max when is_integer(Max) -> Max; Max when is_integer(Max) -> Max;
infinity -> infinity; infinity -> infinity;
_ -> ?MAX_USER_SESSIONS _ -> ?MAX_USER_SESSIONS
@ -701,69 +727,67 @@ process_iq(From, To, Packet) ->
case ets:lookup(sm_iqtable, {XMLNS, Host}) of case ets:lookup(sm_iqtable, {XMLNS, Host}) of
[{_, Module, Function}] -> [{_, Module, Function}] ->
ResIQ = Module:Function(From, To, IQ), ResIQ = Module:Function(From, To, IQ),
if if ResIQ /= ignore ->
ResIQ /= ignore -> ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
ejabberd_router:route(To, From, true -> ok
jlib:iq_to_xml(ResIQ));
true ->
ok
end; end;
[{_, Module, Function, Opts}] -> [{_, Module, Function, Opts}] ->
gen_iq_handler:handle(Host, Module, Function, Opts, gen_iq_handler:handle(Host, Module, Function, Opts,
From, To, IQ); From, To, IQ);
[] -> [] ->
Err = jlib:make_error_reply( Err = jlib:make_error_reply(Packet,
Packet, ?ERR_SERVICE_UNAVAILABLE), ?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(To, From, Err) ejabberd_router:route(To, From, Err)
end; end;
reply -> reply -> ok;
ok;
_ -> _ ->
Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST), Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
ejabberd_router:route(To, From, Err), ejabberd_router:route(To, From, Err),
ok ok
end. end.
-spec force_update_presence({binary(), binary()}) -> any().
force_update_presence({LUser, _LServer} = US) -> force_update_presence({LUser, _LServer} = US) ->
case catch mnesia:dirty_index_read(session, US, #session.us) of case catch mnesia:dirty_index_read(session, US,
{'EXIT', _Reason} -> #session.us)
ok; of
{'EXIT', _Reason} -> ok;
Ss -> Ss ->
lists:foreach(fun (#session{sid = {_, Pid}}) -> lists:foreach(fun (#session{sid = {_, Pid}}) ->
Pid ! {force_update_presence, LUser} Pid ! {force_update_presence, LUser}
end, Ss) end,
Ss)
end. end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% ejabberd commands %%% ejabberd commands
commands() -> commands() ->
[ [#ejabberd_commands{name = connected_users,
#ejabberd_commands{name = connected_users,
tags = [session], tags = [session],
desc = "List all established sessions", desc = "List all established sessions",
module = ?MODULE, function = connected_users, module = ?MODULE, function = connected_users, args = [],
args = [],
result = {connected_users, {list, {sessions, string}}}}, result = {connected_users, {list, {sessions, string}}}},
#ejabberd_commands{name = connected_users_number, #ejabberd_commands{name = connected_users_number,
tags = [session, stats], tags = [session, stats],
desc = "Get the number of established sessions", desc = "Get the number of established sessions",
module = ?MODULE, function = connected_users_number, module = ?MODULE, function = connected_users_number,
args = [], args = [], result = {num_sessions, integer}},
result = {num_sessions, integer}},
#ejabberd_commands{name = user_resources, #ejabberd_commands{name = user_resources,
tags = [session], tags = [session],
desc = "List user's connected resources", desc = "List user's connected resources",
module = ?MODULE, function = user_resources, module = ?MODULE, function = user_resources,
args = [{user, string}, {host, string}], args = [{user, string}, {host, string}],
result = {resources, {list, {resource, string}}}} result = {resources, {list, {resource, string}}}}].
].
-spec connected_users() -> [binary()].
connected_users() -> connected_users() ->
USRs = dirty_get_sessions_list(), USRs = dirty_get_sessions_list(),
SUSRs = lists:sort(USRs), SUSRs = lists:sort(USRs),
lists:map(fun({U, S, R}) -> [U, $@, S, $/, R] end, SUSRs). lists:map(fun ({U, S, R}) -> <<U/binary, $@, S/binary, $/, R/binary>> end,
SUSRs).
connected_users_number() -> connected_users_number() ->
length(dirty_get_sessions_list()). length(dirty_get_sessions_list()).
@ -778,24 +802,18 @@ user_resources(User, Server) ->
update_tables() -> update_tables() ->
case catch mnesia:table_info(session, attributes) of case catch mnesia:table_info(session, attributes) of
[ur, user, node] -> [ur, user, node] -> mnesia:delete_table(session);
mnesia:delete_table(session); [ur, user, pid] -> mnesia:delete_table(session);
[ur, user, pid] -> [usr, us, pid] -> mnesia:delete_table(session);
mnesia:delete_table(session);
[usr, us, pid] ->
mnesia:delete_table(session);
[sid, usr, us, priority] -> [sid, usr, us, priority] ->
mnesia:delete_table(session); mnesia:delete_table(session);
[sid, usr, us, priority, info] -> [sid, usr, us, priority, info] -> ok;
ok; {'EXIT', _} -> ok
{'EXIT', _} ->
ok
end, end,
case lists:member(presence, mnesia:system_info(tables)) of case lists:member(presence, mnesia:system_info(tables))
true -> of
mnesia:delete_table(presence); true -> mnesia:delete_table(presence);
false -> false -> ok
ok
end, end,
case lists:member(local_session, mnesia:system_info(tables)) of case lists:member(local_session, mnesia:system_info(tables)) of
true -> true ->

View File

@ -25,6 +25,7 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(ejabberd_socket). -module(ejabberd_socket).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
%% API %% API
@ -47,8 +48,29 @@
sockname/1, peername/1]). sockname/1, peername/1]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl").
-record(socket_state, {sockmod, socket, receiver}). -type sockmod() :: ejabberd_http_poll | ejabberd_bosh |
ejabberd_http_bind | ejabberd_http_bindjson |
ejabberd_http_ws | ejabberd_http_wsjson |
gen_tcp | tls | ejabberd_zlib.
-type receiver() :: pid () | atom().
-type socket() :: pid() | inet:socket() |
tls:tls_socket() |
ejabberd_zlib:zlib_socket() |
ejabberd_bosh:bosh_socket() |
ejabberd_http_ws:ws_socket() |
ejabberd_http_poll:poll_socket().
-record(socket_state, {sockmod = gen_tcp :: sockmod(),
socket = self() :: socket(),
receiver = self() :: receiver()}).
-type socket_state() :: #socket_state{}.
-export_type([socket_state/0, sockmod/0]).
-spec start(atom(), sockmod(), socket(), [{atom(), any()}]) -> any().
%%==================================================================== %%====================================================================
%% API %% API
@ -60,54 +82,51 @@
start(Module, SockMod, Socket, Opts) -> start(Module, SockMod, Socket, Opts) ->
case Module:socket_type() of case Module:socket_type() of
xml_stream -> xml_stream ->
MaxStanzaSize = MaxStanzaSize = case lists:keysearch(max_stanza_size, 1,
case lists:keysearch(max_stanza_size, 1, Opts) of Opts)
of
{value, {_, Size}} -> Size; {value, {_, Size}} -> Size;
_ -> infinity _ -> infinity
end, end,
{ReceiverMod, Receiver, RecRef} = {ReceiverMod, Receiver, RecRef} = case catch
case catch SockMod:custom_receiver(Socket) of SockMod:custom_receiver(Socket)
of
{receiver, RecMod, RecPid} -> {receiver, RecMod, RecPid} ->
{RecMod, RecPid, RecMod}; {RecMod, RecPid, RecMod};
_ -> _ ->
RecPid = ejabberd_receiver:start( RecPid =
Socket, SockMod, none, MaxStanzaSize), ejabberd_receiver:start(Socket,
{ejabberd_receiver, RecPid, RecPid} SockMod,
none,
MaxStanzaSize),
{ejabberd_receiver, RecPid,
RecPid}
end, end,
SocketData = #socket_state{sockmod = SockMod, SocketData = #socket_state{sockmod = SockMod,
socket = Socket, socket = Socket, receiver = RecRef},
receiver = RecRef},
case Module:start({?MODULE, SocketData}, Opts) of case Module:start({?MODULE, SocketData}, Opts) of
{ok, Pid} -> {ok, Pid} ->
case SockMod:controlling_process(Socket, Receiver) of case SockMod:controlling_process(Socket, Receiver) of
ok -> ok -> ok;
ok; {error, _Reason} -> SockMod:close(Socket)
{error, _Reason} ->
SockMod:close(Socket)
end, end,
ReceiverMod:become_controller(Receiver, Pid); ReceiverMod:become_controller(Receiver, Pid);
{error, _Reason} -> {error, _Reason} ->
SockMod:close(Socket), SockMod:close(Socket),
case ReceiverMod of case ReceiverMod of
ejabberd_receiver -> ejabberd_receiver -> ReceiverMod:close(Receiver);
ReceiverMod:close(Receiver); _ -> ok
_ ->
ok
end end
end; end;
independent -> independent -> ok;
ok;
raw -> raw ->
case Module:start({SockMod, Socket}, Opts) of case Module:start({SockMod, Socket}, Opts) of
{ok, Pid} -> {ok, Pid} ->
case SockMod:controlling_process(Socket, Pid) of case SockMod:controlling_process(Socket, Pid) of
ok -> ok -> ok;
ok; {error, _Reason} -> SockMod:close(Socket)
{error, _Reason} ->
SockMod:close(Socket)
end; end;
{error, _Reason} -> {error, _Reason} -> SockMod:close(Socket)
SockMod:close(Socket)
end end
end. end.
@ -117,21 +136,18 @@ connect(Addr, Port, Opts) ->
connect(Addr, Port, Opts, Timeout) -> connect(Addr, Port, Opts, Timeout) ->
case gen_tcp:connect(Addr, Port, Opts, Timeout) of case gen_tcp:connect(Addr, Port, Opts, Timeout) of
{ok, Socket} -> {ok, Socket} ->
Receiver = ejabberd_receiver:start(Socket, gen_tcp, none), Receiver = ejabberd_receiver:start(Socket, gen_tcp,
none),
SocketData = #socket_state{sockmod = gen_tcp, SocketData = #socket_state{sockmod = gen_tcp,
socket = Socket, socket = Socket, receiver = Receiver},
receiver = Receiver},
Pid = self(), Pid = self(),
case gen_tcp:controlling_process(Socket, Receiver) of case gen_tcp:controlling_process(Socket, Receiver) of
ok -> ok ->
ejabberd_receiver:become_controller(Receiver, Pid), ejabberd_receiver:become_controller(Receiver, Pid),
{ok, SocketData}; {ok, SocketData};
{error, _Reason} = Error -> {error, _Reason} = Error -> gen_tcp:close(Socket), Error
gen_tcp:close(Socket),
Error
end; end;
{error, _Reason} = Error -> {error, _Reason} = Error -> Error
Error
end. end.
starttls(SocketData, TLSOpts) -> starttls(SocketData, TLSOpts) ->
@ -160,11 +176,12 @@ compress(SocketData, Data) ->
send(SocketData, Data), send(SocketData, Data),
SocketData#socket_state{socket = ZlibSocket, sockmod = ejabberd_zlib}. SocketData#socket_state{socket = ZlibSocket, sockmod = ejabberd_zlib}.
reset_stream(SocketData) when is_pid(SocketData#socket_state.receiver) -> reset_stream(SocketData)
when is_pid(SocketData#socket_state.receiver) ->
ejabberd_receiver:reset_stream(SocketData#socket_state.receiver); ejabberd_receiver:reset_stream(SocketData#socket_state.receiver);
reset_stream(SocketData) when is_atom(SocketData#socket_state.receiver) -> reset_stream(SocketData)
(SocketData#socket_state.receiver):reset_stream( when is_atom(SocketData#socket_state.receiver) ->
SocketData#socket_state.socket). (SocketData#socket_state.receiver):reset_stream(SocketData#socket_state.socket).
%% sockmod=gen_tcp|tls|ejabberd_zlib %% sockmod=gen_tcp|tls|ejabberd_zlib
send(SocketData, Data) -> send(SocketData, Data) ->
@ -182,23 +199,29 @@ send(SocketData, Data) ->
%% Can only be called when in c2s StateData#state.xml_socket is true %% Can only be called when in c2s StateData#state.xml_socket is true
%% This function is used for HTTP bind %% This function is used for HTTP bind
%% sockmod=ejabberd_http_poll|ejabberd_http_bind or any custom module %% sockmod=ejabberd_http_poll|ejabberd_http_bind or any custom module
-spec send_xml(socket_state(), xmlel()) -> any().
send_xml(SocketData, Data) -> send_xml(SocketData, Data) ->
catch (SocketData#socket_state.sockmod):send_xml( catch
SocketData#socket_state.socket, Data). (SocketData#socket_state.sockmod):send_xml(SocketData#socket_state.socket,
Data).
change_shaper(SocketData, Shaper) change_shaper(SocketData, Shaper)
when is_pid(SocketData#socket_state.receiver) -> when is_pid(SocketData#socket_state.receiver) ->
ejabberd_receiver:change_shaper(SocketData#socket_state.receiver, Shaper); ejabberd_receiver:change_shaper(SocketData#socket_state.receiver,
Shaper);
change_shaper(SocketData, Shaper) change_shaper(SocketData, Shaper)
when is_atom(SocketData#socket_state.receiver) -> when is_atom(SocketData#socket_state.receiver) ->
(SocketData#socket_state.receiver):change_shaper( (SocketData#socket_state.receiver):change_shaper(SocketData#socket_state.socket,
SocketData#socket_state.socket, Shaper). Shaper).
monitor(SocketData) when is_pid(SocketData#socket_state.receiver) -> monitor(SocketData)
erlang:monitor(process, SocketData#socket_state.receiver); when is_pid(SocketData#socket_state.receiver) ->
monitor(SocketData) when is_atom(SocketData#socket_state.receiver) -> erlang:monitor(process,
(SocketData#socket_state.receiver):monitor( SocketData#socket_state.receiver);
SocketData#socket_state.socket). monitor(SocketData)
when is_atom(SocketData#socket_state.receiver) ->
(SocketData#socket_state.receiver):monitor(SocketData#socket_state.socket).
get_sockmod(SocketData) -> get_sockmod(SocketData) ->
SocketData#socket_state.sockmod. SocketData#socket_state.sockmod.
@ -212,22 +235,21 @@ get_verify_result(SocketData) ->
close(SocketData) -> close(SocketData) ->
ejabberd_receiver:close(SocketData#socket_state.receiver). ejabberd_receiver:close(SocketData#socket_state.receiver).
sockname(#socket_state{sockmod = SockMod, socket = Socket}) -> sockname(#socket_state{sockmod = SockMod,
socket = Socket}) ->
case SockMod of case SockMod of
gen_tcp -> gen_tcp -> inet:sockname(Socket);
inet:sockname(Socket); _ -> SockMod:sockname(Socket)
_ ->
SockMod:sockname(Socket)
end. end.
peername(#socket_state{sockmod = SockMod, socket = Socket}) -> peername(#socket_state{sockmod = SockMod,
socket = Socket}) ->
case SockMod of case SockMod of
gen_tcp -> gen_tcp -> inet:peername(Socket);
inet:peername(Socket); _ -> SockMod:peername(Socket)
_ ->
SockMod:peername(Socket)
end. end.
%%==================================================================== %%====================================================================
%% Internal functions %% Internal functions
%%==================================================================== %%====================================================================
%====================================================================

View File

@ -63,6 +63,13 @@ init([]) ->
brutal_kill, brutal_kill,
worker, worker,
[ejabberd_router]}, [ejabberd_router]},
Router_multicast =
{ejabberd_router_multicast,
{ejabberd_router_multicast, start_link, []},
permanent,
brutal_kill,
worker,
[ejabberd_router_multicast]},
SM = SM =
{ejabberd_sm, {ejabberd_sm,
{ejabberd_sm, start_link, []}, {ejabberd_sm, start_link, []},
@ -189,6 +196,7 @@ init([]) ->
NodeGroups, NodeGroups,
SystemMonitor, SystemMonitor,
Router, Router,
Router_multicast,
SM, SM,
S2S, S2S,
Local, Local,

View File

@ -25,20 +25,21 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(ejabberd_system_monitor). -module(ejabberd_system_monitor).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(gen_server). -behaviour(gen_server).
%% API %% API
-export([start_link/0, -export([start_link/0, process_command/3,
process_command/3,
process_remote_command/1]). process_remote_command/1]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2,
terminate/2, code_change/3]). handle_info/2, terminate/2, code_change/3]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl"). -include("jlib.hrl").
-record(state, {}). -record(state, {}).
@ -51,37 +52,36 @@
%% Description: Starts the server %% Description: Starts the server
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
start_link() -> start_link() ->
LH = case ejabberd_config:get_local_option(watchdog_large_heap) of LH = ejabberd_config:get_local_option(
I when is_integer(I) -> I; watchdog_large_heap,
_ -> 1000000 fun(I) when is_integer(I), I > 0 -> I end,
end, 1000000),
Opts = [{large_heap, LH}], Opts = [{large_heap, LH}],
gen_server:start_link({local, ?MODULE}, ?MODULE, Opts, []). gen_server:start_link({local, ?MODULE}, ?MODULE, Opts,
[]).
process_command(From, To, Packet) -> process_command(From, To, Packet) ->
case To of case To of
#jid{luser = "", lresource = "watchdog"} -> #jid{luser = <<"">>, lresource = <<"watchdog">>} ->
{xmlelement, Name, _Attrs, _Els} = Packet, #xmlel{name = Name} = Packet,
case Name of case Name of
"message" -> <<"message">> ->
LFrom = jlib:jid_tolower(jlib:jid_remove_resource(From)), LFrom =
jlib:jid_tolower(jlib:jid_remove_resource(From)),
case lists:member(LFrom, get_admin_jids()) of case lists:member(LFrom, get_admin_jids()) of
true -> true ->
Body = xml:get_path_s( Body = xml:get_path_s(Packet,
Packet, [{elem, "body"}, cdata]), [{elem, <<"body">>}, cdata]),
spawn(fun () -> spawn(fun () ->
process_flag(priority, high), process_flag(priority, high),
process_command1(From, To, Body) process_command1(From, To, Body)
end), end),
stop; stop;
false -> false -> ok
ok
end; end;
_ -> _ -> ok
ok
end; end;
_ -> _ -> ok
ok
end. end.
%%==================================================================== %%====================================================================
@ -99,11 +99,11 @@ init(Opts) ->
LH = proplists:get_value(large_heap, Opts), LH = proplists:get_value(large_heap, Opts),
process_flag(priority, high), process_flag(priority, high),
erlang:system_monitor(self(), [{large_heap, LH}]), erlang:system_monitor(self(), [{large_heap, LH}]),
lists:foreach( lists:foreach(fun (Host) ->
fun(Host) ->
ejabberd_hooks:add(local_send_to_resource_hook, Host, ejabberd_hooks:add(local_send_to_resource_hook, Host,
?MODULE, process_command, 50) ?MODULE, process_command, 50)
end, ?MYHOSTS), end,
?MYHOSTS),
{ok, #state{}}. {ok, #state{}}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -117,18 +117,20 @@ init(Opts) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_call({get, large_heap}, _From, State) -> handle_call({get, large_heap}, _From, State) ->
{reply, get_large_heap(), State}; {reply, get_large_heap(), State};
handle_call({set, large_heap, NewValue}, _From, State) -> handle_call({set, large_heap, NewValue}, _From,
MonSettings = erlang:system_monitor(self(), [{large_heap, NewValue}]), State) ->
MonSettings = erlang:system_monitor(self(),
[{large_heap, NewValue}]),
OldLH = get_large_heap(MonSettings), OldLH = get_large_heap(MonSettings),
NewLH = get_large_heap(), NewLH = get_large_heap(),
{reply, {lh_changed, OldLH, NewLH}, State}; {reply, {lh_changed, OldLH, NewLH}, State};
handle_call(_Request, _From, State) -> handle_call(_Request, _From, State) ->
Reply = ok, Reply = ok, {reply, Reply, State}.
{reply, Reply, State}.
get_large_heap() -> get_large_heap() ->
MonSettings = erlang:system_monitor(), MonSettings = erlang:system_monitor(),
get_large_heap(MonSettings). get_large_heap(MonSettings).
get_large_heap(MonSettings) -> get_large_heap(MonSettings) ->
{_MonitorPid, Options} = MonSettings, {_MonitorPid, Options} = MonSettings,
proplists:get_value(large_heap, Options). proplists:get_value(large_heap, Options).
@ -139,8 +141,7 @@ get_large_heap(MonSettings) ->
%% {stop, Reason, State} %% {stop, Reason, State}
%% Description: Handling cast messages %% Description: Handling cast messages
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_cast(_Msg, State) -> handle_cast(_Msg, State) -> {noreply, State}.
{noreply, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} | %% Function: handle_info(Info, State) -> {noreply, State} |
@ -154,8 +155,7 @@ handle_info({monitor, Pid, large_heap, Info}, State) ->
process_large_heap(Pid, Info) process_large_heap(Pid, Info)
end), end),
{noreply, State}; {noreply, State};
handle_info(_Info, State) -> handle_info(_Info, State) -> {noreply, State}.
{noreply, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void() %% Function: terminate(Reason, State) -> void()
@ -164,63 +164,48 @@ handle_info(_Info, State) ->
%% cleaning up. When it returns, the gen_server terminates with Reason. %% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored. %% The return value is ignored.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
terminate(_Reason, _State) -> terminate(_Reason, _State) -> ok.
ok.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed %% Description: Convert process state when code is changed
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) -> {ok, State}.
{ok, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%%% Internal functions %%% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
process_large_heap(Pid, Info) -> process_large_heap(Pid, Info) ->
Host = ?MYNAME, Host = (?MYNAME),
case ejabberd_config:get_local_option(watchdog_admins) of JIDs = get_admin_jids(),
JIDs when is_list(JIDs),
JIDs /= [] ->
DetailedInfo = detailed_info(Pid), DetailedInfo = detailed_info(Pid),
Body = io_lib:format( Body = iolist_to_binary(
"(~w) The process ~w is consuming too much memory:~n~p~n" io_lib:format("(~w) The process ~w is consuming too "
"~s", "much memory:~n~p~n~s",
[node(), Pid, Info, DetailedInfo]), [node(), Pid, Info, DetailedInfo])),
From = jlib:make_jid("", Host, "watchdog"), From = jlib:make_jid(<<"">>, Host, <<"watchdog">>),
lists:foreach( lists:foreach(fun (JID) ->
fun(S) -> send_message(From, jlib:make_jid(JID), Body)
case jlib:string_to_jid(S) of end, JIDs).
error -> ok;
JID ->
send_message(From, JID, Body)
end
end, JIDs);
_ ->
ok
end.
send_message(From, To, Body) -> send_message(From, To, Body) ->
ejabberd_router:route( ejabberd_router:route(From, To,
From, To, #xmlel{name = <<"message">>,
{xmlelement, "message", [{"type", "chat"}], attrs = [{<<"type">>, <<"chat">>}],
[{xmlelement, "body", [], children =
[{xmlcdata, lists:flatten(Body)}]}]}). [#xmlel{name = <<"body">>, attrs = [],
children =
[{xmlcdata, Body}]}]}).
get_admin_jids() -> get_admin_jids() ->
case ejabberd_config:get_local_option(watchdog_admins) of ejabberd_config:get_local_option(
JIDs when is_list(JIDs) -> watchdog_admins,
lists:flatmap( fun(JIDs) ->
fun(S) -> [jlib:jid_tolower(
case jlib:string_to_jid(S) of jlib:string_to_jid(
error -> []; iolist_to_binary(S))) || S <- JIDs]
JID -> [jlib:jid_tolower(JID)] end, []).
end
end, JIDs);
_ ->
[]
end.
detailed_info(Pid) -> detailed_info(Pid) ->
case process_info(Pid, dictionary) of case process_info(Pid, dictionary) of
@ -228,140 +213,122 @@ detailed_info(Pid) ->
case lists:keysearch('$ancestors', 1, Dict) of case lists:keysearch('$ancestors', 1, Dict) of
{value, {'$ancestors', [Sup | _]}} -> {value, {'$ancestors', [Sup | _]}} ->
case Sup of case Sup of
ejabberd_c2s_sup -> ejabberd_c2s_sup -> c2s_info(Pid);
c2s_info(Pid); ejabberd_s2s_out_sup -> s2s_out_info(Pid);
ejabberd_s2s_out_sup -> ejabberd_service_sup -> service_info(Pid);
s2s_out_info(Pid); _ -> detailed_info1(Pid)
ejabberd_service_sup ->
service_info(Pid);
_ ->
detailed_info1(Pid)
end; end;
_ -> _ -> detailed_info1(Pid)
detailed_info1(Pid)
end; end;
_ -> _ -> detailed_info1(Pid)
detailed_info1(Pid)
end. end.
detailed_info1(Pid) -> detailed_info1(Pid) ->
io_lib:format( io_lib:format("~p",
"~p", [[process_info(Pid, current_function), [[process_info(Pid, current_function),
process_info(Pid, initial_call), process_info(Pid, initial_call),
process_info(Pid, message_queue_len), process_info(Pid, message_queue_len),
process_info(Pid, links), process_info(Pid, links), process_info(Pid, dictionary),
process_info(Pid, dictionary),
process_info(Pid, heap_size), process_info(Pid, heap_size),
process_info(Pid, stack_size) process_info(Pid, stack_size)]]).
]]).
c2s_info(Pid) -> c2s_info(Pid) ->
["Process type: c2s", [<<"Process type: c2s">>, check_send_queue(Pid),
check_send_queue(Pid), <<"\n">>,
"\n",
io_lib:format("Command to kill this process: kill ~s ~w", io_lib:format("Command to kill this process: kill ~s ~w",
[atom_to_list(node()), Pid])]. [iolist_to_binary(atom_to_list(node())), Pid])].
s2s_out_info(Pid) -> s2s_out_info(Pid) ->
FromTo = mnesia:dirty_select( FromTo = mnesia:dirty_select(s2s,
s2s, [{{s2s, '$1', Pid, '_'}, [], ['$1']}]), [{{s2s, '$1', Pid, '_'}, [], ['$1']}]),
["Process type: s2s_out", [<<"Process type: s2s_out">>,
case FromTo of case FromTo of
[{From, To}] -> [{From, To}] ->
"\n" ++ io_lib:format("S2S connection: from ~s to ~s", <<"\n",
[From, To]); (io_lib:format("S2S connection: from ~s to ~s",
_ -> [From, To]))/binary>>;
"" _ -> <<"">>
end, end,
check_send_queue(Pid), check_send_queue(Pid), <<"\n">>,
"\n",
io_lib:format("Command to kill this process: kill ~s ~w", io_lib:format("Command to kill this process: kill ~s ~w",
[atom_to_list(node()), Pid])]. [iolist_to_binary(atom_to_list(node())), Pid])].
service_info(Pid) -> service_info(Pid) ->
Routes = mnesia:dirty_select( Routes = mnesia:dirty_select(route,
route, [{{route, '$1', Pid, '_'}, [], ['$1']}]), [{{route, '$1', Pid, '_'}, [], ['$1']}]),
["Process type: s2s_out", [<<"Process type: s2s_out">>,
case Routes of case Routes of
[Route] -> [Route] -> <<"\nServiced domain: ", Route/binary>>;
"\nServiced domain: " ++ Route; _ -> <<"">>
_ ->
""
end, end,
check_send_queue(Pid), check_send_queue(Pid), <<"\n">>,
"\n",
io_lib:format("Command to kill this process: kill ~s ~w", io_lib:format("Command to kill this process: kill ~s ~w",
[atom_to_list(node()), Pid])]. [iolist_to_binary(atom_to_list(node())), Pid])].
check_send_queue(Pid) -> check_send_queue(Pid) ->
case {process_info(Pid, current_function), case {process_info(Pid, current_function),
process_info(Pid, message_queue_len)} of process_info(Pid, message_queue_len)}
of
{{current_function, MFA}, {message_queue_len, MLen}} -> {{current_function, MFA}, {message_queue_len, MLen}} ->
if if MLen > 100 ->
MLen > 100 ->
case MFA of case MFA of
{prim_inet, send, 2} -> {prim_inet, send, 2} ->
"\nPossible reason: the process is blocked " <<"\nPossible reason: the process is blocked "
"trying to send data over its TCP connection."; "trying to send data over its TCP connection.">>;
{M, F, A} -> {M, F, A} ->
["\nPossible reason: the process can't process " [<<"\nPossible reason: the process can't "
"messages faster than they arrive. ", "process messages faster than they arrive. ">>,
io_lib:format("Current function is ~w:~w/~w", io_lib:format("Current function is ~w:~w/~w",
[M, F, A]) [M, F, A])]
]
end; end;
true -> true -> <<"">>
""
end; end;
_ -> _ -> <<"">>
""
end. end.
process_command1(From, To, Body) -> process_command1(From, To, Body) ->
process_command2(string:tokens(Body, " "), From, To). process_command2(str:tokens(Body, <<" ">>), From, To).
process_command2(["kill", SNode, SPid], From, To) -> process_command2([<<"kill">>, SNode, SPid], From, To) ->
Node = list_to_atom(SNode), Node = jlib:binary_to_atom(SNode),
remote_command(Node, [kill, SPid], From, To); remote_command(Node, [kill, SPid], From, To);
process_command2(["showlh", SNode], From, To) -> process_command2([<<"showlh">>, SNode], From, To) ->
Node = list_to_atom(SNode), Node = jlib:binary_to_atom(SNode),
remote_command(Node, [showlh], From, To); remote_command(Node, [showlh], From, To);
process_command2(["setlh", SNode, NewValueString], From, To) -> process_command2([<<"setlh">>, SNode, NewValueString],
Node = list_to_atom(SNode), From, To) ->
NewValue = list_to_integer(NewValueString), Node = jlib:binary_to_atom(SNode),
NewValue = jlib:binary_to_integer(NewValueString),
remote_command(Node, [setlh, NewValue], From, To); remote_command(Node, [setlh, NewValue], From, To);
process_command2(["help"], From, To) -> process_command2([<<"help">>], From, To) ->
send_message(To, From, help()); send_message(To, From, help());
process_command2(_, From, To) -> process_command2(_, From, To) ->
send_message(To, From, help()). send_message(To, From, help()).
help() -> help() ->
"Commands:\n" <<"Commands:\n kill <node> <pid>\n showlh "
" kill <node> <pid>\n" "<node>\n setlh <node> <integer>">>.
" showlh <node>\n"
" setlh <node> <integer>".
remote_command(Node, Args, From, To) -> remote_command(Node, Args, From, To) ->
Message = Message = case rpc:call(Node, ?MODULE,
case rpc:call(Node, ?MODULE, process_remote_command, [Args]) of process_remote_command, [Args])
of
{badrpc, Reason} -> {badrpc, Reason} ->
io_lib:format("Command failed:~n~p", [Reason]); io_lib:format("Command failed:~n~p", [Reason]);
Result -> Result -> Result
Result
end, end,
send_message(To, From, Message). send_message(To, From, iolist_to_binary(Message)).
process_remote_command([kill, SPid]) -> process_remote_command([kill, SPid]) ->
exit(list_to_pid(SPid), kill), exit(list_to_pid(SPid), kill), <<"ok">>;
"ok";
process_remote_command([showlh]) -> process_remote_command([showlh]) ->
Res = gen_server:call(ejabberd_system_monitor, {get, large_heap}), Res = gen_server:call(ejabberd_system_monitor,
{get, large_heap}),
io_lib:format("Current large heap: ~p", [Res]); io_lib:format("Current large heap: ~p", [Res]);
process_remote_command([setlh, NewValue]) -> process_remote_command([setlh, NewValue]) ->
{lh_changed, OldLH, NewLH} = gen_server:call(ejabberd_system_monitor, {set, large_heap, NewValue}), {lh_changed, OldLH, NewLH} =
io_lib:format("Result of set large heap: ~p --> ~p", [OldLH, NewLH]); gen_server:call(ejabberd_system_monitor,
process_remote_command(_) -> {set, large_heap, NewValue}),
throw(unknown_command). io_lib:format("Result of set large heap: ~p --> ~p",
[OldLH, NewLH]);
process_remote_command(_) -> throw(unknown_command).

View File

@ -25,6 +25,7 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(ejabberd_tmp_sup). -module(ejabberd_tmp_sup).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-export([start_link/2, init/1]). -export([start_link/2, init/1]).
@ -32,8 +33,8 @@
start_link(Name, Module) -> start_link(Name, Module) ->
supervisor:start_link({local, Name}, ?MODULE, Module). supervisor:start_link({local, Name}, ?MODULE, Module).
init(Module) -> init(Module) ->
{ok, {{simple_one_for_one, 10, 1}, {ok,
[{undefined, {Module, start_link, []}, {{simple_one_for_one, 10, 1},
temporary, brutal_kill, worker, [Module]}]}}. [{undefined, {Module, start_link, []}, temporary,
brutal_kill, worker, [Module]}]}}.

View File

@ -71,12 +71,7 @@ update(ModulesToUpdate) ->
%% But OTP R14B04 and newer provide release_handler_1:eval_script/5 %% But OTP R14B04 and newer provide release_handler_1:eval_script/5
%% Dialyzer reports a call to missing function; don't worry. %% Dialyzer reports a call to missing function; don't worry.
eval_script(Script, Apps, LibDirs) -> eval_script(Script, Apps, LibDirs) ->
case lists:member({eval_script, 5}, release_handler_1:module_info(exports)) of release_handler_1:eval_script(Script, Apps, LibDirs, [], []).
true ->
release_handler_1:eval_script(Script, Apps, LibDirs, [], []);
false ->
release_handler_1:eval_script(Script, Apps, LibDirs)
end.
%% Get information about the modified modules %% Get information about the modified modules
update_info() -> update_info() ->

View File

@ -26,7 +26,7 @@ EFLAGS += -pz ..
# make debug=true to compile Erlang module with debug informations. # make debug=true to compile Erlang module with debug informations.
ifdef debug ifdef debug
EFLAGS+=+debug_info +export_all EFLAGS+=+debug_info
endif endif
ERLSHLIBS = ../ejabberd_zlib_drv.so ERLSHLIBS = ../ejabberd_zlib_drv.so

View File

@ -25,169 +25,184 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(ejabberd_zlib). -module(ejabberd_zlib).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(gen_server). -behaviour(gen_server).
-export([start/0, start_link/0, -export([start/0, start_link/0, enable_zlib/2,
enable_zlib/2, disable_zlib/1, disable_zlib/1, send/2, recv/2, recv/3, recv_data/2,
send/2, setopts/2, sockname/1, peername/1, get_sockmod/1,
recv/2, recv/3, recv_data/2, controlling_process/2, close/1]).
setopts/2,
sockname/1, peername/1,
get_sockmod/1,
controlling_process/2,
close/1]).
%% Internal exports, call-back functions. %% Internal exports, call-back functions.
-export([init/1, -export([init/1, handle_call/3, handle_cast/2,
handle_call/3, handle_info/2, code_change/3, terminate/2]).
handle_cast/2,
handle_info/2,
code_change/3,
terminate/2]).
-define(DEFLATE, 1). -define(DEFLATE, 1).
-define(INFLATE, 2). -define(INFLATE, 2).
-record(zlibsock, {sockmod, socket, zlibport}). -record(zlibsock, {sockmod :: atom(),
socket :: inet:socket(),
zlibport :: port()}).
-type zlib_socket() :: #zlibsock{}.
-export_type([zlib_socket/0]).
start() -> start() ->
gen_server:start({local, ?MODULE}, ?MODULE, [], []). gen_server:start({local, ?MODULE}, ?MODULE, [], []).
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [],
[]).
init([]) -> init([]) ->
case erl_ddll:load_driver(ejabberd:get_so_path(), ejabberd_zlib_drv) of case erl_ddll:load_driver(ejabberd:get_so_path(),
ejabberd_zlib_drv)
of
ok -> ok; ok -> ok;
{error, already_loaded} -> ok {error, already_loaded} -> ok
end, end,
Port = open_port({spawn, "ejabberd_zlib_drv"}, [binary]), Port = open_port({spawn, "ejabberd_zlib_drv"},
[binary]),
{ok, Port}. {ok, Port}.
%%% -------------------------------------------------------- %%% --------------------------------------------------------
%%% The call-back functions. %%% The call-back functions.
%%% -------------------------------------------------------- %%% --------------------------------------------------------
handle_call(_, _, State) -> handle_call(_, _, State) -> {noreply, State}.
{noreply, State}.
handle_cast(_, State) -> handle_cast(_, State) -> {noreply, State}.
{noreply, State}.
handle_info({'EXIT', Port, Reason}, Port) -> handle_info({'EXIT', Port, Reason}, Port) ->
{stop, {port_died, Reason}, Port}; {stop, {port_died, Reason}, Port};
handle_info({'EXIT', _Pid, _Reason}, Port) -> handle_info({'EXIT', _Pid, _Reason}, Port) ->
{noreply, Port}; {noreply, Port};
handle_info(_, State) -> {noreply, State}.
handle_info(_, State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}.
{noreply, State}.
code_change(_OldVsn, State, _Extra) -> terminate(_Reason, Port) -> Port ! {self, close}, ok.
{ok, State}.
terminate(_Reason, Port) ->
Port ! {self, close},
ok.
-spec enable_zlib(atom(), inet:socket()) -> {ok, zlib_socket()}.
enable_zlib(SockMod, Socket) -> enable_zlib(SockMod, Socket) ->
case erl_ddll:load_driver(ejabberd:get_so_path(), ejabberd_zlib_drv) of case erl_ddll:load_driver(ejabberd:get_so_path(),
ejabberd_zlib_drv)
of
ok -> ok; ok -> ok;
{error, already_loaded} -> ok {error, already_loaded} -> ok
end, end,
Port = open_port({spawn, "ejabberd_zlib_drv"}, [binary]), Port = open_port({spawn, "ejabberd_zlib_drv"},
{ok, #zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}}. [binary]),
{ok,
#zlibsock{sockmod = SockMod, socket = Socket,
zlibport = Port}}.
disable_zlib(#zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}) -> -spec disable_zlib(zlib_socket()) -> {atom(), inet:socket()}.
port_close(Port),
{SockMod, Socket}.
recv(Socket, Length) -> disable_zlib(#zlibsock{sockmod = SockMod,
recv(Socket, Length, infinity). socket = Socket, zlibport = Port}) ->
recv(#zlibsock{sockmod = SockMod, socket = Socket} = ZlibSock, port_close(Port), {SockMod, Socket}.
-spec recv(zlib_socket(), number()) -> {ok, binary()} | {error, any()}.
recv(Socket, Length) -> recv(Socket, Length, infinity).
-spec recv(zlib_socket(), number(), timeout()) -> {ok, binary()} |
{error, any()}.
recv(#zlibsock{sockmod = SockMod, socket = Socket} =
ZlibSock,
Length, Timeout) -> Length, Timeout) ->
case SockMod:recv(Socket, Length, Timeout) of case SockMod:recv(Socket, Length, Timeout) of
{ok, Packet} -> {ok, Packet} -> recv_data(ZlibSock, Packet);
recv_data(ZlibSock, Packet); {error, _Reason} = Error -> Error
{error, _Reason} = Error ->
Error
end. end.
recv_data(#zlibsock{sockmod = SockMod, socket = Socket} = ZlibSock, Packet) -> -spec recv_data(zlib_socket(), iodata()) -> {ok, binary()} | {error, any()}.
recv_data(#zlibsock{sockmod = SockMod,
socket = Socket} =
ZlibSock,
Packet) ->
case SockMod of case SockMod of
gen_tcp -> gen_tcp -> recv_data2(ZlibSock, Packet);
recv_data2(ZlibSock, Packet);
_ -> _ ->
case SockMod:recv_data(Socket, Packet) of case SockMod:recv_data(Socket, Packet) of
{ok, Packet2} -> {ok, Packet2} -> recv_data2(ZlibSock, Packet2);
recv_data2(ZlibSock, Packet2); Error -> Error
Error ->
Error
end end
end. end.
recv_data2(ZlibSock, Packet) -> recv_data2(ZlibSock, Packet) ->
case catch recv_data1(ZlibSock, Packet) of case catch recv_data1(ZlibSock, Packet) of
{'EXIT', Reason} -> {'EXIT', Reason} -> {error, Reason};
{error, Reason}; Res -> Res
Res ->
Res
end. end.
recv_data1(#zlibsock{zlibport = Port} = _ZlibSock, Packet) -> recv_data1(#zlibsock{zlibport = Port} = _ZlibSock,
Packet) ->
case port_control(Port, ?INFLATE, Packet) of case port_control(Port, ?INFLATE, Packet) of
<<0, In/binary>> -> <<0, In/binary>> -> {ok, In};
{ok, In}; <<1, Error/binary>> -> {error, (Error)}
<<1, Error/binary>> ->
{error, binary_to_list(Error)}
end. end.
send(#zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}, -spec send(zlib_socket(), iodata()) -> ok | {error, binary() | inet:posix()}.
send(#zlibsock{sockmod = SockMod, socket = Socket,
zlibport = Port},
Packet) -> Packet) ->
case port_control(Port, ?DEFLATE, Packet) of case port_control(Port, ?DEFLATE, Packet) of
<<0, Out/binary>> -> <<0, Out/binary>> -> SockMod:send(Socket, Out);
SockMod:send(Socket, Out); <<1, Error/binary>> -> {error, (Error)}
<<1, Error/binary>> ->
{error, binary_to_list(Error)}
end. end.
-spec setopts(zlib_socket(), list()) -> ok | {error, inet:posix()}.
setopts(#zlibsock{sockmod = SockMod, socket = Socket}, Opts) -> setopts(#zlibsock{sockmod = SockMod, socket = Socket},
Opts) ->
case SockMod of case SockMod of
gen_tcp -> gen_tcp -> inet:setopts(Socket, Opts);
inet:setopts(Socket, Opts); _ -> SockMod:setopts(Socket, Opts)
_ ->
SockMod:setopts(Socket, Opts)
end. end.
sockname(#zlibsock{sockmod = SockMod, socket = Socket}) -> -spec sockname(zlib_socket()) -> {ok, {inet:ip_address(), inet:port_number()}} |
{error, inet:posix()}.
sockname(#zlibsock{sockmod = SockMod,
socket = Socket}) ->
case SockMod of case SockMod of
gen_tcp -> gen_tcp -> inet:sockname(Socket);
inet:sockname(Socket); _ -> SockMod:sockname(Socket)
_ ->
SockMod:sockname(Socket)
end. end.
get_sockmod(#zlibsock{sockmod = SockMod}) -> -spec get_sockmod(zlib_socket()) -> atom().
SockMod.
peername(#zlibsock{sockmod = SockMod, socket = Socket}) -> get_sockmod(#zlibsock{sockmod = SockMod}) -> SockMod.
-spec peername(zlib_socket()) -> {ok, {inet:ip_address(), inet:port_number()}} |
{error, inet:posix()}.
peername(#zlibsock{sockmod = SockMod,
socket = Socket}) ->
case SockMod of case SockMod of
gen_tcp -> gen_tcp -> inet:peername(Socket);
inet:peername(Socket); _ -> SockMod:peername(Socket)
_ ->
SockMod:peername(Socket)
end. end.
controlling_process(#zlibsock{sockmod = SockMod, socket = Socket}, Pid) -> -spec controlling_process(zlib_socket(), pid()) -> ok | {error, atom()}.
controlling_process(#zlibsock{sockmod = SockMod,
socket = Socket},
Pid) ->
SockMod:controlling_process(Socket, Pid). SockMod:controlling_process(Socket, Pid).
close(#zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}) -> -spec close(zlib_socket()) -> true.
SockMod:close(Socket),
port_close(Port).
close(#zlibsock{sockmod = SockMod, socket = Socket,
zlibport = Port}) ->
SockMod:close(Socket), port_close(Port).

View File

@ -53,6 +53,7 @@ if [ "$EJABBERD_DOC_PATH" = "" ] ; then
fi fi
if [ "$ERLANG_NODE_ARG" != "" ] ; then if [ "$ERLANG_NODE_ARG" != "" ] ; then
ERLANG_NODE=$ERLANG_NODE_ARG ERLANG_NODE=$ERLANG_NODE_ARG
NODE=${ERLANG_NODE%@*}
fi fi
# check the proper system user is used # check the proper system user is used

View File

@ -25,59 +25,12 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(ejd2odbc). -module(ejd2odbc).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
%% External exports -export([export/2, export/3]).
-export([export_passwd/2,
export_roster/2,
export_offline/2,
export_last/2,
export_vcard/2,
export_vcard_search/2,
export_vcard_xupdate/2,
export_private_storage/2,
export_privacy/2,
export_motd/2,
export_motd_users/2,
export_irc_custom/2,
export_sr_group/2,
export_sr_user/2,
export_muc_room/2,
export_muc_registered/2]).
-include("ejabberd.hrl"). -define(MAX_RECORDS_PER_TRANSACTION, 100).
-include("jlib.hrl").
-include("mod_roster.hrl").
-include("mod_privacy.hrl").
-record(offline_msg, {us, timestamp, expire, from, to, packet}).
-record(last_activity, {us, timestamp, status}).
-record(vcard, {us, vcard}).
-record(vcard_xupdate, {us, hash}).
-record(vcard_search, {us,
user, luser,
fn, lfn,
family, lfamily,
given, lgiven,
middle, lmiddle,
nickname, lnickname,
bday, lbday,
ctry, lctry,
locality, llocality,
email, lemail,
orgname, lorgname,
orgunit, lorgunit
}).
-record(private_storage, {usns, xml}).
-record(irc_custom, {us_host, data}).
-record(muc_room, {name_host, opts}).
-record(muc_registered, {us_host, nick}).
-record(sr_group, {group_host, opts}).
-record(sr_user, {us, group_host}).
-record(motd, {server, packet}).
-record(motd_users, {us, dummy = []}).
-define(MAX_RECORDS_PER_TRANSACTION, 1000).
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% API %%% API
@ -89,397 +42,43 @@
%%% - Output can be either odbc to export to the configured relational %%% - Output can be either odbc to export to the configured relational
%%% database or "Filename" to export to text file. %%% database or "Filename" to export to text file.
export_passwd(Server, Output) -> export(Server, Output) ->
export_common( LServer = jlib:nameprep(iolist_to_binary(Server)),
Server, passwd, Output, Modules = [ejabberd_auth,
fun(_Host, {passwd, {LUser, LServer}, {scram, _, _, _, _}} = _R) -> mod_announce,
?INFO_MSG("You are trying to export the authentication " mod_caps,
"information of the account ~s@~s, but his password " mod_irc,
"is stored as SCRAM, and ejabberd ODBC authentication " mod_last,
"doesn't support SCRAM.", [LUser, LServer]), mod_muc,
[]; mod_offline,
(Host, {passwd, {LUser, LServer}, Password} = _R) mod_privacy,
when LServer == Host -> mod_private,
Username = ejabberd_odbc:escape(LUser), mod_roster,
Pass = ejabberd_odbc:escape(Password), mod_shared_roster,
["delete from users where username='", Username ,"';" mod_vcard,
"insert into users(username, password) " mod_vcard_xupdate],
"values ('", Username, "', '", Pass, "');"]; IO = prepare_output(Output),
(_Host, _R) -> lists:foreach(
[] fun(Module) ->
end). export(LServer, IO, Module)
end, Modules),
close_output(Output, IO).
export_roster(Server, Output) -> export(Server, Output, Module) ->
export_common( LServer = jlib:nameprep(iolist_to_binary(Server)),
Server, roster, Output, IO = prepare_output(Output),
fun(Host, #roster{usj = {LUser, LServer, LJID}} = R) lists:foreach(
when LServer == Host -> fun({Table, ConvertFun}) ->
Username = ejabberd_odbc:escape(LUser), export(LServer, Table, IO, ConvertFun)
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)), end, Module:export(Server)),
ItemVals = record_to_string(R), close_output(Output, IO).
ItemGroups = groups_to_string(R),
["delete from rosterusers "
" where username='", Username, "' "
" and jid='", SJID, "';"
"insert into rosterusers("
" username, jid, nick, "
" subscription, ask, askmessage, "
" server, subscribe, type) "
" values ", ItemVals, ";"
"delete from rostergroups "
" where username='", Username, "' "
" and jid='", SJID, "';",
[["insert into rostergroups("
" username, jid, grp) "
" values ", ItemGroup, ";"] ||
ItemGroup <- ItemGroups]];
(_Host, _R) ->
[]
end).
export_offline(Server, Output) ->
export_common(
Server, offline_msg, Output,
fun(Host, #offline_msg{us = {LUser, LServer},
timestamp = TimeStamp,
from = From,
to = To,
packet = Packet})
when LServer == Host ->
Username = ejabberd_odbc:escape(LUser),
{xmlelement, Name, Attrs, Els} = Packet,
Attrs2 = jlib:replace_from_to_attrs(
jlib:jid_to_string(From),
jlib:jid_to_string(To),
Attrs),
NewPacket = {xmlelement, Name, Attrs2,
Els ++
[jlib:timestamp_to_xml(
calendar:now_to_universal_time(TimeStamp),
utc,
jlib:make_jid("", Server, ""),
"Offline Storage"),
%% TODO: Delete the next three lines once XEP-0091 is Obsolete
jlib:timestamp_to_xml(
calendar:now_to_universal_time(
TimeStamp))]},
XML =
ejabberd_odbc:escape(
xml:element_to_binary(NewPacket)),
["insert into spool(username, xml) "
"values ('", Username, "', '",
XML,
"');"];
(_Host, _R) ->
[]
end).
export_last(Server, Output) ->
export_common(
Server, last_activity, Output,
fun(Host, #last_activity{us = {LUser, LServer},
timestamp = TimeStamp,
status = Status})
when LServer == Host ->
Username = ejabberd_odbc:escape(LUser),
Seconds = ejabberd_odbc:escape(integer_to_list(TimeStamp)),
State = ejabberd_odbc:escape(Status),
["delete from last where username='", Username, "';"
"insert into last(username, seconds, state) "
"values ('", Username, "', '", Seconds, "', '", State, "');"];
(_Host, _R) ->
[]
end).
export_vcard(Server, Output) ->
export_common(
Server, vcard, Output,
fun(Host, #vcard{us = {LUser, LServer},
vcard = VCARD})
when LServer == Host ->
Username = ejabberd_odbc:escape(LUser),
SVCARD = ejabberd_odbc:escape(
xml:element_to_binary(VCARD)),
["delete from vcard where username='", Username, "';"
"insert into vcard(username, vcard) "
"values ('", Username, "', '", SVCARD, "');"];
(_Host, _R) ->
[]
end).
export_vcard_search(Server, Output) ->
export_common(
Server, vcard_search, Output,
fun(Host, #vcard_search{user = {User, LServer},
luser = LUser,
fn = FN, lfn = LFN,
family = Family, lfamily = LFamily,
given = Given, lgiven = LGiven,
middle = Middle, lmiddle = LMiddle,
nickname = Nickname, lnickname = LNickname,
bday = BDay, lbday = LBDay,
ctry = CTRY, lctry = LCTRY,
locality = Locality, llocality = LLocality,
email = EMail, lemail = LEMail,
orgname = OrgName, lorgname = LOrgName,
orgunit = OrgUnit, lorgunit = LOrgUnit
})
when LServer == Host ->
Username = ejabberd_odbc:escape(User),
LUsername = ejabberd_odbc:escape(LUser),
SFN = ejabberd_odbc:escape(FN),
SLFN = ejabberd_odbc:escape(LFN),
SFamily = ejabberd_odbc:escape(Family),
SLFamily = ejabberd_odbc:escape(LFamily),
SGiven = ejabberd_odbc:escape(Given),
SLGiven = ejabberd_odbc:escape(LGiven),
SMiddle = ejabberd_odbc:escape(Middle),
SLMiddle = ejabberd_odbc:escape(LMiddle),
SNickname = ejabberd_odbc:escape(Nickname),
SLNickname = ejabberd_odbc:escape(LNickname),
SBDay = ejabberd_odbc:escape(BDay),
SLBDay = ejabberd_odbc:escape(LBDay),
SCTRY = ejabberd_odbc:escape(CTRY),
SLCTRY = ejabberd_odbc:escape(LCTRY),
SLocality = ejabberd_odbc:escape(Locality),
SLLocality = ejabberd_odbc:escape(LLocality),
SEMail = ejabberd_odbc:escape(EMail),
SLEMail = ejabberd_odbc:escape(LEMail),
SOrgName = ejabberd_odbc:escape(OrgName),
SLOrgName = ejabberd_odbc:escape(LOrgName),
SOrgUnit = ejabberd_odbc:escape(OrgUnit),
SLOrgUnit = ejabberd_odbc:escape(LOrgUnit),
["delete from vcard_search where lusername='", LUsername, "';"
"insert into vcard_search("
" username, lusername, fn, lfn, family, lfamily,"
" given, lgiven, middle, lmiddle, nickname, lnickname,"
" bday, lbday, ctry, lctry, locality, llocality,"
" email, lemail, orgname, lorgname, orgunit, lorgunit)"
"values (",
" '", Username, "', '", LUsername, "',"
" '", SFN, "', '", SLFN, "',"
" '", SFamily, "', '", SLFamily, "',"
" '", SGiven, "', '", SLGiven, "',"
" '", SMiddle, "', '", SLMiddle, "',"
" '", SNickname, "', '", SLNickname, "',"
" '", SBDay, "', '", SLBDay, "',"
" '", SCTRY, "', '", SLCTRY, "',"
" '", SLocality, "', '", SLLocality, "',"
" '", SEMail, "', '", SLEMail, "',"
" '", SOrgName, "', '", SLOrgName, "',"
" '", SOrgUnit, "', '", SLOrgUnit, "');"];
(_Host, _R) ->
[]
end).
export_vcard_xupdate(Server, Output) ->
export_common(
Server, vcard_xupdate, Output,
fun(Host, #vcard_xupdate{us = {LUser, LServer}, hash = Hash})
when LServer == Host ->
Username = ejabberd_odbc:escape(LUser),
SHash = ejabberd_odbc:escape(Hash),
["delete from vcard_xupdate where username='", Username, "';"
"insert into vcard_xupdate(username, hash) "
"values ('", Username, "', '", SHash, "');"];
(_Host, _R) ->
[]
end).
export_private_storage(Server, Output) ->
export_common(
Server, private_storage, Output,
fun(Host, #private_storage{usns = {LUser, LServer, XMLNS},
xml = Data})
when LServer == Host ->
Username = ejabberd_odbc:escape(LUser),
LXMLNS = ejabberd_odbc:escape(XMLNS),
SData = ejabberd_odbc:escape(
xml:element_to_binary(Data)),
odbc_queries:set_private_data_sql(Username, LXMLNS, SData);
(_Host, _R) ->
[]
end).
export_muc_room(Server, Output) ->
export_common(
Server, muc_room, Output,
fun(Host, #muc_room{name_host = {Name, RoomHost}, opts = Opts}) ->
case lists:suffix(Host, RoomHost) of
true ->
SName = ejabberd_odbc:escape(Name),
SRoomHost = ejabberd_odbc:escape(RoomHost),
SOpts = ejabberd_odbc:encode_term(Opts),
["delete from muc_room where name='", SName,
"' and host='", SRoomHost, "';",
"insert into muc_room(name, host, opts) values (",
"'", SName, "', '", SRoomHost, "', '", SOpts, "');"];
false ->
[]
end
end).
export_muc_registered(Server, Output) ->
export_common(
Server, muc_registered, Output,
fun(Host, #muc_registered{us_host = {{U, S}, RoomHost}, nick = Nick}) ->
case lists:suffix(Host, RoomHost) of
true ->
SJID = ejabberd_odbc:escape(
jlib:jid_to_string(
jlib:make_jid(U, S, ""))),
SNick = ejabberd_odbc:escape(Nick),
SRoomHost = ejabberd_odbc:escape(RoomHost),
["delete from muc_registered where jid='", SJID,
"' and host='", SRoomHost, "';"
"insert into muc_registered(jid, host, nick) values ("
"'", SJID, "', '", SRoomHost, "', '", SNick, "');"];
false ->
[]
end
end).
export_irc_custom(Server, Output) ->
export_common(
Server, irc_custom, Output,
fun(Host, #irc_custom{us_host = {{U, S}, IRCHost}, data = Data}) ->
case lists:suffix(Host, IRCHost) of
true ->
SJID = ejabberd_odbc:escape(
jlib:jid_to_string(
jlib:make_jid(U, S, ""))),
SIRCHost = ejabberd_odbc:escape(IRCHost),
SData = ejabberd_odbc:encode_term(Data),
["delete from irc_custom where jid='", SJID,
"' and host='", SIRCHost, "';"
"insert into irc_custom(jid, host, data) values ("
"'", SJID, "', '", SIRCHost, "', '", SData, "');"];
false ->
[]
end
end).
export_privacy(Server, Output) ->
case ejabberd_odbc:sql_query(
jlib:nameprep(Server),
["select id from privacy_list order by id desc limit 1;"]) of
{selected, ["id"], [{I}]} ->
put(id, list_to_integer(I));
_ ->
put(id, 0)
end,
export_common(
Server, privacy, Output,
fun(Host, #privacy{us = {LUser, LServer},
lists = Lists,
default = Default}) when LServer == Host ->
Username = ejabberd_odbc:escape(LUser),
if Default /= none ->
SDefault = ejabberd_odbc:escape(Default),
["delete from privacy_default_list where ",
"username='", Username, "';",
"insert into privacy_default_list(username, name) ",
"values ('", Username, "', '", SDefault, "');"];
true ->
[]
end ++
lists:flatmap(
fun({Name, List}) ->
SName = ejabberd_odbc:escape(Name),
RItems = lists:map(
fun mod_privacy:item_to_raw/1,
List),
ID = integer_to_list(get_id()),
["delete from privacy_list "
"where username='", Username, "' and name='", SName, "';"
"insert into privacy_list(username, name, id) "
"values ('", Username, "', '", SName, "', '", ID, "');",
"delete from privacy_list_data where id='", ID, "';"
|[["insert into privacy_list_data("
"id, t, value, action, ord, match_all, match_iq, "
"match_message, match_presence_in, "
"match_presence_out) values ('", ID, "', '",
string:join(Items, "', '"), "');"] || Items <- RItems]]
end, Lists);
(_Host, _R) ->
[]
end).
export_sr_group(Server, Output) ->
export_common(
Server, sr_group, Output,
fun(Host, #sr_group{group_host = {Group, LServer}, opts = Opts})
when LServer == Host ->
SGroup = ejabberd_odbc:escape(Group),
SOpts = ejabberd_odbc:encode_term(Opts),
["delete from sr_group where name='", Group, "';"
"insert into sr_group(name, opts) values ('",
SGroup, "', '", SOpts, "');"];
(_Host, _R) ->
[]
end).
export_sr_user(Server, Output) ->
export_common(
Server, sr_user, Output,
fun(Host, #sr_user{us = {U, S}, group_host = {Group, LServer}})
when LServer == Host ->
SGroup = ejabberd_odbc:escape(Group),
SJID = ejabberd_odbc:escape(
jlib:jid_to_string(
jlib:jid_tolower(
jlib:make_jid(U, S, "")))),
["delete from sr_user where jid='", SJID,
"'and grp='", Group, "';"
"insert into sr_user(jid, grp) values ('",
SJID, "', '", SGroup, "');"];
(_Host, _R) ->
[]
end).
export_motd(Server, Output) ->
export_common(
Server, motd, Output,
fun(Host, #motd{server = LServer, packet = El})
when LServer == Host ->
["delete from motd where username='';"
"insert into motd(username, xml) values ('', '",
ejabberd_odbc:escape(xml:element_to_binary(El)), "');"];
(_Host, _R) ->
[]
end).
export_motd_users(Server, Output) ->
export_common(
Server, motd_users, Output,
fun(Host, #motd_users{us = {LUser, LServer}})
when LServer == Host, LUser /= "" ->
Username = ejabberd_odbc:escape(LUser),
["delete from motd where username='", Username, "';"
"insert into motd(username, xml) values ('",
Username, "', '');"];
(_Host, _R) ->
[]
end).
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% Internal functions %%% Internal functions
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
export(LServer, Table, IO, ConvertFun) ->
export_common(Server, Table, Output, ConvertFun) -> F = fun () ->
IO = case Output of
odbc ->
odbc;
_ ->
{ok, IODevice} = file:open(Output, [write, raw]),
IODevice
end,
mnesia:transaction(
fun() ->
mnesia:read_lock_table(Table), mnesia:read_lock_table(Table),
LServer = jlib:nameprep(Server),
{_N, SQLs} = {_N, SQLs} =
mnesia:foldl( mnesia:foldl(
fun(R, {N, SQLs} = Acc) -> fun(R, {N, SQLs} = Acc) ->
@ -487,83 +86,54 @@ export_common(Server, Table, Output, ConvertFun) ->
[] -> [] ->
Acc; Acc;
SQL -> SQL ->
if if N < (?MAX_RECORDS_PER_TRANSACTION) - 1 ->
N < ?MAX_RECORDS_PER_TRANSACTION - 1 ->
{N + 1, [SQL | SQLs]}; {N + 1, [SQL | SQLs]};
true -> true ->
%% Execute full SQL transaction output(LServer,
output(LServer, IO, Table, IO,
["begin;", flatten([SQL | SQLs])),
lists:reverse([SQL | SQLs]),
"commit"]),
{0, []} {0, []}
end end
end end
end, {0, []}, Table),
%% Execute SQL transaction with remaining records
output(LServer, IO,
["begin;",
lists:reverse(SQLs),
"commit"])
end).
output(LServer, IO, SQL) ->
case IO of
odbc ->
catch ejabberd_odbc:sql_query(LServer, SQL);
_ ->
file:write(IO, [SQL, $;, $\n])
end.
record_to_string(#roster{usj = {User, _Server, JID},
name = Name,
subscription = Subscription,
ask = Ask,
askmessage = AskMessage}) ->
Username = ejabberd_odbc:escape(User),
SJID = ejabberd_odbc:escape(jlib:jid_to_string(JID)),
Nick = ejabberd_odbc:escape(Name),
SSubscription = case Subscription of
both -> "B";
to -> "T";
from -> "F";
none -> "N"
end, end,
SAsk = case Ask of {0, []}, Table),
subscribe -> "S"; output(LServer, Table, IO, flatten(SQLs))
unsubscribe -> "U";
both -> "B";
out -> "O";
in -> "I";
none -> "N"
end, end,
SAskMessage = mnesia:transaction(F).
case catch ejabberd_odbc:escape(
binary_to_list(list_to_binary([AskMessage]))) of
{'EXIT', _Reason} ->
[];
SAM ->
SAM
end,
["("
"'", Username, "',"
"'", SJID, "',"
"'", Nick, "',"
"'", SSubscription, "',"
"'", SAsk, "',"
"'", SAskMessage, "',"
"'N', '', 'item')"].
groups_to_string(#roster{usj = {User, _Server, JID}, output(_LServer, _Table, _IO, []) ->
groups = Groups}) -> ok;
Username = ejabberd_odbc:escape(User), output(LServer, _Table, odbc, SQLs) ->
SJID = ejabberd_odbc:escape(jlib:jid_to_string(JID)), ejabberd_odbc:sql_transaction(LServer, SQLs);
[["(" output(_LServer, Table, Fd, SQLs) ->
"'", Username, "'," file:write(Fd, ["-- \n-- Mnesia table: ", atom_to_list(Table),
"'", SJID, "'," "\n--\n", SQLs]).
"'", ejabberd_odbc:escape(Group), "')"] || Group <- Groups].
get_id() -> prepare_output(FileName) when is_list(FileName); is_binary(FileName) ->
ID = get(id), case file:open(FileName, [write, raw]) of
put(id, ID+1), {ok, Fd} ->
ID+1. Fd;
Err ->
exit(Err)
end;
prepare_output(Output) ->
Output.
close_output(FileName, Fd) when FileName /= Fd ->
file:close(Fd),
ok;
close_output(_, _) ->
ok.
flatten(SQLs) ->
flatten(SQLs, []).
flatten([L|Ls], Acc) ->
flatten(Ls, flatten1(lists:reverse(L), Acc));
flatten([], Acc) ->
Acc.
flatten1([H|T], Acc) ->
flatten1(T, [[H, $\n]|Acc]);
flatten1([], Acc) ->
Acc.

View File

@ -6,7 +6,7 @@ CPPFLAGS = @CPPFLAGS@
LDFLAGS = @LDFLAGS@ LDFLAGS = @LDFLAGS@
LIBS = @LIBS@ LIBS = @LIBS@
ASN_FLAGS = -bber_bin +optimize ASN_FLAGS = -bber_bin +optimize +binary_strings
ERLANG_CFLAGS = @ERLANG_CFLAGS@ ERLANG_CFLAGS = @ERLANG_CFLAGS@
ERLANG_LIBS = @ERLANG_LIBS@ ERLANG_LIBS = @ERLANG_LIBS@
@ -17,7 +17,7 @@ EFLAGS += -pz ..
# make debug=true to compile Erlang module with debug informations. # make debug=true to compile Erlang module with debug informations.
ifdef debug ifdef debug
EFLAGS+=+debug_info +export_all EFLAGS+=+debug_info
endif endif
OUTDIR = .. OUTDIR = ..
@ -31,6 +31,8 @@ ELDAPv3.beam: ELDAPv3.erl
ELDAPv3.erl: ELDAPv3.asn ELDAPv3.erl: ELDAPv3.asn
@ERLC@ $(ASN_FLAGS) -W $(EFLAGS) $< @ERLC@ $(ASN_FLAGS) -W $(EFLAGS) $<
@ERL@ -noinput +B -eval \
'case file:read_file("ELDAPv3.erl") of {ok, Data} -> NewData = re:replace(Data, "\\?RT_BER:decode_octet_string", "eldap_utils:decode_octet_string", [global]), file:write_file("ELDAPv3.erl", NewData), halt(0); _Err -> halt(1) end'
eldap_filter_yecc.beam: eldap_filter_yecc.erl eldap_filter_yecc.beam: eldap_filter_yecc.erl

File diff suppressed because it is too large Load Diff

View File

@ -20,20 +20,45 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-define(LDAP_PORT, 389). -define(LDAP_PORT, 389).
-define(LDAPS_PORT, 636). -define(LDAPS_PORT, 636).
-record(eldap_search, {scope = wholeSubtree, -type scope() :: baseObject | singleLevel | wholeSubtree.
base = [],
filter,
limit = 0,
attributes = [],
types_only = false,
deref_aliases = neverDerefAliases,
timeout = 0}).
-record(eldap_search,
{scope = wholeSubtree :: scope(),
base = <<"">> :: binary(),
filter :: eldap:filter(),
limit = 0 :: non_neg_integer(),
attributes = [] :: [binary()],
types_only = false :: boolean(),
deref_aliases = neverDerefAliases :: neverDerefAliases |
derefInSearching |
derefFindingBaseObj |
derefAlways,
timeout = 0 :: non_neg_integer()}).
-record(eldap_search_result, {entries, -record(eldap_search_result, {entries = [] :: [eldap_entry()],
referrals}). referrals = [] :: list()}).
-record(eldap_entry, {object_name, -record(eldap_entry, {object_name = <<>> :: binary(),
attributes}). attributes = [] :: [{binary(), [binary()]}]}).
-type tlsopts() :: [{encrypt, tls | starttls | none} |
{tls_cacertfile, binary() | undefined} |
{tls_depth, non_neg_integer() | undefined} |
{tls_verify, hard | soft | false}].
-record(eldap_config, {servers = [] :: [binary()],
backups = [] :: [binary()],
tls_options = [] :: tlsopts(),
port = ?LDAP_PORT :: inet:port_number(),
dn = <<"">> :: binary(),
password = <<"">> :: binary(),
base = <<"">> :: binary(),
deref_aliases = never :: never | searching |
finding | always}).
-type eldap_config() :: #eldap_config{}.
-type eldap_search() :: #eldap_search{}.
-type eldap_entry() :: #eldap_entry{}.

View File

@ -27,8 +27,6 @@
-module(eldap_filter). -module(eldap_filter).
%% TODO: remove this when new regexp module will be used %% TODO: remove this when new regexp module will be used
-compile({nowarn_deprecated_function, {regexp, sub, 3}}).
-export([parse/1, parse/2, do_sub/2]). -export([parse/1, parse/2, do_sub/2]).
%%==================================================================== %%====================================================================
@ -50,7 +48,9 @@
%%% {ok,{'and',[{'not',{lessOrEqual,{'AttributeValueAssertion',"uid","100"}}}, %%% {ok,{'and',[{'not',{lessOrEqual,{'AttributeValueAssertion',"uid","100"}}},
%%% {present,"mail"}]}} %%% {present,"mail"}]}}
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
parse(L) when is_list(L) -> -spec parse(binary()) -> {error, any()} | {ok, eldap:filter()}.
parse(L) ->
parse(L, []). parse(L, []).
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
@ -80,8 +80,12 @@ parse(L) when is_list(L) ->
%%% "jid", %%% "jid",
%%% "xramtsov@gmail.com"}}]}} %%% "xramtsov@gmail.com"}}]}}
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
parse(L, SList) when is_list(L), is_list(SList) -> -spec parse(binary(), [{binary(), binary()} |
case catch eldap_filter_yecc:parse(scan(L, SList)) of {binary(), binary(), pos_integer()}]) ->
{error, any()} | {ok, eldap:filter()}.
parse(L, SList) ->
case catch eldap_filter_yecc:parse(scan(binary_to_list(L), SList)) of
{'EXIT', _} = Err -> {'EXIT', _} = Err ->
{error, Err}; {error, Err};
{error, {_, _, Msg}} -> {error, {_, _, Msg}} ->
@ -95,13 +99,13 @@ parse(L, SList) when is_list(L), is_list(SList) ->
%%==================================================================== %%====================================================================
%% Internal functions %% Internal functions
%%==================================================================== %%====================================================================
-define(do_scan(L), scan(Rest, [], [{L, 1} | check(Buf, S) ++ Result], L, S)). -define(do_scan(L), scan(Rest, <<>>, [{L, 1} | check(Buf, S) ++ Result], L, S)).
scan(L, SList) -> scan(L, SList) ->
scan(L, "", [], undefined, SList). scan(L, <<"">>, [], undefined, SList).
scan("=*)" ++ Rest, Buf, Result, '(', S) -> scan("=*)" ++ Rest, Buf, Result, '(', S) ->
scan(Rest, [], [{')', 1}, {'=*', 1} | check(Buf, S) ++ Result], ')', S); scan(Rest, <<>>, [{')', 1}, {'=*', 1} | check(Buf, S) ++ Result], ')', S);
scan(":dn" ++ Rest, Buf, Result, '(', S) -> ?do_scan(':dn'); scan(":dn" ++ Rest, Buf, Result, '(', S) -> ?do_scan(':dn');
scan(":=" ++ Rest, Buf, Result, '(', S) -> ?do_scan(':='); scan(":=" ++ Rest, Buf, Result, '(', S) -> ?do_scan(':=');
scan(":=" ++ Rest, Buf, Result, ':dn', S) -> ?do_scan(':='); scan(":=" ++ Rest, Buf, Result, ':dn', S) -> ?do_scan(':=');
@ -112,35 +116,35 @@ scan("<=" ++ Rest, Buf, Result, '(', S) -> ?do_scan('<=');
scan("=" ++ Rest, Buf, Result, '(', S) -> ?do_scan('='); scan("=" ++ Rest, Buf, Result, '(', S) -> ?do_scan('=');
scan(":" ++ Rest, Buf, Result, '(', S) -> ?do_scan(':'); scan(":" ++ Rest, Buf, Result, '(', S) -> ?do_scan(':');
scan(":" ++ Rest, Buf, Result, ':dn', S) -> ?do_scan(':'); scan(":" ++ Rest, Buf, Result, ':dn', S) -> ?do_scan(':');
scan("&" ++ Rest, Buf, Result, '(', S) when Buf=="" -> ?do_scan('&'); scan("&" ++ Rest, Buf, Result, '(', S) when Buf==<<"">> -> ?do_scan('&');
scan("|" ++ Rest, Buf, Result, '(', S) when Buf=="" -> ?do_scan('|'); scan("|" ++ Rest, Buf, Result, '(', S) when Buf==<<"">> -> ?do_scan('|');
scan("!" ++ Rest, Buf, Result, '(', S) when Buf=="" -> ?do_scan('!'); scan("!" ++ Rest, Buf, Result, '(', S) when Buf==<<"">> -> ?do_scan('!');
scan("*" ++ Rest, Buf, Result, '*', S) -> ?do_scan('*'); scan("*" ++ Rest, Buf, Result, '*', S) -> ?do_scan('*');
scan("*" ++ Rest, Buf, Result, '=', S) -> ?do_scan('*'); scan("*" ++ Rest, Buf, Result, '=', S) -> ?do_scan('*');
scan("(" ++ Rest, Buf, Result, _, S) -> ?do_scan('('); scan("(" ++ Rest, Buf, Result, _, S) -> ?do_scan('(');
scan(")" ++ Rest, Buf, Result, _, S) -> ?do_scan(')'); scan(")" ++ Rest, Buf, Result, _, S) -> ?do_scan(')');
scan([Letter | Rest], Buf, Result, PreviosAtom, S) -> scan([Letter | Rest], Buf, Result, PreviosAtom, S) ->
scan(Rest, [Letter|Buf], Result, PreviosAtom, S); scan(Rest, <<Buf/binary, Letter>>, Result, PreviosAtom, S);
scan([], Buf, Result, _, S) -> scan([], Buf, Result, _, S) ->
lists:reverse(check(Buf, S) ++ Result). lists:reverse(check(Buf, S) ++ Result).
check([], _) -> check(<<>>, _) ->
[]; [];
check(Buf, S) -> check(Buf, S) ->
[{str, 1, do_sub(lists:reverse(Buf), S)}]. [{str, 1, binary_to_list(do_sub(Buf, S))}].
-define(MAX_RECURSION, 100). -define(MAX_RECURSION, 100).
-spec do_sub(binary(), [{binary(), binary()} |
{binary(), binary(), pos_integer()}]) -> binary().
do_sub(S, []) -> do_sub(S, []) ->
S; S;
do_sub(<<>>, _) ->
do_sub([], _) -> <<>>;
[];
do_sub(S, [{RegExp, New} | T]) -> do_sub(S, [{RegExp, New} | T]) ->
Result = do_sub(S, {RegExp, replace_amps(New)}, 1), Result = do_sub(S, {RegExp, replace_amps(New)}, 1),
do_sub(Result, T); do_sub(Result, T);
do_sub(S, [{RegExp, New, Times} | T]) -> do_sub(S, [{RegExp, New, Times} | T]) ->
Result = do_sub(S, {RegExp, replace_amps(New), Times}, 1), Result = do_sub(S, {RegExp, replace_amps(New), Times}, 1),
do_sub(Result, T). do_sub(Result, T).
@ -178,9 +182,10 @@ do_sub(S, {RegExp, New, Times}, Iter) ->
erlang:error(bad_regexp) erlang:error(bad_regexp)
end. end.
replace_amps(String) -> replace_amps(Bin) ->
list_to_binary(
lists:flatmap( lists:flatmap(
fun($&) -> "\\&"; fun($&) -> "\\&";
($\\) -> "\\\\"; ($\\) -> "\\\\";
(Chr) -> [Chr] (Chr) -> [Chr]
end, String). end, binary_to_list(Bin))).

View File

@ -67,5 +67,5 @@ final(Value) -> {final, Value}.
'any'(Token, Value) -> [Token, {any, Value}]. 'any'(Token, Value) -> [Token, {any, Value}].
xattr(Value) -> {type, Value}. xattr(Value) -> {type, Value}.
matchingrule(Value) -> {matchingRule, Value}. matchingrule(Value) -> {matchingRule, Value}.
value_of(Token) -> element(3, Token). value_of(Token) -> iolist_to_binary(element(3, Token)).
flatten(List) -> lists:flatten(List). flatten(List) -> lists:flatten(List).

View File

@ -25,24 +25,15 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(eldap_pool). -module(eldap_pool).
-author('xram@jabber.ru'). -author('xram@jabber.ru').
%% API %% API
-export([ -export([start_link/7, bind/3, search/2,
start_link/7, modify_passwd/3]).
bind/3,
search/2,
modify_passwd/3
]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-ifdef(SSL40).
-define(PG2, pg2).
-else.
-define(PG2, pg2_backport).
-endif.
%%==================================================================== %%====================================================================
%% API %% API
%%==================================================================== %%====================================================================
@ -55,26 +46,29 @@ search(PoolName, Opts) ->
modify_passwd(PoolName, DN, Passwd) -> modify_passwd(PoolName, DN, Passwd) ->
do_request(PoolName, {modify_passwd, [DN, Passwd]}). do_request(PoolName, {modify_passwd, [DN, Passwd]}).
start_link(Name, Hosts, Backups, Port, Rootdn, Passwd, Opts) -> start_link(Name, Hosts, Backups, Port, Rootdn, Passwd,
Opts) ->
PoolName = make_id(Name), PoolName = make_id(Name),
?PG2:create(PoolName), pg2:create(PoolName),
lists:foreach( lists:foreach(fun (Host) ->
fun(Host) -> ID = list_to_binary(erlang:ref_to_list(make_ref())),
ID = erlang:ref_to_list(make_ref()), case catch eldap:start_link(ID, [Host | Backups],
case catch eldap:start_link(ID, [Host|Backups], Port, Port, Rootdn, Passwd,
Rootdn, Passwd, Opts) of Opts)
{ok, Pid} -> of
?PG2:join(PoolName, Pid); {ok, Pid} -> pg2:join(PoolName, Pid);
_ -> Err ->
?INFO_MSG("Err = ~p", [Err]),
error error
end end
end, Hosts). end,
Hosts).
%%==================================================================== %%====================================================================
%% Internal functions %% Internal functions
%%==================================================================== %%====================================================================
do_request(Name, {F, Args}) -> do_request(Name, {F, Args}) ->
case ?PG2:get_closest_pid(make_id(Name)) of case pg2:get_closest_pid(make_id(Name)) of
Pid when is_pid(Pid) -> Pid when is_pid(Pid) ->
case catch apply(eldap, F, [Pid | Args]) of case catch apply(eldap, F, [Pid | Args]) of
{'EXIT', {timeout, _}} -> {'EXIT', {timeout, _}} ->
@ -83,12 +77,10 @@ do_request(Name, {F, Args}) ->
?ERROR_MSG("LDAP request failed: eldap:~p(~p)~nReason: ~p", ?ERROR_MSG("LDAP request failed: eldap:~p(~p)~nReason: ~p",
[F, Args, Reason]), [F, Args, Reason]),
{error, Reason}; {error, Reason};
Reply -> Reply -> Reply
Reply
end; end;
Err -> Err -> Err
Err
end. end.
make_id(Name) -> make_id(Name) ->
list_to_atom("eldap_pool_" ++ Name). jlib:binary_to_atom(<<"eldap_pool_", Name/binary>>).

View File

@ -30,15 +30,18 @@
-export([generate_subfilter/1, -export([generate_subfilter/1,
find_ldap_attrs/2, find_ldap_attrs/2,
get_ldap_attr/2, get_ldap_attr/2,
usort_attrs/1,
get_user_part/2, get_user_part/2,
make_filter/2, make_filter/2,
get_state/2, get_state/2,
case_insensitive_match/2, case_insensitive_match/2,
check_filter/1, get_opt/3,
get_opt/4,
get_config/2,
decode_octet_string/3,
uids_domain_subst/2]). uids_domain_subst/2]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("eldap.hrl").
%% Generate an 'or' LDAP query on one or several attributes %% Generate an 'or' LDAP query on one or several attributes
%% If there is only one attribute %% If there is only one attribute
@ -46,27 +49,34 @@ generate_subfilter([UID]) ->
subfilter(UID); subfilter(UID);
%% If there is several attributes %% If there is several attributes
generate_subfilter(UIDs) -> generate_subfilter(UIDs) ->
"(|" ++ [subfilter(UID) || UID <- UIDs] ++ ")". iolist_to_binary(["(|", [subfilter(UID) || UID <- UIDs], ")"]).
%% Subfilter for a single attribute %% Subfilter for a single attribute
subfilter({UIDAttr, UIDAttrFormat}) -> subfilter({UIDAttr, UIDAttrFormat}) ->
"(" ++ UIDAttr ++ "=" ++ UIDAttrFormat ++ ")";
%% The default UiDAttrFormat is %u %% The default UiDAttrFormat is %u
<<$(, UIDAttr/binary, $=, UIDAttrFormat/binary, $)>>;
%% The default UiDAttrFormat is <<"%u">>
subfilter({UIDAttr}) -> subfilter({UIDAttr}) ->
"(" ++ UIDAttr ++ "=" ++ "%u)". <<$(, UIDAttr/binary, $=, "%u)">>.
%% Not tail-recursive, but it is not very terribly. %% Not tail-recursive, but it is not very terribly.
%% It stops finding on the first not empty value. %% It stops finding on the first not empty value.
-spec find_ldap_attrs([{binary()} | {binary(), binary()}],
[{binary(), [binary()]}]) -> <<>> | {binary(), binary()}.
find_ldap_attrs([{Attr} | Rest], Attributes) -> find_ldap_attrs([{Attr} | Rest], Attributes) ->
find_ldap_attrs([{Attr, "%u"} | Rest], Attributes); find_ldap_attrs([{Attr, <<"%u">>} | Rest], Attributes);
find_ldap_attrs([{Attr, Format} | Rest], Attributes) -> find_ldap_attrs([{Attr, Format} | Rest], Attributes) ->
case get_ldap_attr(Attr, Attributes) of case get_ldap_attr(Attr, Attributes) of
Value when is_list(Value), Value /= "" -> Value when is_binary(Value), Value /= <<>> ->
{Value, Format}; {Value, Format};
_ -> _ ->
find_ldap_attrs(Rest, Attributes) find_ldap_attrs(Rest, Attributes)
end; end;
find_ldap_attrs([], _) -> find_ldap_attrs([], _) ->
"". <<>>.
-spec get_ldap_attr(binary(), [{binary(), [binary()]}]) -> binary().
get_ldap_attr(LDAPAttr, Attributes) -> get_ldap_attr(LDAPAttr, Attributes) ->
Res = lists:filter( Res = lists:filter(
@ -75,31 +85,26 @@ get_ldap_attr(LDAPAttr, Attributes) ->
end, Attributes), end, Attributes),
case Res of case Res of
[{_, [Value|_]}] -> Value; [{_, [Value|_]}] -> Value;
_ -> "" _ -> <<>>
end. end.
-spec get_user_part(binary(), binary()) -> {ok, binary()} | {error, badmatch}.
usort_attrs(Attrs) when is_list(Attrs) ->
lists:usort(Attrs);
usort_attrs(_) ->
[].
get_user_part(String, Pattern) -> get_user_part(String, Pattern) ->
F = fun(S, P) -> F = fun(S, P) ->
First = string:str(P, "%u"), First = str:str(P, <<"%u">>),
TailLength = length(P) - (First+1), TailLength = byte_size(P) - (First+1),
string:sub_string(S, First, length(S) - TailLength) str:sub_string(S, First, byte_size(S) - TailLength)
end, end,
case catch F(String, Pattern) of case catch F(String, Pattern) of
{'EXIT', _} -> {'EXIT', _} ->
{error, badmatch}; {error, badmatch};
Result -> Result ->
case catch ejabberd_regexp:replace(Pattern, "%u", Result) of case catch ejabberd_regexp:replace(Pattern, <<"%u">>, Result) of
{'EXIT', _} -> {'EXIT', _} ->
{error, badmatch}; {error, badmatch};
StringRes -> StringRes ->
case (string:to_lower(StringRes) == case case_insensitive_match(StringRes, String) of
string:to_lower(String)) of
true -> true ->
{ok, Result}; {ok, Result};
false -> false ->
@ -108,20 +113,25 @@ get_user_part(String, Pattern) ->
end end
end. end.
-spec make_filter([{binary(), [binary()]}], [{binary(), binary()}]) -> any().
make_filter(Data, UIDs) -> make_filter(Data, UIDs) ->
NewUIDs = [{U, eldap_filter:do_sub(UF, [{"%u", "*%u*", 1}])} || {U, UF} <- UIDs], NewUIDs = [{U, eldap_filter:do_sub(
UF, [{<<"%u">>, <<"*%u*">>, 1}])} || {U, UF} <- UIDs],
Filter = lists:flatmap( Filter = lists:flatmap(
fun({Name, [Value | _]}) -> fun({Name, [Value | _]}) ->
case Name of case Name of
"%u" when Value /= "" -> <<"%u">> when Value /= <<"">> ->
case eldap_filter:parse( case eldap_filter:parse(
lists:flatten(generate_subfilter(NewUIDs)), generate_subfilter(NewUIDs),
[{"%u", Value}]) of [{<<"%u">>, Value}]) of
{ok, F} -> [F]; {ok, F} -> [F];
_ -> [] _ -> []
end; end;
_ when Value /= "" -> _ when Value /= <<"">> ->
[eldap:substrings(Name, [{any, Value}])]; [eldap:substrings(
Name,
[{any, Value}])];
_ -> _ ->
[] []
end end
@ -133,9 +143,11 @@ make_filter(Data, UIDs) ->
eldap:'and'(Filter) eldap:'and'(Filter)
end. end.
-spec case_insensitive_match(binary(), binary()) -> boolean().
case_insensitive_match(X, Y) -> case_insensitive_match(X, Y) ->
X1 = stringprep:tolower(X), X1 = str:to_lower(X),
Y1 = stringprep:tolower(Y), Y1 = str:to_lower(Y),
if if
X1 == Y1 -> true; X1 == Y1 -> true;
true -> false true -> false
@ -149,22 +161,194 @@ get_state(Server, Module) ->
%% we look from alias domain (%d) and make the substitution %% we look from alias domain (%d) and make the substitution
%% with the actual host domain %% with the actual host domain
%% This help when you need to configure many virtual domains. %% This help when you need to configure many virtual domains.
-spec uids_domain_subst(binary(), [{binary(), binary()}]) ->
[{binary(), binary()}].
uids_domain_subst(Host, UIDs) -> uids_domain_subst(Host, UIDs) ->
lists:map(fun({U,V}) -> lists:map(fun({U,V}) ->
{U, eldap_filter:do_sub(V,[{"%d", Host}])}; {U, eldap_filter:do_sub(V,[{<<"%d">>, Host}])};
(A) -> A (A) -> A
end, end,
UIDs). UIDs).
check_filter(undefined) -> -spec get_opt({atom(), binary()}, list(), fun()) -> any().
ok;
check_filter(Filter) -> get_opt({Key, Host}, Opts, F) ->
case eldap_filter:parse(Filter) of get_opt({Key, Host}, Opts, F, undefined).
{ok, _} ->
ok; -spec get_opt({atom(), binary()}, list(), fun(), any()) -> any().
Err ->
?ERROR_MSG("failed to parse LDAP filter:~n" get_opt({Key, Host}, Opts, F, Default) ->
"** Filter: ~p~n" case gen_mod:get_opt(Key, Opts, F, undefined) of
"** Reason: ~p", undefined ->
[Filter, Err]) ejabberd_config:get_local_option(
{Key, Host}, F, Default);
Val ->
Val
end. end.
-spec get_config(binary(), list()) -> eldap_config().
get_config(Host, Opts) ->
Servers = get_opt({ldap_servers, Host}, Opts,
fun(L) ->
[iolist_to_binary(H) || H <- L]
end, [<<"localhost">>]),
Backups = get_opt({ldap_backups, Host}, Opts,
fun(L) ->
[iolist_to_binary(H) || H <- L]
end, []),
Encrypt = get_opt({ldap_encrypt, Host}, Opts,
fun(tls) -> tls;
(starttls) -> starttls;
(none) -> none
end, none),
TLSVerify = get_opt({ldap_tls_verify, Host}, Opts,
fun(hard) -> hard;
(soft) -> soft;
(false) -> false
end, false),
TLSCAFile = get_opt({ldap_tls_cacertfile, Host}, Opts,
fun iolist_to_binary/1),
TLSDepth = get_opt({ldap_tls_depth, Host}, Opts,
fun(I) when is_integer(I), I>=0 -> I end),
Port = get_opt({ldap_port, Host}, Opts,
fun(I) when is_integer(I), I>0 -> I end,
case Encrypt of
tls -> ?LDAPS_PORT;
starttls -> ?LDAP_PORT;
_ -> ?LDAP_PORT
end),
RootDN = get_opt({ldap_rootdn, Host}, Opts,
fun iolist_to_binary/1,
<<"">>),
Password = get_opt({ldap_password, Host}, Opts,
fun iolist_to_binary/1,
<<"">>),
Base = get_opt({ldap_base, Host}, Opts,
fun iolist_to_binary/1,
<<"">>),
DerefAliases = get_opt({deref_aliases, Host}, Opts,
fun(never) -> never;
(searching) -> searching;
(finding) -> finding;
(always) -> always
end, never),
#eldap_config{servers = Servers,
backups = Backups,
tls_options = [{encrypt, Encrypt},
{tls_verify, TLSVerify},
{tls_cacertfile, TLSCAFile},
{tls_depth, TLSDepth}],
port = Port,
dn = RootDN,
password = Password,
base = Base,
deref_aliases = DerefAliases}.
%%----------------------------------------
%% Borrowed from asn1rt_ber_bin_v2.erl
%%----------------------------------------
%%% The tag-number for universal types
-define(N_BOOLEAN, 1).
-define(N_INTEGER, 2).
-define(N_BIT_STRING, 3).
-define(N_OCTET_STRING, 4).
-define(N_NULL, 5).
-define(N_OBJECT_IDENTIFIER, 6).
-define(N_OBJECT_DESCRIPTOR, 7).
-define(N_EXTERNAL, 8).
-define(N_REAL, 9).
-define(N_ENUMERATED, 10).
-define(N_EMBEDDED_PDV, 11).
-define(N_SEQUENCE, 16).
-define(N_SET, 17).
-define(N_NumericString, 18).
-define(N_PrintableString, 19).
-define(N_TeletexString, 20).
-define(N_VideotexString, 21).
-define(N_IA5String, 22).
-define(N_UTCTime, 23).
-define(N_GeneralizedTime, 24).
-define(N_GraphicString, 25).
-define(N_VisibleString, 26).
-define(N_GeneralString, 27).
-define(N_UniversalString, 28).
-define(N_BMPString, 30).
decode_octet_string(Buffer, Range, Tags) ->
% NewTags = new_tags(HasTag,#tag{class=?UNIVERSAL,number=?N_OCTET_STRING}),
decode_restricted_string(Buffer, Range, Tags).
decode_restricted_string(Tlv, Range, TagsIn) ->
Val = match_tags(Tlv, TagsIn),
Val2 =
case Val of
PartList = [_H|_T] -> % constructed val
collect_parts(PartList);
Bin ->
Bin
end,
check_and_convert_restricted_string(Val2, Range).
check_and_convert_restricted_string(Val, Range) ->
{StrLen,NewVal} = if is_binary(Val) ->
{size(Val), Val};
true ->
{length(Val), list_to_binary(Val)}
end,
case Range of
[] -> % No length constraint
NewVal;
{Lb,Ub} when StrLen >= Lb, Ub >= StrLen -> % variable length constraint
NewVal;
{{Lb,_Ub},[]} when StrLen >= Lb ->
NewVal;
{{Lb,_Ub},_Ext=[Min|_]} when StrLen >= Lb; StrLen >= Min ->
NewVal;
{{Lb1,Ub1},{Lb2,Ub2}} when StrLen >= Lb1, StrLen =< Ub1;
StrLen =< Ub2, StrLen >= Lb2 ->
NewVal;
StrLen -> % fixed length constraint
NewVal;
{_,_} ->
exit({error,{asn1,{length,Range,Val}}});
_Len when is_integer(_Len) ->
exit({error,{asn1,{length,Range,Val}}});
_ -> % some strange constraint that we don't support yet
NewVal
end.
%%----------------------------------------
%% Decode the in buffer to bits
%%----------------------------------------
match_tags({T,V},[T]) ->
V;
match_tags({T,V}, [T|Tt]) ->
match_tags(V,Tt);
match_tags([{T,V}],[T|Tt]) ->
match_tags(V, Tt);
match_tags(Vlist = [{T,_V}|_], [T]) ->
Vlist;
match_tags(Tlv, []) ->
Tlv;
match_tags({Tag,_V},[T|_Tt]) ->
{error,{asn1,{wrong_tag,{Tag,T}}}}.
collect_parts(TlvList) ->
collect_parts(TlvList,[]).
collect_parts([{_,L}|Rest],Acc) when is_list(L) ->
collect_parts(Rest,[collect_parts(L)|Acc]);
collect_parts([{?N_BIT_STRING,<<Unused,Bits/binary>>}|Rest],_Acc) ->
collect_parts_bit(Rest,[Bits],Unused);
collect_parts([{_T,V}|Rest],Acc) ->
collect_parts(Rest,[V|Acc]);
collect_parts([],Acc) ->
list_to_binary(lists:reverse(Acc)).
collect_parts_bit([{?N_BIT_STRING,<<Unused,Bits/binary>>}|Rest],Acc,Uacc) ->
collect_parts_bit(Rest,[Bits|Acc],Unused+Uacc);
collect_parts_bit([],Acc,Uacc) ->
list_to_binary([Uacc|lists:reverse(Acc)]).

View File

@ -70,13 +70,13 @@ void encode_name(const XML_Char *name)
memcpy(buf, prefix_start+1, prefix_len); memcpy(buf, prefix_start+1, prefix_len);
memcpy(buf+prefix_len, name_start, name_len); memcpy(buf+prefix_len, name_start, name_len);
buf[prefix_len] = ':'; buf[prefix_len] = ':';
ei_x_encode_string_len(&event_buf, buf, buf_len); ei_x_encode_binary(&event_buf, buf, buf_len);
driver_free(buf); driver_free(buf);
} else { } else {
ei_x_encode_string(&event_buf, name_start+1); ei_x_encode_binary(&event_buf, name_start+1, strlen(name_start+1));
}; };
} else { } else {
ei_x_encode_string(&event_buf, name); ei_x_encode_binary(&event_buf, name, strlen(name));
} }
} }
@ -105,7 +105,7 @@ void *erlXML_StartElementHandler(expat_data *d,
{ {
ei_x_encode_tuple_header(&event_buf, 2); ei_x_encode_tuple_header(&event_buf, 2);
encode_name(atts[i]); encode_name(atts[i]);
ei_x_encode_string(&event_buf, atts[i+1]); ei_x_encode_binary(&event_buf, atts[i+1], strlen(atts[i+1]));
} }
} }
@ -159,12 +159,12 @@ void *erlXML_StartNamespaceDeclHandler(expat_data *d,
buf = driver_alloc(7 + prefix_len); buf = driver_alloc(7 + prefix_len);
strcpy(buf, "xmlns:"); strcpy(buf, "xmlns:");
strcpy(buf+6, prefix); strcpy(buf+6, prefix);
ei_x_encode_string(&xmlns_buf, buf); ei_x_encode_binary(&xmlns_buf, buf, strlen(buf));
driver_free(buf); driver_free(buf);
} else { } else {
ei_x_encode_string(&xmlns_buf, "xmlns"); ei_x_encode_binary(&xmlns_buf, "xmlns", strlen("xmlns"));
}; };
ei_x_encode_string(&xmlns_buf, uri); ei_x_encode_binary(&xmlns_buf, uri, strlen(uri));
return NULL; return NULL;
} }
@ -229,7 +229,7 @@ static ErlDrvSSizeT expat_erl_control(ErlDrvData drv_data,
ei_x_encode_long(&event_buf, XML_ERROR); ei_x_encode_long(&event_buf, XML_ERROR);
ei_x_encode_tuple_header(&event_buf, 2); ei_x_encode_tuple_header(&event_buf, 2);
ei_x_encode_long(&event_buf, errcode); ei_x_encode_long(&event_buf, errcode);
ei_x_encode_string(&event_buf, errstring); ei_x_encode_binary(&event_buf, errstring, strlen(errstring));
} }
ei_x_encode_empty_list(&event_buf); ei_x_encode_empty_list(&event_buf);

View File

@ -25,30 +25,24 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(extauth). -module(extauth).
-author('leifj@it.su.se'). -author('leifj@it.su.se').
-export([start/2, -export([start/2, stop/1, init/2, check_password/3,
stop/1, set_password/3, try_register/3, remove_user/2,
init/2, remove_user/3, is_user_exists/2]).
check_password/3,
set_password/3,
try_register/3,
remove_user/2,
remove_user/3,
is_user_exists/2]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-define(INIT_TIMEOUT, 60000). % Timeout is in milliseconds: 60 seconds == 60000 -define(INIT_TIMEOUT, 60000).
-define(CALL_TIMEOUT, 10000). % Timeout is in milliseconds: 10 seconds == 10000
-define(CALL_TIMEOUT, 10000).
start(Host, ExtPrg) -> start(Host, ExtPrg) ->
lists:foreach( lists:foreach(fun (This) ->
fun(This) ->
start_instance(get_process_name(Host, This), ExtPrg) start_instance(get_process_name(Host, This), ExtPrg)
end, end,
lists:seq(0, get_instances(Host)-1) lists:seq(0, get_instances(Host) - 1)).
).
start_instance(ProcessName, ExtPrg) -> start_instance(ProcessName, ExtPrg) ->
spawn(?MODULE, init, [ProcessName, ExtPrg]). spawn(?MODULE, init, [ProcessName, ExtPrg]).
@ -64,15 +58,15 @@ init(ProcessName, ExtPrg) ->
loop(Port, ?INIT_TIMEOUT, ProcessName, ExtPrg). loop(Port, ?INIT_TIMEOUT, ProcessName, ExtPrg).
stop(Host) -> stop(Host) ->
lists:foreach( lists:foreach(fun (This) ->
fun(This) ->
get_process_name(Host, This) ! stop get_process_name(Host, This) ! stop
end, end,
lists:seq(0, get_instances(Host)-1) lists:seq(0, get_instances(Host) - 1)).
).
get_process_name(Host, Integer) -> get_process_name(Host, Integer) ->
gen_mod:get_module_proc(lists:append([Host, integer_to_list(Integer)]), eauth). gen_mod:get_module_proc(iolist_to_binary([Host,
integer_to_list(Integer)]),
eauth).
check_password(User, Server, Password) -> check_password(User, Server, Password) ->
call_port(Server, ["auth", User, Server, Password]). call_port(Server, ["auth", User, Server, Password]).
@ -84,7 +78,9 @@ set_password(User, Server, Password) ->
call_port(Server, ["setpass", User, Server, Password]). call_port(Server, ["setpass", User, Server, Password]).
try_register(User, Server, Password) -> try_register(User, Server, Password) ->
case call_port(Server, ["tryregister", User, Server, Password]) of case call_port(Server,
["tryregister", User, Server, Password])
of
true -> {atomic, ok}; true -> {atomic, ok};
false -> {error, not_allowed} false -> {error, not_allowed}
end. end.
@ -93,16 +89,15 @@ remove_user(User, Server) ->
call_port(Server, ["removeuser", User, Server]). call_port(Server, ["removeuser", User, Server]).
remove_user(User, Server, Password) -> remove_user(User, Server, Password) ->
call_port(Server, ["removeuser3", User, Server, Password]). call_port(Server,
["removeuser3", User, Server, Password]).
call_port(Server, Msg) -> call_port(Server, Msg) ->
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
ProcessName = get_process_name(LServer, random_instance(get_instances(LServer))), ProcessName = get_process_name(LServer,
random_instance(get_instances(LServer))),
ProcessName ! {call, self(), Msg}, ProcessName ! {call, self(), Msg},
receive receive {eauth, Result} -> Result end.
{eauth,Result} ->
Result
end.
random_instance(MaxNum) -> random_instance(MaxNum) ->
{A1, A2, A3} = now(), {A1, A2, A3} = now(),
@ -110,10 +105,11 @@ random_instance(MaxNum) ->
random:uniform(MaxNum) - 1. random:uniform(MaxNum) - 1.
get_instances(Server) -> get_instances(Server) ->
case ejabberd_config:get_local_option({extauth_instances, Server}) of ejabberd_config:get_local_option(
Num when is_integer(Num) -> Num; {extauth_instances, Server},
_ -> 1 fun(V) when is_integer(V), V > 0 ->
end. V
end, 1).
loop(Port, Timeout, ProcessName, ExtPrg) -> loop(Port, Timeout, ProcessName, ExtPrg) ->
receive receive
@ -121,16 +117,18 @@ loop(Port, Timeout, ProcessName, ExtPrg) ->
port_command(Port, encode(Msg)), port_command(Port, encode(Msg)),
receive receive
{Port, {data, Data}} -> {Port, {data, Data}} ->
?DEBUG("extauth call '~p' received data response:~n~p", [Msg, Data]), ?DEBUG("extauth call '~p' received data response:~n~p",
[Msg, Data]),
Caller ! {eauth, decode(Data)}, Caller ! {eauth, decode(Data)},
loop(Port, ?CALL_TIMEOUT, ProcessName, ExtPrg); loop(Port, ?CALL_TIMEOUT, ProcessName, ExtPrg);
{Port, Other} -> {Port, Other} ->
?ERROR_MSG("extauth call '~p' received strange response:~n~p", [Msg, Other]), ?ERROR_MSG("extauth call '~p' received strange response:~n~p",
[Msg, Other]),
Caller ! {eauth, false}, Caller ! {eauth, false},
loop(Port, ?CALL_TIMEOUT, ProcessName, ExtPrg) loop(Port, ?CALL_TIMEOUT, ProcessName, ExtPrg)
after after Timeout ->
Timeout -> ?ERROR_MSG("extauth call '~p' didn't receive response",
?ERROR_MSG("extauth call '~p' didn't receive response", [Msg]), [Msg]),
Caller ! {eauth, false}, Caller ! {eauth, false},
Pid = restart_instance(ProcessName, ExtPrg), Pid = restart_instance(ProcessName, ExtPrg),
flush_buffer_and_forward_messages(Pid), flush_buffer_and_forward_messages(Pid),
@ -138,12 +136,11 @@ loop(Port, Timeout, ProcessName, ExtPrg) ->
end; end;
stop -> stop ->
Port ! {self(), close}, Port ! {self(), close},
receive receive {Port, closed} -> exit(normal) end;
{Port, closed} ->
exit(normal)
end;
{'EXIT', Port, Reason} -> {'EXIT', Port, Reason} ->
?CRITICAL_MSG("extauth script has exitted abruptly with reason '~p'", [Reason]), ?CRITICAL_MSG("extauth script has exitted abruptly "
"with reason '~p'",
[Reason]),
Pid = restart_instance(ProcessName, ExtPrg), Pid = restart_instance(ProcessName, ExtPrg),
flush_buffer_and_forward_messages(Pid), flush_buffer_and_forward_messages(Pid),
exit(port_terminated) exit(port_terminated)
@ -152,22 +149,17 @@ loop(Port, Timeout, ProcessName, ExtPrg) ->
flush_buffer_and_forward_messages(Pid) -> flush_buffer_and_forward_messages(Pid) ->
receive receive
Message -> Message ->
Pid ! Message, Pid ! Message, flush_buffer_and_forward_messages(Pid)
flush_buffer_and_forward_messages(Pid) after 0 -> true
after 0 ->
true
end. end.
join(List, Sep) -> join(List, Sep) ->
lists:foldl(fun (A, "") -> A; lists:foldl(fun (A, "") -> A;
(A, Acc) -> Acc ++ Sep ++ A (A, Acc) -> Acc ++ Sep ++ A
end, "", List). end,
"", List).
encode(L) -> encode(L) -> join(L, ":").
join(L,":").
decode([0,0]) ->
false;
decode([0,1]) ->
true.
decode([0, 0]) -> false;
decode([0, 1]) -> true.

View File

@ -25,27 +25,23 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(gen_iq_handler). -module(gen_iq_handler).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(gen_server). -behaviour(gen_server).
%% API %% API
-export([start_link/3, -export([start_link/3, add_iq_handler/6,
add_iq_handler/6, remove_iq_handler/3, stop_iq_handler/3, handle/7,
remove_iq_handler/3,
stop_iq_handler/3,
handle/7,
process_iq/6]). process_iq/6]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2,
terminate/2, code_change/3]). handle_info/2, terminate/2, code_change/3]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-record(state, {host, -record(state, {host, module, function}).
module,
function}).
%%==================================================================== %%====================================================================
%% API %% API
@ -55,30 +51,34 @@
%% Description: Starts the server %% Description: Starts the server
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
start_link(Host, Module, Function) -> start_link(Host, Module, Function) ->
gen_server:start_link(?MODULE, [Host, Module, Function], []). gen_server:start_link(?MODULE, [Host, Module, Function],
[]).
add_iq_handler(Component, Host, NS, Module, Function, Type) -> add_iq_handler(Component, Host, NS, Module, Function,
Type) ->
case Type of case Type of
no_queue -> no_queue ->
Component:register_iq_handler(Host, NS, Module, Function, no_queue); Component:register_iq_handler(Host, NS, Module,
Function, no_queue);
one_queue -> one_queue ->
{ok, Pid} = supervisor:start_child(ejabberd_iq_sup, {ok, Pid} = supervisor:start_child(ejabberd_iq_sup,
[Host, Module, Function]), [Host, Module, Function]),
Component:register_iq_handler(Host, NS, Module, Function, Component:register_iq_handler(Host, NS, Module,
{one_queue, Pid}); Function, {one_queue, Pid});
{queues, N} -> {queues, N} ->
Pids = Pids = lists:map(fun (_) ->
lists:map( {ok, Pid} =
fun(_) -> supervisor:start_child(ejabberd_iq_sup,
{ok, Pid} = supervisor:start_child( [Host, Module,
ejabberd_iq_sup, Function]),
[Host, Module, Function]),
Pid Pid
end, lists:seq(1, N)), end,
Component:register_iq_handler(Host, NS, Module, Function, lists:seq(1, N)),
{queues, Pids}); Component:register_iq_handler(Host, NS, Module,
Function, {queues, Pids});
parallel -> parallel ->
Component:register_iq_handler(Host, NS, Module, Function, parallel) Component:register_iq_handler(Host, NS, Module,
Function, parallel)
end. end.
remove_iq_handler(Component, Host, NS) -> remove_iq_handler(Component, Host, NS) ->
@ -86,43 +86,37 @@ remove_iq_handler(Component, Host, NS) ->
stop_iq_handler(_Module, _Function, Opts) -> stop_iq_handler(_Module, _Function, Opts) ->
case Opts of case Opts of
{one_queue, Pid} -> {one_queue, Pid} -> gen_server:call(Pid, stop);
gen_server:call(Pid, stop);
{queues, Pids} -> {queues, Pids} ->
lists:foreach(fun (Pid) -> lists:foreach(fun (Pid) ->
catch gen_server:call(Pid, stop) catch gen_server:call(Pid, stop)
end, Pids); end,
_ -> Pids);
ok _ -> ok
end. end.
handle(Host, Module, Function, Opts, From, To, IQ) -> handle(Host, Module, Function, Opts, From, To, IQ) ->
case Opts of case Opts of
no_queue -> no_queue ->
process_iq(Host, Module, Function, From, To, IQ); process_iq(Host, Module, Function, From, To, IQ);
{one_queue, Pid} -> {one_queue, Pid} -> Pid ! {process_iq, From, To, IQ};
Pid ! {process_iq, From, To, IQ};
{queues, Pids} -> {queues, Pids} ->
Pid = lists:nth(erlang:phash(now(), length(Pids)), Pids), Pid = lists:nth(erlang:phash(now(), str:len(Pids)),
Pids),
Pid ! {process_iq, From, To, IQ}; Pid ! {process_iq, From, To, IQ};
parallel -> parallel ->
spawn(?MODULE, process_iq, [Host, Module, Function, From, To, IQ]); spawn(?MODULE, process_iq,
_ -> [Host, Module, Function, From, To, IQ]);
todo _ -> todo
end. end.
process_iq(_Host, Module, Function, From, To, IQ) -> process_iq(_Host, Module, Function, From, To, IQ) ->
case catch Module:Function(From, To, IQ) of case catch Module:Function(From, To, IQ) of
{'EXIT', Reason} -> {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
?ERROR_MSG("~p", [Reason]);
ResIQ -> ResIQ ->
if if ResIQ /= ignore ->
ResIQ /= ignore -> ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
ejabberd_router:route(To, From, true -> ok
jlib:iq_to_xml(ResIQ));
true ->
ok
end end
end. end.
@ -138,8 +132,8 @@ process_iq(_Host, Module, Function, From, To, IQ) ->
%% Description: Initiates the server %% Description: Initiates the server
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([Host, Module, Function]) -> init([Host, Module, Function]) ->
{ok, #state{host = Host, {ok,
module = Module, #state{host = Host, module = Module,
function = Function}}. function = Function}}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -152,8 +146,7 @@ init([Host, Module, Function]) ->
%% Description: Handling call messages %% Description: Handling call messages
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_call(stop, _From, State) -> handle_call(stop, _From, State) ->
Reply = ok, Reply = ok, {stop, normal, Reply, State}.
{stop, normal, Reply, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} | %% Function: handle_cast(Msg, State) -> {noreply, State} |
@ -161,8 +154,7 @@ handle_call(stop, _From, State) ->
%% {stop, Reason, State} %% {stop, Reason, State}
%% Description: Handling cast messages %% Description: Handling cast messages
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_cast(_Msg, State) -> handle_cast(_Msg, State) -> {noreply, State}.
{noreply, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} | %% Function: handle_info(Info, State) -> {noreply, State} |
@ -171,13 +163,12 @@ handle_cast(_Msg, State) ->
%% Description: Handling all non call/cast messages %% Description: Handling all non call/cast messages
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_info({process_iq, From, To, IQ}, handle_info({process_iq, From, To, IQ},
#state{host = Host, #state{host = Host, module = Module,
module = Module, function = Function} =
function = Function} = State) -> State) ->
process_iq(Host, Module, Function, From, To, IQ), process_iq(Host, Module, Function, From, To, IQ),
{noreply, State}; {noreply, State};
handle_info(_Info, State) -> handle_info(_Info, State) -> {noreply, State}.
{noreply, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void() %% Function: terminate(Reason, State) -> void()
@ -186,16 +177,15 @@ handle_info(_Info, State) ->
%% cleaning up. When it returns, the gen_server terminates with Reason. %% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored. %% The return value is ignored.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
terminate(_Reason, _State) -> terminate(_Reason, _State) -> ok.
ok.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed %% Description: Convert process state when code is changed
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) -> {ok, State}.
{ok, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%%% Internal functions %%% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------

View File

@ -2,6 +2,7 @@
%%% File : gen_mod.erl %%% File : gen_mod.erl
%%% Author : Alexey Shchepin <alexey@process-one.net> %%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : %%% Purpose :
%%% Purpose :
%%% Created : 24 Jan 2003 by Alexey Shchepin <alexey@process-one.net> %%% Created : 24 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%% %%%
%%% %%%
@ -25,78 +26,81 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(gen_mod). -module(gen_mod).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-export([start/0, -export([start/0, start_module/3, stop_module/2,
start_module/3, stop_module_keep_config/2, get_opt/3, get_opt/4,
stop_module/2, get_opt_host/3, db_type/1, db_type/2, get_module_opt/5,
stop_module_keep_config/2, get_module_opt_host/3, loaded_modules/1,
get_opt/2, loaded_modules_with_opts/1, get_hosts/2,
get_opt/3, get_module_proc/2, is_loaded/2]).
get_opt_host/3,
db_type/1,
db_type/2,
get_module_opt/4,
get_module_opt_host/3,
loaded_modules/1,
loaded_modules_with_opts/1,
get_hosts/2,
get_module_proc/2,
is_loaded/2]).
-export([behaviour_info/1]). %%-export([behaviour_info/1]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-record(ejabberd_module, {module_host, opts}). -record(ejabberd_module,
{module_host = {undefined, <<"">>} :: {atom(), binary()},
opts = [] :: opts() | '_' | '$2'}).
behaviour_info(callbacks) -> -type opts() :: [{atom(), any()}].
[{start, 2},
{stop, 1}]; -callback start(binary(), opts()) -> any().
behaviour_info(_Other) -> -callback stop(binary()) -> any().
undefined.
-export_type([opts/0]).
%%behaviour_info(callbacks) -> [{start, 2}, {stop, 1}];
%%behaviour_info(_Other) -> undefined.
start() -> start() ->
ets:new(ejabberd_modules, [named_table, ets:new(ejabberd_modules,
public, [named_table, public,
{keypos, #ejabberd_module.module_host}]), {keypos, #ejabberd_module.module_host}]),
ok. ok.
-spec start_module(binary(), atom(), opts()) -> any().
start_module(Host, Module, Opts) -> start_module(Host, Module, Opts) ->
set_module_opts_mnesia(Host, Module, Opts), set_module_opts_mnesia(Host, Module, Opts),
ets:insert(ejabberd_modules, ets:insert(ejabberd_modules,
#ejabberd_module{module_host = {Module, Host}, #ejabberd_module{module_host = {Module, Host},
opts = Opts}), opts = Opts}),
try Module:start(Host, Opts) try Module:start(Host, Opts) catch
catch Class:Reason -> Class:Reason ->
del_module_mnesia(Host, Module), del_module_mnesia(Host, Module),
ets:delete(ejabberd_modules, {Module, Host}), ets:delete(ejabberd_modules, {Module, Host}),
ErrorText = io_lib:format("Problem starting the module ~p for host ~p ~n options: ~p~n ~p: ~p", ErrorText =
[Module, Host, Opts, Class, Reason]), io_lib:format("Problem starting the module ~p for host "
"~p ~n options: ~p~n ~p: ~p~n~p",
[Module, Host, Opts, Class, Reason,
erlang:get_stacktrace()]),
?CRITICAL_MSG(ErrorText, []), ?CRITICAL_MSG(ErrorText, []),
case is_app_running(ejabberd) of case is_app_running(ejabberd) of
true -> true ->
erlang:raise(Class, Reason, erlang:get_stacktrace()); erlang:raise(Class, Reason, erlang:get_stacktrace());
false -> false ->
?CRITICAL_MSG("ejabberd initialization was aborted because a module start failed.", []), ?CRITICAL_MSG("ejabberd initialization was aborted "
"because a module start failed.",
[]),
timer:sleep(3000), timer:sleep(3000),
erlang:halt(string:substr(lists:flatten(ErrorText), 1, 199)) erlang:halt(string:substr(lists:flatten(ErrorText), 1, 199))
end end
end. end.
is_app_running(AppName) -> is_app_running(AppName) ->
%% Use a high timeout to prevent a false positive in a high load system
Timeout = 15000, Timeout = 15000,
lists:keymember(AppName, 1, application:which_applications(Timeout)). lists:keymember(AppName, 1,
application:which_applications(Timeout)).
-spec stop_module(binary(), atom()) -> error | {aborted, any()} | {atomic, any()}.
%% @doc Stop the module in a host, and forget its configuration. %% @doc Stop the module in a host, and forget its configuration.
stop_module(Host, Module) -> stop_module(Host, Module) ->
case stop_module_keep_config(Host, Module) of case stop_module_keep_config(Host, Module) of
error -> error -> error;
error; ok -> del_module_mnesia(Host, Module)
ok ->
del_module_mnesia(Host, Module)
end. end.
%% @doc Stop the module in a host, but keep its configuration. %% @doc Stop the module in a host, but keep its configuration.
@ -104,11 +108,11 @@ stop_module(Host, Module) ->
%% when ejabberd is restarted the module will be started again. %% when ejabberd is restarted the module will be started again.
%% This function is useful when ejabberd is being stopped %% This function is useful when ejabberd is being stopped
%% and it stops all modules. %% and it stops all modules.
-spec stop_module_keep_config(binary(), atom()) -> error | ok.
stop_module_keep_config(Host, Module) -> stop_module_keep_config(Host, Module) ->
case catch Module:stop(Host) of case catch Module:stop(Host) of
{'EXIT', Reason} -> {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), error;
?ERROR_MSG("~p", [Reason]),
error;
{wait, ProcList} when is_list(ProcList) -> {wait, ProcList} when is_list(ProcList) ->
lists:foreach(fun wait_for_process/1, ProcList), lists:foreach(fun wait_for_process/1, ProcList),
ets:delete(ejabberd_modules, {Module, Host}), ets:delete(ejabberd_modules, {Module, Host}),
@ -117,9 +121,7 @@ stop_module_keep_config(Host, Module) ->
wait_for_process(Process), wait_for_process(Process),
ets:delete(ejabberd_modules, {Module, Host}), ets:delete(ejabberd_modules, {Module, Host}),
ok; ok;
_ -> _ -> ets:delete(ejabberd_modules, {Module, Host}), ok
ets:delete(ejabberd_modules, {Module, Host}),
ok
end. end.
wait_for_process(Process) -> wait_for_process(Process) ->
@ -128,8 +130,7 @@ wait_for_process(Process) ->
wait_for_stop(Process, MonitorReference) -> wait_for_stop(Process, MonitorReference) ->
receive receive
{'DOWN', MonitorReference, _Type, _Object, _Info} -> {'DOWN', MonitorReference, _Type, _Object, _Info} -> ok
ok
after 5000 -> after 5000 ->
catch exit(whereis(Process), kill), catch exit(whereis(Process), kill),
wait_for_stop1(MonitorReference) wait_for_stop1(MonitorReference)
@ -137,38 +138,37 @@ wait_for_stop(Process, MonitorReference) ->
wait_for_stop1(MonitorReference) -> wait_for_stop1(MonitorReference) ->
receive receive
{'DOWN', MonitorReference, _Type, _Object, _Info} -> {'DOWN', MonitorReference, _Type, _Object, _Info} -> ok
ok after 5000 -> ok
after 5000 ->
ok
end. end.
get_opt(Opt, Opts) -> -type check_fun() :: fun((any()) -> any()) | {module(), atom()}.
case lists:keysearch(Opt, 1, Opts) of
false ->
% TODO: replace with more appropriate function
throw({undefined_option, Opt});
{value, {_, Val}} ->
Val
end.
get_opt(Opt, Opts, Default) -> -spec get_opt(atom(), opts(), check_fun()) -> any().
get_opt(Opt, Opts, F) ->
get_opt(Opt, Opts, F, undefined).
-spec get_opt(atom(), opts(), check_fun(), any()) -> any().
get_opt(Opt, Opts, F, Default) ->
case lists:keysearch(Opt, 1, Opts) of case lists:keysearch(Opt, 1, Opts) of
false -> false ->
Default; Default;
{value, {_, Val}} -> {value, {_, Val}} ->
Val ejabberd_config:prepare_opt_val(Opt, Val, F, Default)
end. end.
get_module_opt(global, Module, Opt, Default) -> -spec get_module_opt(global | binary(), atom(), atom(), check_fun(), any()) -> any().
Hosts = ?MYHOSTS,
[Value | Values] = lists:map( get_module_opt(global, Module, Opt, F, Default) ->
fun(Host) -> Hosts = (?MYHOSTS),
get_module_opt(Host, Module, Opt, Default) [Value | Values] = lists:map(fun (Host) ->
get_module_opt(Host, Module, Opt,
F, Default)
end, end,
Hosts), Hosts),
Same_all = lists:all( Same_all = lists:all(fun (Other_value) ->
fun(Other_value) ->
Other_value == Value Other_value == Value
end, end,
Values), Values),
@ -176,76 +176,90 @@ get_module_opt(global, Module, Opt, Default) ->
true -> Value; true -> Value;
false -> Default false -> Default
end; end;
get_module_opt(Host, Module, Opt, F, Default) ->
get_module_opt(Host, Module, Opt, Default) ->
OptsList = ets:lookup(ejabberd_modules, {Module, Host}), OptsList = ets:lookup(ejabberd_modules, {Module, Host}),
case OptsList of case OptsList of
[] -> [] -> Default;
Default;
[#ejabberd_module{opts = Opts} | _] -> [#ejabberd_module{opts = Opts} | _] ->
get_opt(Opt, Opts, Default) get_opt(Opt, Opts, F, Default)
end. end.
-spec get_module_opt_host(global | binary(), atom(), binary()) -> binary().
get_module_opt_host(Host, Module, Default) -> get_module_opt_host(Host, Module, Default) ->
Val = get_module_opt(Host, Module, host, Default), Val = get_module_opt(Host, Module, host,
ejabberd_regexp:greplace(Val, "@HOST@", Host). fun iolist_to_binary/1,
Default),
ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
-spec get_opt_host(binary(), opts(), binary()) -> binary().
get_opt_host(Host, Opts, Default) -> get_opt_host(Host, Opts, Default) ->
Val = get_opt(host, Opts, Default), Val = get_opt(host, Opts, fun iolist_to_binary/1, Default),
ejabberd_regexp:greplace(Val, "@HOST@", Host). ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
-spec db_type(opts()) -> odbc | mnesia.
db_type(Opts) -> db_type(Opts) ->
case get_opt(db_type, Opts, mnesia) of get_opt(db_type, Opts,
odbc -> odbc; fun(odbc) -> odbc;
_ -> mnesia (internal) -> mnesia;
end. (mnesia) -> mnesia end,
mnesia).
-spec db_type(binary(), atom()) -> odbc | mnesia.
db_type(Host, Module) -> db_type(Host, Module) ->
case get_module_opt(Host, Module, db_type, mnesia) of get_module_opt(Host, Module, db_type,
odbc -> odbc; fun(odbc) -> odbc;
_ -> mnesia (internal) -> mnesia;
end. (mnesia) -> mnesia end,
mnesia).
-spec loaded_modules(binary()) -> [atom()].
loaded_modules(Host) -> loaded_modules(Host) ->
ets:select(ejabberd_modules, ets:select(ejabberd_modules,
[{#ejabberd_module{_ = '_', module_host = {'$1', Host}}, [{#ejabberd_module{_ = '_', module_host = {'$1', Host}},
[], [], ['$1']}]).
['$1']}]).
-spec loaded_modules_with_opts(binary()) -> [{atom(), opts()}].
loaded_modules_with_opts(Host) -> loaded_modules_with_opts(Host) ->
ets:select(ejabberd_modules, ets:select(ejabberd_modules,
[{#ejabberd_module{_ = '_', module_host = {'$1', Host}, [{#ejabberd_module{_ = '_', module_host = {'$1', Host},
opts = '$2'}, opts = '$2'},
[], [], [{{'$1', '$2'}}]}]).
[{{'$1', '$2'}}]}]).
set_module_opts_mnesia(Host, Module, Opts) -> set_module_opts_mnesia(Host, Module, Opts) ->
Modules = case ejabberd_config:get_local_option({modules, Host}) of Modules = ejabberd_config:get_local_option(
undefined -> {modules, Host},
[]; fun(Ls) when is_list(Ls) -> Ls end,
Ls -> []),
Ls
end,
Modules1 = lists:keydelete(Module, 1, Modules), Modules1 = lists:keydelete(Module, 1, Modules),
Modules2 = [{Module, Opts} | Modules1], Modules2 = [{Module, Opts} | Modules1],
ejabberd_config:add_local_option({modules, Host}, Modules2). ejabberd_config:add_local_option({modules, Host},
Modules2).
del_module_mnesia(Host, Module) -> del_module_mnesia(Host, Module) ->
Modules = case ejabberd_config:get_local_option({modules, Host}) of Modules = ejabberd_config:get_local_option(
undefined -> {modules, Host},
[]; fun(Ls) when is_list(Ls) -> Ls end,
Ls -> []),
Ls
end,
Modules1 = lists:keydelete(Module, 1, Modules), Modules1 = lists:keydelete(Module, 1, Modules),
ejabberd_config:add_local_option({modules, Host}, Modules1). ejabberd_config:add_local_option({modules, Host},
Modules1).
-spec get_hosts(opts(), binary()) -> [binary()].
get_hosts(Opts, Prefix) -> get_hosts(Opts, Prefix) ->
case catch gen_mod:get_opt(hosts, Opts) of case get_opt(hosts, Opts,
{'EXIT', _Error1} -> fun(Hs) -> [iolist_to_binary(H) || H <- Hs] end) of
case catch gen_mod:get_opt(host, Opts) of undefined ->
{'EXIT', _Error2} -> case get_opt(host, Opts,
[Prefix ++ Host || Host <- ?MYHOSTS]; fun iolist_to_binary/1) of
undefined ->
[<<Prefix/binary, Host/binary>> || Host <- ?MYHOSTS];
Host -> Host ->
[Host] [Host]
end; end;
@ -253,11 +267,16 @@ get_hosts(Opts, Prefix) ->
Hosts Hosts
end. end.
-spec get_module_proc(binary(), {frontend, atom()} | atom()) -> atom().
get_module_proc(Host, {frontend, Base}) -> get_module_proc(Host, {frontend, Base}) ->
get_module_proc("frontend_" ++ Host, Base); get_module_proc(<<"frontend_", Host/binary>>, Base);
get_module_proc(Host, Base) -> get_module_proc(Host, Base) ->
list_to_atom(atom_to_list(Base) ++ "_" ++ Host). binary_to_atom(
<<(erlang:atom_to_binary(Base, latin1))/binary, "_", Host/binary>>,
latin1).
-spec is_loaded(binary(), atom()) -> boolean().
is_loaded(Host, Module) -> is_loaded(Host, Module) ->
ets:member(ejabberd_modules, {Module, Host}). ets:member(ejabberd_modules, {Module, Host}).

View File

@ -25,172 +25,182 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(idna). -module(idna).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
%%-compile(export_all). %%-compile(export_all).
-export([domain_utf8_to_ascii/1, -export([domain_utf8_to_ascii/1,
domain_ucs2_to_ascii/1]). domain_ucs2_to_ascii/1,
utf8_to_ucs2/1]).
-spec domain_utf8_to_ascii(binary()) -> false | binary().
domain_utf8_to_ascii(Domain) -> domain_utf8_to_ascii(Domain) ->
domain_ucs2_to_ascii(utf8_to_ucs2(Domain)). domain_ucs2_to_ascii(utf8_to_ucs2(Domain)).
utf8_to_ucs2(S) -> utf8_to_ucs2(S) ->
utf8_to_ucs2(S, ""). list_to_binary(utf8_to_ucs2(binary_to_list(S), "")).
utf8_to_ucs2([], R) -> utf8_to_ucs2([], R) -> lists:reverse(R);
lists:reverse(R); utf8_to_ucs2([C | S], R) when C < 128 ->
utf8_to_ucs2([C | S], R) when C < 16#80 ->
utf8_to_ucs2(S, [C | R]); utf8_to_ucs2(S, [C | R]);
utf8_to_ucs2([C1, C2 | S], R) when C1 < 16#E0 -> utf8_to_ucs2([C1, C2 | S], R) when C1 < 224 ->
utf8_to_ucs2(S, [((C1 band 16#1F) bsl 6) bor utf8_to_ucs2(S, [C1 band 31 bsl 6 bor C2 band 63 | R]);
(C2 band 16#3F) | R]); utf8_to_ucs2([C1, C2, C3 | S], R) when C1 < 240 ->
utf8_to_ucs2([C1, C2, C3 | S], R) when C1 < 16#F0 -> utf8_to_ucs2(S,
utf8_to_ucs2(S, [((C1 band 16#0F) bsl 12) bor [C1 band 15 bsl 12 bor (C2 band 63 bsl 6) bor C3 band 63
((C2 band 16#3F) bsl 6) bor | R]).
(C3 band 16#3F) | R]).
-spec domain_ucs2_to_ascii(binary()) -> false | binary().
domain_ucs2_to_ascii(Domain) -> domain_ucs2_to_ascii(Domain) ->
case catch domain_ucs2_to_ascii1(Domain) of case catch domain_ucs2_to_ascii1(binary_to_list(Domain)) of
{'EXIT', _Reason} -> {'EXIT', _Reason} -> false;
false; Res -> iolist_to_binary(Res)
Res ->
Res
end. end.
domain_ucs2_to_ascii1(Domain) -> domain_ucs2_to_ascii1(Domain) ->
Parts = string:tokens(Domain, [16#002E, 16#3002, 16#FF0E, 16#FF61]), Parts = string:tokens(Domain,
ASCIIParts = lists:map(fun(P) -> [46, 12290, 65294, 65377]),
to_ascii(P) ASCIIParts = lists:map(fun (P) -> to_ascii(P) end,
end, Parts), Parts),
string:strip(lists:flatmap(fun(P) -> [$. | P] end, ASCIIParts), string:strip(lists:flatmap(fun (P) -> [$. | P] end,
ASCIIParts),
left, $.). left, $.).
%% Domain names are already nameprep'ed in ejabberd, so we skiping this step %% Domain names are already nameprep'ed in ejabberd, so we skiping this step
to_ascii(Name) -> to_ascii(Name) ->
false = lists:any( false = lists:any(fun (C)
fun(C) when when (0 =< C) and (C =< 44) or
( 0 =< C) and (C =< 16#2C) or (46 =< C) and (C =< 47)
(16#2E =< C) and (C =< 16#2F) or or (58 =< C) and (C =< 64)
(16#3A =< C) and (C =< 16#40) or or (91 =< C) and (C =< 96)
(16#5B =< C) and (C =< 16#60) or or (123 =< C) and (C =< 127) ->
(16#7B =< C) and (C =< 16#7F) ->
true; true;
(_) -> (_) -> false
false
end, Name),
case Name of
[H | _] when H /= $- ->
true = lists:last(Name) /= $-
end, end,
ASCIIName = case lists:any(fun(C) -> C > 16#7F end, Name) of Name),
case Name of
[H | _] when H /= $- -> true = lists:last(Name) /= $-
end,
ASCIIName = case lists:any(fun (C) -> C > 127 end, Name)
of
true -> true ->
true = case Name of true = case Name of
"xn--" ++ _ -> false; "xn--" ++ _ -> false;
_ -> true _ -> true
end, end,
"xn--" ++ punycode_encode(Name); "xn--" ++ punycode_encode(Name);
false -> false -> Name
Name
end, end,
L = length(ASCIIName), L = length(ASCIIName),
true = (1 =< L) and (L =< 63), true = (1 =< L) and (L =< 63),
ASCIIName. ASCIIName.
%%% PUNYCODE (RFC3492) %%% PUNYCODE (RFC3492)
-define(BASE, 36). -define(BASE, 36).
-define(TMIN, 1). -define(TMIN, 1).
-define(TMAX, 26). -define(TMAX, 26).
-define(SKEW, 38). -define(SKEW, 38).
-define(DAMP, 700). -define(DAMP, 700).
-define(INITIAL_BIAS, 72). -define(INITIAL_BIAS, 72).
-define(INITIAL_N, 128). -define(INITIAL_N, 128).
punycode_encode(Input) -> punycode_encode(Input) ->
N = ?INITIAL_N, N = (?INITIAL_N),
Delta = 0, Delta = 0,
Bias = ?INITIAL_BIAS, Bias = (?INITIAL_BIAS),
Basic = lists:filter(fun(C) -> C =< 16#7f end, Input), Basic = lists:filter(fun (C) -> C =< 127 end, Input),
NonBasic = lists:filter(fun(C) -> C > 16#7f end, Input), NonBasic = lists:filter(fun (C) -> C > 127 end, Input),
L = length(Input), L = length(Input),
B = length(Basic), B = length(Basic),
SNonBasic = lists:usort(NonBasic), SNonBasic = lists:usort(NonBasic),
Output1 = if Output1 = if B > 0 -> Basic ++ "-";
B > 0 -> Basic ++ "-";
true -> "" true -> ""
end, end,
Output2 = punycode_encode1(Input, SNonBasic, B, B, L, N, Delta, Bias, ""), Output2 = punycode_encode1(Input, SNonBasic, B, B, L, N,
Delta, Bias, ""),
Output1 ++ Output2. Output1 ++ Output2.
punycode_encode1(Input, [M | SNonBasic], B, H, L, N,
punycode_encode1(Input, [M | SNonBasic], B, H, L, N, Delta, Bias, Out) Delta, Bias, Out)
when H < L -> when H < L ->
Delta1 = Delta + (M - N) * (H + 1), Delta1 = Delta + (M - N) * (H + 1),
% let n = m % let n = m
{NewDelta, NewBias, NewH, NewOut} = {NewDelta, NewBias, NewH, NewOut} = lists:foldl(fun (C,
lists:foldl( {ADelta, ABias, AH,
fun(C, {ADelta, ABias, AH, AOut}) -> AOut}) ->
if if C < M ->
C < M -> {ADelta + 1,
{ADelta + 1, ABias, AH, AOut}; ABias, AH,
AOut};
C == M -> C == M ->
NewOut = punycode_encode_delta(ADelta, ABias, AOut), NewOut =
NewBias = adapt(ADelta, H + 1, H == B), punycode_encode_delta(ADelta,
{0, NewBias, AH + 1, NewOut}; ABias,
AOut),
NewBias =
adapt(ADelta,
H +
1,
H
==
B),
{0, NewBias,
AH + 1,
NewOut};
true -> true ->
{ADelta, ABias, AH, AOut} {ADelta,
ABias, AH,
AOut}
end end
end, {Delta1, Bias, H, Out}, Input), end,
punycode_encode1( {Delta1, Bias, H, Out},
Input, SNonBasic, B, NewH, L, M + 1, NewDelta + 1, NewBias, NewOut); Input),
punycode_encode1(Input, SNonBasic, B, NewH, L, M + 1,
punycode_encode1(_Input, _SNonBasic, _B, _H, _L, _N, _Delta, _Bias, Out) -> NewDelta + 1, NewBias, NewOut);
punycode_encode1(_Input, _SNonBasic, _B, _H, _L, _N,
_Delta, _Bias, Out) ->
lists:reverse(Out). lists:reverse(Out).
punycode_encode_delta(Delta, Bias, Out) -> punycode_encode_delta(Delta, Bias, Out) ->
punycode_encode_delta(Delta, Bias, Out, ?BASE). punycode_encode_delta(Delta, Bias, Out, ?BASE).
punycode_encode_delta(Delta, Bias, Out, K) -> punycode_encode_delta(Delta, Bias, Out, K) ->
T = if T = if K =< Bias -> ?TMIN;
K =< Bias -> ?TMIN; K >= Bias + (?TMAX) -> ?TMAX;
K >= Bias + ?TMAX -> ?TMAX;
true -> K - Bias true -> K - Bias
end, end,
if if Delta < T -> [codepoint(Delta) | Out];
Delta < T ->
[codepoint(Delta) | Out];
true -> true ->
C = T + ((Delta - T) rem (?BASE - T)), C = T + (Delta - T) rem ((?BASE) - T),
punycode_encode_delta((Delta - T) div (?BASE - T), Bias, punycode_encode_delta((Delta - T) div ((?BASE) - T),
[codepoint(C) | Out], K + ?BASE) Bias, [codepoint(C) | Out], K + (?BASE))
end. end.
adapt(Delta, NumPoints, FirstTime) -> adapt(Delta, NumPoints, FirstTime) ->
Delta1 = if Delta1 = if FirstTime -> Delta div (?DAMP);
FirstTime -> Delta div ?DAMP;
true -> Delta div 2 true -> Delta div 2
end, end,
Delta2 = Delta1 + (Delta1 div NumPoints), Delta2 = Delta1 + Delta1 div NumPoints,
adapt1(Delta2, 0). adapt1(Delta2, 0).
adapt1(Delta, K) -> adapt1(Delta, K) ->
if if Delta > ((?BASE) - (?TMIN)) * (?TMAX) div 2 ->
Delta > ((?BASE - ?TMIN) * ?TMAX) div 2 -> adapt1(Delta div ((?BASE) - (?TMIN)), K + (?BASE));
adapt1(Delta div (?BASE - ?TMIN), K + ?BASE);
true -> true ->
K + (((?BASE - ?TMIN + 1) * Delta) div (Delta + ?SKEW)) K +
((?BASE) - (?TMIN) + 1) * Delta div (Delta + (?SKEW))
end. end.
codepoint(C) -> codepoint(C) ->
if if (0 =< C) and (C =< 25) -> C + 97;
(0 =< C) and (C =< 25) -> (26 =< C) and (C =< 35) -> C + 22
C + 97;
(26 =< C) and (C =< 35) ->
C + 22
end. end.

View File

@ -25,17 +25,16 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(jd2ejd). -module(jd2ejd).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
%% External exports %% External exports
-export([import_file/1, -export([import_file/1, import_dir/1]).
import_dir/1]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl"). -include("jlib.hrl").
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% API %%% API
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
@ -43,21 +42,21 @@
import_file(File) -> import_file(File) ->
User = filename:rootname(filename:basename(File)), User = filename:rootname(filename:basename(File)),
Server = filename:basename(filename:dirname(File)), Server = filename:basename(filename:dirname(File)),
case (jlib:nodeprep(User) /= error) andalso case jlib:nodeprep(User) /= error andalso
(jlib:nameprep(Server) /= error) of jlib:nameprep(Server) /= error
of
true -> true ->
case file:read_file(File) of case file:read_file(File) of
{ok, Text} -> {ok, Text} ->
case xml_stream:parse_element(Text) of case xml_stream:parse_element(Text) of
El when element(1, El) == xmlelement -> El when is_record(El, xmlel) ->
case catch process_xdb(User, Server, El) of case catch process_xdb(User, Server, El) of
{'EXIT', Reason} -> {'EXIT', Reason} ->
?ERROR_MSG( ?ERROR_MSG("Error while processing file \"~s\": "
"Error while processing file \"~s\": ~p~n", "~p~n",
[File, Reason]), [File, Reason]),
{error, Reason}; {error, Reason};
_ -> _ -> ok
ok
end; end;
{error, Reason} -> {error, Reason} ->
?ERROR_MSG("Can't parse file \"~s\": ~p~n", ?ERROR_MSG("Can't parse file \"~s\": ~p~n",
@ -65,118 +64,113 @@ import_file(File) ->
{error, Reason} {error, Reason}
end; end;
{error, Reason} -> {error, Reason} ->
?ERROR_MSG("Can't read file \"~s\": ~p~n", [File, Reason]), ?ERROR_MSG("Can't read file \"~s\": ~p~n",
[File, Reason]),
{error, Reason} {error, Reason}
end; end;
false -> false ->
?ERROR_MSG("Illegal user/server name in file \"~s\"~n", [File]), ?ERROR_MSG("Illegal user/server name in file \"~s\"~n",
{error, "illegal user/server"} [File]),
{error, <<"illegal user/server">>}
end. end.
import_dir(Dir) -> import_dir(Dir) ->
{ok, Files} = file:list_dir(Dir), {ok, Files} = file:list_dir(Dir),
MsgFiles = lists:filter( MsgFiles = lists:filter(fun (FN) ->
fun(FN) -> case length(FN) > 4 of
case string:len(FN) > 4 of
true -> true ->
string:substr(FN, string:substr(FN, length(FN) - 3) ==
string:len(FN) - 3) == ".xml"; ".xml";
_ -> _ -> false
false
end end
end, Files), end,
lists:foldl( Files),
fun(FN, A) -> lists:foldl(fun (FN, A) ->
Res = import_file(filename:join([Dir, FN])), Res = import_file(filename:join([Dir, FN])),
case {A, Res} of case {A, Res} of
{ok, ok} -> ok; {ok, ok} -> ok;
{ok, _} -> {error, "see ejabberd log for details"}; {ok, _} ->
{error, <<"see ejabberd log for details">>};
_ -> A _ -> A
end end
end, ok, MsgFiles). end,
ok, MsgFiles).
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% Internal functions %%% Internal functions
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
process_xdb(User, Server, {xmlelement, Name, _Attrs, Els}) -> process_xdb(User, Server,
#xmlel{name = Name, children = Els}) ->
case Name of case Name of
"xdb" -> <<"xdb">> ->
lists:foreach( lists:foreach(fun (El) -> xdb_data(User, Server, El)
fun(El) -> end,
xdb_data(User, Server, El) Els);
end, Els); _ -> ok
_ ->
ok
end. end.
xdb_data(_User, _Server, {xmlcdata, _CData}) -> ok;
xdb_data(_User, _Server, {xmlcdata, _CData}) -> xdb_data(User, Server, #xmlel{attrs = Attrs} = El) ->
ok; From = jlib:make_jid(User, Server, <<"">>),
xdb_data(User, Server, {xmlelement, _Name, Attrs, _Els} = El) -> case xml:get_attr_s(<<"xmlns">>, Attrs) of
From = jlib:make_jid(User, Server, ""),
case xml:get_attr_s("xmlns", Attrs) of
?NS_AUTH -> ?NS_AUTH ->
Password = xml:get_tag_cdata(El), Password = xml:get_tag_cdata(El),
ejabberd_auth:set_password(User, Server, Password), ejabberd_auth:set_password(User, Server, Password),
ok; ok;
?NS_ROSTER -> ?NS_ROSTER ->
catch mod_roster:set_items(User, Server, El), catch mod_roster:set_items(User, Server, El), ok;
ok;
?NS_LAST -> ?NS_LAST ->
TimeStamp = xml:get_attr_s("last", Attrs), TimeStamp = xml:get_attr_s(<<"last">>, Attrs),
Status = xml:get_tag_cdata(El), Status = xml:get_tag_cdata(El),
catch mod_last:store_last_info( catch mod_last:store_last_info(User, Server,
User, jlib:binary_to_integer(TimeStamp),
Server,
list_to_integer(TimeStamp),
Status), Status),
ok; ok;
?NS_VCARD -> ?NS_VCARD ->
catch mod_vcard:process_sm_iq( catch mod_vcard:process_sm_iq(From,
From, jlib:make_jid(<<"">>, Server, <<"">>),
jlib:make_jid("", Server, ""), #iq{type = set, xmlns = ?NS_VCARD,
#iq{type = set, xmlns = ?NS_VCARD, sub_el = El}), sub_el = El}),
ok;
"jabber:x:offline" ->
process_offline(Server, From, El),
ok; ok;
<<"jabber:x:offline">> ->
process_offline(Server, From, El), ok;
XMLNS -> XMLNS ->
case xml:get_attr_s("j_private_flag", Attrs) of case xml:get_attr_s(<<"j_private_flag">>, Attrs) of
"1" -> <<"1">> ->
catch mod_private:process_sm_iq( catch mod_private:process_sm_iq(From,
From, jlib:make_jid(<<"">>, Server,
jlib:make_jid("", Server, ""), <<"">>),
#iq{type = set, xmlns = ?NS_PRIVATE, #iq{type = set,
sub_el = {xmlelement, "query", [], xmlns = ?NS_PRIVATE,
[jlib:remove_attr( sub_el =
"j_private_flag", #xmlel{name =
jlib:remove_attr("xdbns", El))]}}); <<"query">>,
attrs = [],
children =
[jlib:remove_attr(<<"j_private_flag">>,
jlib:remove_attr(<<"xdbns">>,
El))]}});
_ -> _ ->
?DEBUG("jd2ejd: Unknown namespace \"~s\"~n", [XMLNS]) ?DEBUG("jd2ejd: Unknown namespace \"~s\"~n", [XMLNS])
end, end,
ok ok
end. end.
process_offline(Server, To, #xmlel{children = Els}) ->
process_offline(Server, To, {xmlelement, _, _, Els}) ->
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
lists:foreach(fun({xmlelement, _, Attrs, _} = El) -> lists:foreach(fun (#xmlel{attrs = Attrs} = El) ->
FromS = xml:get_attr_s("from", Attrs), FromS = xml:get_attr_s(<<"from">>, Attrs),
From = case FromS of From = case FromS of
"" -> <<"">> ->
jlib:make_jid("", Server, ""); jlib:make_jid(<<"">>, Server, <<"">>);
_ -> _ -> jlib:string_to_jid(FromS)
jlib:string_to_jid(FromS)
end, end,
case From of case From of
error -> error -> ok;
ok;
_ -> _ ->
ejabberd_hooks:run(offline_message_hook, ejabberd_hooks:run(offline_message_hook,
LServer, LServer, [From, To, El])
[From, To, El])
end end
end, Els). end,
Els).

File diff suppressed because it is too large Load Diff

View File

@ -19,318 +19,687 @@
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-define(NS_DISCO_ITEMS, "http://jabber.org/protocol/disco#items").
-define(NS_DISCO_INFO, "http://jabber.org/protocol/disco#info").
-define(NS_VCARD, "vcard-temp").
-define(NS_VCARD_UPDATE, "vcard-temp:x:update").
-define(NS_AUTH, "jabber:iq:auth").
-define(NS_AUTH_ERROR, "jabber:iq:auth:error").
-define(NS_REGISTER, "jabber:iq:register").
-define(NS_SEARCH, "jabber:iq:search").
-define(NS_ROSTER, "jabber:iq:roster").
-define(NS_ROSTER_VER, "urn:xmpp:features:rosterver").
-define(NS_PRIVACY, "jabber:iq:privacy").
-define(NS_BLOCKING, "urn:xmpp:blocking").
-define(NS_PRIVATE, "jabber:iq:private").
-define(NS_VERSION, "jabber:iq:version").
-define(NS_TIME90, "jabber:iq:time"). % TODO: Remove once XEP-0090 is Obsolete
-define(NS_TIME, "urn:xmpp:time").
-define(NS_LAST, "jabber:iq:last").
-define(NS_XDATA, "jabber:x:data").
-define(NS_IQDATA, "jabber:iq:data").
-define(NS_DELAY91, "jabber:x:delay"). % TODO: Remove once XEP-0091 is Obsolete
-define(NS_DELAY, "urn:xmpp:delay").
-define(NS_EXPIRE, "jabber:x:expire").
-define(NS_EVENT, "jabber:x:event").
-define(NS_CHATSTATES, "http://jabber.org/protocol/chatstates").
-define(NS_XCONFERENCE, "jabber:x:conference").
-define(NS_STATS, "http://jabber.org/protocol/stats").
-define(NS_MUC, "http://jabber.org/protocol/muc").
-define(NS_MUC_USER, "http://jabber.org/protocol/muc#user").
-define(NS_MUC_ADMIN, "http://jabber.org/protocol/muc#admin").
-define(NS_MUC_OWNER, "http://jabber.org/protocol/muc#owner").
-define(NS_MUC_UNIQUE, "http://jabber.org/protocol/muc#unique").
-define(NS_PUBSUB, "http://jabber.org/protocol/pubsub").
-define(NS_PUBSUB_EVENT, "http://jabber.org/protocol/pubsub#event").
-define(NS_PUBSUB_OWNER, "http://jabber.org/protocol/pubsub#owner").
-define(NS_PUBSUB_NMI, "http://jabber.org/protocol/pubsub#node-meta-info").
-define(NS_PUBSUB_ERRORS,"http://jabber.org/protocol/pubsub#errors").
-define(NS_PUBSUB_NODE_CONFIG, "http://jabber.org/protocol/pubsub#node_config").
-define(NS_PUBSUB_SUB_OPTIONS, "http://jabber.org/protocol/pubsub#subscribe_options").
-define(NS_PUBSUB_SUB_AUTH, "http://jabber.org/protocol/pubsub#subscribe_authorization").
-define(NS_PUBSUB_GET_PENDING, "http://jabber.org/protocol/pubsub#get-pending").
-define(NS_COMMANDS, "http://jabber.org/protocol/commands").
-define(NS_BYTESTREAMS, "http://jabber.org/protocol/bytestreams").
-define(NS_ADMIN, "http://jabber.org/protocol/admin").
-define(NS_SERVERINFO, "http://jabber.org/network/serverinfo").
-define(NS_RSM, "http://jabber.org/protocol/rsm"). -define(NS_DISCO_ITEMS,
-define(NS_EJABBERD_CONFIG, "ejabberd:config"). <<"http://jabber.org/protocol/disco#items">>).
-define(NS_STREAM, "http://etherx.jabber.org/streams"). -define(NS_DISCO_INFO,
<<"http://jabber.org/protocol/disco#info">>).
-define(NS_STANZAS, "urn:ietf:params:xml:ns:xmpp-stanzas"). -define(NS_VCARD, <<"vcard-temp">>).
-define(NS_STREAMS, "urn:ietf:params:xml:ns:xmpp-streams").
-define(NS_TLS, "urn:ietf:params:xml:ns:xmpp-tls"). -define(NS_VCARD_UPDATE, <<"vcard-temp:x:update">>).
-define(NS_SASL, "urn:ietf:params:xml:ns:xmpp-sasl").
-define(NS_SESSION, "urn:ietf:params:xml:ns:xmpp-session").
-define(NS_BIND, "urn:ietf:params:xml:ns:xmpp-bind").
-define(NS_FEATURE_IQAUTH, "http://jabber.org/features/iq-auth"). -define(NS_AUTH, <<"jabber:iq:auth">>).
-define(NS_FEATURE_IQREGISTER, "http://jabber.org/features/iq-register").
-define(NS_FEATURE_COMPRESS, "http://jabber.org/features/compress").
-define(NS_FEATURE_MSGOFFLINE, "msgoffline").
-define(NS_COMPRESS, "http://jabber.org/protocol/compress"). -define(NS_AUTH_ERROR, <<"jabber:iq:auth:error">>).
-define(NS_CAPS, "http://jabber.org/protocol/caps"). -define(NS_REGISTER, <<"jabber:iq:register">>).
-define(NS_SHIM, "http://jabber.org/protocol/shim").
-define(NS_ADDRESS, "http://jabber.org/protocol/address").
%% CAPTCHA related NSes. -define(NS_SEARCH, <<"jabber:iq:search">>).
-define(NS_OOB, "jabber:x:oob").
-define(NS_CAPTCHA, "urn:xmpp:captcha"). -define(NS_ROSTER, <<"jabber:iq:roster">>).
-define(NS_MEDIA, "urn:xmpp:media-element").
-define(NS_BOB, "urn:xmpp:bob"). -define(NS_ROSTER_VER,
<<"urn:xmpp:features:rosterver">>).
-define(NS_PRIVACY, <<"jabber:iq:privacy">>).
-define(NS_BLOCKING, <<"urn:xmpp:blocking">>).
-define(NS_PRIVATE, <<"jabber:iq:private">>).
-define(NS_VERSION, <<"jabber:iq:version">>).
-define(NS_TIME90, <<"jabber:iq:time">>).
-define(NS_TIME, <<"urn:xmpp:time">>).
-define(NS_LAST, <<"jabber:iq:last">>).
-define(NS_XDATA, <<"jabber:x:data">>).
-define(NS_IQDATA, <<"jabber:iq:data">>).
-define(NS_DELAY91, <<"jabber:x:delay">>).
-define(NS_DELAY, <<"urn:xmpp:delay">>).
-define(NS_EXPIRE, <<"jabber:x:expire">>).
-define(NS_EVENT, <<"jabber:x:event">>).
-define(NS_CHATSTATES,
<<"http://jabber.org/protocol/chatstates">>).
-define(NS_XCONFERENCE, <<"jabber:x:conference">>).
-define(NS_STATS,
<<"http://jabber.org/protocol/stats">>).
-define(NS_MUC, <<"http://jabber.org/protocol/muc">>).
-define(NS_MUC_USER,
<<"http://jabber.org/protocol/muc#user">>).
-define(NS_MUC_ADMIN,
<<"http://jabber.org/protocol/muc#admin">>).
-define(NS_MUC_OWNER,
<<"http://jabber.org/protocol/muc#owner">>).
-define(NS_MUC_UNIQUE,
<<"http://jabber.org/protocol/muc#unique">>).
-define(NS_PUBSUB,
<<"http://jabber.org/protocol/pubsub">>).
-define(NS_PUBSUB_EVENT,
<<"http://jabber.org/protocol/pubsub#event">>).
-define(NS_PUBSUB_META_DATA,
<<"http://jabber.org/protocol/pubsub#meta-data">>).
-define(NS_PUBSUB_OWNER,
<<"http://jabber.org/protocol/pubsub#owner">>).
-define(NS_PUBSUB_NMI,
<<"http://jabber.org/protocol/pubsub#node-meta-info">>).
-define(NS_PUBSUB_ERRORS,
<<"http://jabber.org/protocol/pubsub#errors">>).
-define(NS_PUBSUB_NODE_CONFIG,
<<"http://jabber.org/protocol/pubsub#node_config">>).
-define(NS_PUBSUB_SUB_OPTIONS,
<<"http://jabber.org/protocol/pubsub#subscribe_options">>).
-define(NS_PUBSUB_SUBSCRIBE_OPTIONS,
<<"http://jabber.org/protocol/pubsub#subscribe_options">>).
-define(NS_PUBSUB_PUBLISH_OPTIONS,
<<"http://jabber.org/protocol/pubsub#publish_options">>).
-define(NS_PUBSUB_SUB_AUTH,
<<"http://jabber.org/protocol/pubsub#subscribe_authorization">>).
-define(NS_PUBSUB_GET_PENDING,
<<"http://jabber.org/protocol/pubsub#get-pending">>).
-define(NS_COMMANDS,
<<"http://jabber.org/protocol/commands">>).
-define(NS_BYTESTREAMS,
<<"http://jabber.org/protocol/bytestreams">>).
-define(NS_ADMIN,
<<"http://jabber.org/protocol/admin">>).
-define(NS_ADMIN_ANNOUNCE,
<<"http://jabber.org/protocol/admin#announce">>).
-define(NS_ADMIN_ANNOUNCE_ALL,
<<"http://jabber.org/protocol/admin#announce-all">>).
-define(NS_ADMIN_SET_MOTD,
<<"http://jabber.org/protocol/admin#set-motd">>).
-define(NS_ADMIN_EDIT_MOTD,
<<"http://jabber.org/protocol/admin#edit-motd">>).
-define(NS_ADMIN_DELETE_MOTD,
<<"http://jabber.org/protocol/admin#delete-motd">>).
-define(NS_ADMIN_ANNOUNCE_ALLHOSTS,
<<"http://jabber.org/protocol/admin#announce-allhosts">>).
-define(NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS,
<<"http://jabber.org/protocol/admin#announce-all-allhosts">>).
-define(NS_ADMIN_SET_MOTD_ALLHOSTS,
<<"http://jabber.org/protocol/admin#set-motd-allhosts">>).
-define(NS_ADMIN_EDIT_MOTD_ALLHOSTS,
<<"http://jabber.org/protocol/admin#edit-motd-allhosts">>).
-define(NS_ADMIN_DELETE_MOTD_ALLHOSTS,
<<"http://jabber.org/protocol/admin#delete-motd-allhosts">>).
-define(NS_SERVERINFO,
<<"http://jabber.org/network/serverinfo">>).
-define(NS_RSM, <<"http://jabber.org/protocol/rsm">>).
-define(NS_EJABBERD_CONFIG, <<"ejabberd:config">>).
-define(NS_STREAM,
<<"http://etherx.jabber.org/streams">>).
-define(NS_STANZAS,
<<"urn:ietf:params:xml:ns:xmpp-stanzas">>).
-define(NS_STREAMS,
<<"urn:ietf:params:xml:ns:xmpp-streams">>).
-define(NS_TLS, <<"urn:ietf:params:xml:ns:xmpp-tls">>).
-define(NS_SASL,
<<"urn:ietf:params:xml:ns:xmpp-sasl">>).
-define(NS_SESSION,
<<"urn:ietf:params:xml:ns:xmpp-session">>).
-define(NS_BIND,
<<"urn:ietf:params:xml:ns:xmpp-bind">>).
-define(NS_FEATURE_IQAUTH,
<<"http://jabber.org/features/iq-auth">>).
-define(NS_FEATURE_IQREGISTER,
<<"http://jabber.org/features/iq-register">>).
-define(NS_FEATURE_COMPRESS,
<<"http://jabber.org/features/compress">>).
-define(NS_FEATURE_MSGOFFLINE, <<"msgoffline">>).
-define(NS_COMPRESS,
<<"http://jabber.org/protocol/compress">>).
-define(NS_CAPS, <<"http://jabber.org/protocol/caps">>).
-define(NS_SHIM, <<"http://jabber.org/protocol/shim">>).
-define(NS_ADDRESS,
<<"http://jabber.org/protocol/address">>).
-define(NS_OOB, <<"jabber:x:oob">>).
-define(NS_CAPTCHA, <<"urn:xmpp:captcha">>).
-define(NS_MEDIA, <<"urn:xmpp:media-element">>).
-define(NS_BOB, <<"urn:xmpp:bob">>).
% TODO: remove "code" attribute (currently it used for backward-compatibility)
-define(STANZA_ERROR(Code, Type, Condition), -define(STANZA_ERROR(Code, Type, Condition),
{xmlelement, "error", #xmlel{name = <<"error">>,
[{"code", Code}, {"type", Type}], attrs = [{<<"code">>, Code}, {<<"type">>, Type}],
[{xmlelement, Condition, [{"xmlns", ?NS_STANZAS}], []}]}). children =
[#xmlel{name = Condition,
attrs = [{<<"xmlns">>, ?NS_STANZAS}],
children = []}]}).
-define(ERR_BAD_FORMAT, -define(ERR_BAD_FORMAT,
?STANZA_ERROR("406", "modify", "bad-format")). ?STANZA_ERROR(<<"406">>, <<"modify">>,
<<"bad-format">>)).
-define(ERR_BAD_REQUEST, -define(ERR_BAD_REQUEST,
?STANZA_ERROR("400", "modify", "bad-request")). ?STANZA_ERROR(<<"400">>, <<"modify">>,
<<"bad-request">>)).
-define(ERR_CONFLICT, -define(ERR_CONFLICT,
?STANZA_ERROR("409", "cancel", "conflict")). ?STANZA_ERROR(<<"409">>, <<"cancel">>, <<"conflict">>)).
-define(ERR_FEATURE_NOT_IMPLEMENTED, -define(ERR_FEATURE_NOT_IMPLEMENTED,
?STANZA_ERROR("501", "cancel", "feature-not-implemented")). ?STANZA_ERROR(<<"501">>, <<"cancel">>,
<<"feature-not-implemented">>)).
-define(ERR_FORBIDDEN, -define(ERR_FORBIDDEN,
?STANZA_ERROR("403", "auth", "forbidden")). ?STANZA_ERROR(<<"403">>, <<"auth">>, <<"forbidden">>)).
-define(ERR_GONE, -define(ERR_GONE,
?STANZA_ERROR("302", "modify", "gone")). ?STANZA_ERROR(<<"302">>, <<"modify">>, <<"gone">>)).
-define(ERR_INTERNAL_SERVER_ERROR, -define(ERR_INTERNAL_SERVER_ERROR,
?STANZA_ERROR("500", "wait", "internal-server-error")). ?STANZA_ERROR(<<"500">>, <<"wait">>,
<<"internal-server-error">>)).
-define(ERR_ITEM_NOT_FOUND, -define(ERR_ITEM_NOT_FOUND,
?STANZA_ERROR("404", "cancel", "item-not-found")). ?STANZA_ERROR(<<"404">>, <<"cancel">>,
<<"item-not-found">>)).
-define(ERR_JID_MALFORMED, -define(ERR_JID_MALFORMED,
?STANZA_ERROR("400", "modify", "jid-malformed")). ?STANZA_ERROR(<<"400">>, <<"modify">>,
<<"jid-malformed">>)).
-define(ERR_NOT_ACCEPTABLE, -define(ERR_NOT_ACCEPTABLE,
?STANZA_ERROR("406", "modify", "not-acceptable")). ?STANZA_ERROR(<<"406">>, <<"modify">>,
<<"not-acceptable">>)).
-define(ERR_NOT_ALLOWED, -define(ERR_NOT_ALLOWED,
?STANZA_ERROR("405", "cancel", "not-allowed")). ?STANZA_ERROR(<<"405">>, <<"cancel">>,
<<"not-allowed">>)).
-define(ERR_NOT_AUTHORIZED, -define(ERR_NOT_AUTHORIZED,
?STANZA_ERROR("401", "auth", "not-authorized")). ?STANZA_ERROR(<<"401">>, <<"auth">>,
<<"not-authorized">>)).
-define(ERR_PAYMENT_REQUIRED, -define(ERR_PAYMENT_REQUIRED,
?STANZA_ERROR("402", "auth", "payment-required")). ?STANZA_ERROR(<<"402">>, <<"auth">>,
<<"payment-required">>)).
-define(ERR_RECIPIENT_UNAVAILABLE, -define(ERR_RECIPIENT_UNAVAILABLE,
?STANZA_ERROR("404", "wait", "recipient-unavailable")). ?STANZA_ERROR(<<"404">>, <<"wait">>,
<<"recipient-unavailable">>)).
-define(ERR_REDIRECT, -define(ERR_REDIRECT,
?STANZA_ERROR("302", "modify", "redirect")). ?STANZA_ERROR(<<"302">>, <<"modify">>, <<"redirect">>)).
-define(ERR_REGISTRATION_REQUIRED, -define(ERR_REGISTRATION_REQUIRED,
?STANZA_ERROR("407", "auth", "registration-required")). ?STANZA_ERROR(<<"407">>, <<"auth">>,
<<"registration-required">>)).
-define(ERR_REMOTE_SERVER_NOT_FOUND, -define(ERR_REMOTE_SERVER_NOT_FOUND,
?STANZA_ERROR("404", "cancel", "remote-server-not-found")). ?STANZA_ERROR(<<"404">>, <<"cancel">>,
<<"remote-server-not-found">>)).
-define(ERR_REMOTE_SERVER_TIMEOUT, -define(ERR_REMOTE_SERVER_TIMEOUT,
?STANZA_ERROR("504", "wait", "remote-server-timeout")). ?STANZA_ERROR(<<"504">>, <<"wait">>,
<<"remote-server-timeout">>)).
-define(ERR_RESOURCE_CONSTRAINT, -define(ERR_RESOURCE_CONSTRAINT,
?STANZA_ERROR("500", "wait", "resource-constraint")). ?STANZA_ERROR(<<"500">>, <<"wait">>,
<<"resource-constraint">>)).
-define(ERR_SERVICE_UNAVAILABLE, -define(ERR_SERVICE_UNAVAILABLE,
?STANZA_ERROR("503", "cancel", "service-unavailable")). ?STANZA_ERROR(<<"503">>, <<"cancel">>,
<<"service-unavailable">>)).
-define(ERR_SUBSCRIPTION_REQUIRED, -define(ERR_SUBSCRIPTION_REQUIRED,
?STANZA_ERROR("407", "auth", "subscription-required")). ?STANZA_ERROR(<<"407">>, <<"auth">>,
<<"subscription-required">>)).
-define(ERR_UNEXPECTED_REQUEST, -define(ERR_UNEXPECTED_REQUEST,
?STANZA_ERROR("400", "wait", "unexpected-request")). ?STANZA_ERROR(<<"400">>, <<"wait">>,
<<"unexpected-request">>)).
-define(ERR_UNEXPECTED_REQUEST_CANCEL, -define(ERR_UNEXPECTED_REQUEST_CANCEL,
?STANZA_ERROR("401", "cancel", "unexpected-request")). ?STANZA_ERROR(<<"401">>, <<"cancel">>,
<<"unexpected-request">>)).
%-define(ERR_, %-define(ERR_,
% ?STANZA_ERROR("", "", "")). % ?STANZA_ERROR("", "", "")).
-define(STANZA_ERRORT(Code, Type, Condition, Lang, Text), -define(STANZA_ERRORT(Code, Type, Condition, Lang,
{xmlelement, "error", Text),
[{"code", Code}, {"type", Type}], #xmlel{name = <<"error">>,
[{xmlelement, Condition, [{"xmlns", ?NS_STANZAS}], []}, attrs = [{<<"code">>, Code}, {<<"type">>, Type}],
{xmlelement, "text", [{"xmlns", ?NS_STANZAS}], children =
[{xmlcdata, translate:translate(Lang, Text)}]}]}). [#xmlel{name = Condition,
attrs = [{<<"xmlns">>, ?NS_STANZAS}], children = []},
#xmlel{name = <<"text">>,
attrs = [{<<"xmlns">>, ?NS_STANZAS}],
children =
[{xmlcdata,
translate:translate(Lang, Text)}]}]}).
-define(ERRT_BAD_FORMAT(Lang, Text), -define(ERRT_BAD_FORMAT(Lang, Text),
?STANZA_ERRORT("406", "modify", "bad-format", Lang, Text)). ?STANZA_ERRORT(<<"406">>, <<"modify">>,
<<"bad-format">>, Lang, Text)).
-define(ERRT_BAD_REQUEST(Lang, Text), -define(ERRT_BAD_REQUEST(Lang, Text),
?STANZA_ERRORT("400", "modify", "bad-request", Lang, Text)). ?STANZA_ERRORT(<<"400">>, <<"modify">>,
<<"bad-request">>, Lang, Text)).
-define(ERRT_CONFLICT(Lang, Text), -define(ERRT_CONFLICT(Lang, Text),
?STANZA_ERRORT("409", "cancel", "conflict", Lang, Text)). ?STANZA_ERRORT(<<"409">>, <<"cancel">>, <<"conflict">>,
Lang, Text)).
-define(ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Text), -define(ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Text),
?STANZA_ERRORT("501", "cancel", "feature-not-implemented", Lang, Text)). ?STANZA_ERRORT(<<"501">>, <<"cancel">>,
<<"feature-not-implemented">>, Lang, Text)).
-define(ERRT_FORBIDDEN(Lang, Text), -define(ERRT_FORBIDDEN(Lang, Text),
?STANZA_ERRORT("403", "auth", "forbidden", Lang, Text)). ?STANZA_ERRORT(<<"403">>, <<"auth">>, <<"forbidden">>,
Lang, Text)).
-define(ERRT_GONE(Lang, Text), -define(ERRT_GONE(Lang, Text),
?STANZA_ERRORT("302", "modify", "gone", Lang, Text)). ?STANZA_ERRORT(<<"302">>, <<"modify">>, <<"gone">>,
Lang, Text)).
-define(ERRT_INTERNAL_SERVER_ERROR(Lang, Text), -define(ERRT_INTERNAL_SERVER_ERROR(Lang, Text),
?STANZA_ERRORT("500", "wait", "internal-server-error", Lang, Text)). ?STANZA_ERRORT(<<"500">>, <<"wait">>,
<<"internal-server-error">>, Lang, Text)).
-define(ERRT_ITEM_NOT_FOUND(Lang, Text), -define(ERRT_ITEM_NOT_FOUND(Lang, Text),
?STANZA_ERRORT("404", "cancel", "item-not-found", Lang, Text)). ?STANZA_ERRORT(<<"404">>, <<"cancel">>,
<<"item-not-found">>, Lang, Text)).
-define(ERRT_JID_MALFORMED(Lang, Text), -define(ERRT_JID_MALFORMED(Lang, Text),
?STANZA_ERRORT("400", "modify", "jid-malformed", Lang, Text)). ?STANZA_ERRORT(<<"400">>, <<"modify">>,
<<"jid-malformed">>, Lang, Text)).
-define(ERRT_NOT_ACCEPTABLE(Lang, Text), -define(ERRT_NOT_ACCEPTABLE(Lang, Text),
?STANZA_ERRORT("406", "modify", "not-acceptable", Lang, Text)). ?STANZA_ERRORT(<<"406">>, <<"modify">>,
<<"not-acceptable">>, Lang, Text)).
-define(ERRT_NOT_ALLOWED(Lang, Text), -define(ERRT_NOT_ALLOWED(Lang, Text),
?STANZA_ERRORT("405", "cancel", "not-allowed", Lang, Text)). ?STANZA_ERRORT(<<"405">>, <<"cancel">>,
<<"not-allowed">>, Lang, Text)).
-define(ERRT_NOT_AUTHORIZED(Lang, Text), -define(ERRT_NOT_AUTHORIZED(Lang, Text),
?STANZA_ERRORT("401", "auth", "not-authorized", Lang, Text)). ?STANZA_ERRORT(<<"401">>, <<"auth">>,
<<"not-authorized">>, Lang, Text)).
-define(ERRT_PAYMENT_REQUIRED(Lang, Text), -define(ERRT_PAYMENT_REQUIRED(Lang, Text),
?STANZA_ERRORT("402", "auth", "payment-required", Lang, Text)). ?STANZA_ERRORT(<<"402">>, <<"auth">>,
<<"payment-required">>, Lang, Text)).
-define(ERRT_RECIPIENT_UNAVAILABLE(Lang, Text), -define(ERRT_RECIPIENT_UNAVAILABLE(Lang, Text),
?STANZA_ERRORT("404", "wait", "recipient-unavailable", Lang, Text)). ?STANZA_ERRORT(<<"404">>, <<"wait">>,
<<"recipient-unavailable">>, Lang, Text)).
-define(ERRT_REDIRECT(Lang, Text), -define(ERRT_REDIRECT(Lang, Text),
?STANZA_ERRORT("302", "modify", "redirect", Lang, Text)). ?STANZA_ERRORT(<<"302">>, <<"modify">>, <<"redirect">>,
Lang, Text)).
-define(ERRT_REGISTRATION_REQUIRED(Lang, Text), -define(ERRT_REGISTRATION_REQUIRED(Lang, Text),
?STANZA_ERRORT("407", "auth", "registration-required", Lang, Text)). ?STANZA_ERRORT(<<"407">>, <<"auth">>,
<<"registration-required">>, Lang, Text)).
-define(ERRT_REMOTE_SERVER_NOT_FOUND(Lang, Text), -define(ERRT_REMOTE_SERVER_NOT_FOUND(Lang, Text),
?STANZA_ERRORT("404", "cancel", "remote-server-not-found", Lang, Text)). ?STANZA_ERRORT(<<"404">>, <<"cancel">>,
<<"remote-server-not-found">>, Lang, Text)).
-define(ERRT_REMOTE_SERVER_TIMEOUT(Lang, Text), -define(ERRT_REMOTE_SERVER_TIMEOUT(Lang, Text),
?STANZA_ERRORT("504", "wait", "remote-server-timeout", Lang, Text)). ?STANZA_ERRORT(<<"504">>, <<"wait">>,
<<"remote-server-timeout">>, Lang, Text)).
-define(ERRT_RESOURCE_CONSTRAINT(Lang, Text), -define(ERRT_RESOURCE_CONSTRAINT(Lang, Text),
?STANZA_ERRORT("500", "wait", "resource-constraint", Lang, Text)). ?STANZA_ERRORT(<<"500">>, <<"wait">>,
<<"resource-constraint">>, Lang, Text)).
-define(ERRT_SERVICE_UNAVAILABLE(Lang, Text), -define(ERRT_SERVICE_UNAVAILABLE(Lang, Text),
?STANZA_ERRORT("503", "cancel", "service-unavailable", Lang, Text)). ?STANZA_ERRORT(<<"503">>, <<"cancel">>,
<<"service-unavailable">>, Lang, Text)).
-define(ERRT_SUBSCRIPTION_REQUIRED(Lang, Text), -define(ERRT_SUBSCRIPTION_REQUIRED(Lang, Text),
?STANZA_ERRORT("407", "auth", "subscription-required", Lang, Text)). ?STANZA_ERRORT(<<"407">>, <<"auth">>,
<<"subscription-required">>, Lang, Text)).
-define(ERRT_UNEXPECTED_REQUEST(Lang, Text), -define(ERRT_UNEXPECTED_REQUEST(Lang, Text),
?STANZA_ERRORT("400", "wait", "unexpected-request", Lang, Text)). ?STANZA_ERRORT(<<"400">>, <<"wait">>,
<<"unexpected-request">>, Lang, Text)).
% Auth stanza errors
-define(ERR_AUTH_NO_RESOURCE_PROVIDED(Lang), -define(ERR_AUTH_NO_RESOURCE_PROVIDED(Lang),
?ERRT_NOT_ACCEPTABLE(Lang, "No resource provided")). ?ERRT_NOT_ACCEPTABLE(Lang, <<"No resource provided">>)).
-define(ERR_AUTH_BAD_RESOURCE_FORMAT(Lang), -define(ERR_AUTH_BAD_RESOURCE_FORMAT(Lang),
?ERRT_NOT_ACCEPTABLE(Lang, "Illegal resource format")). ?ERRT_NOT_ACCEPTABLE(Lang,
<<"Illegal resource format">>)).
-define(ERR_AUTH_RESOURCE_CONFLICT(Lang), -define(ERR_AUTH_RESOURCE_CONFLICT(Lang),
?ERRT_CONFLICT(Lang, "Resource conflict")). ?ERRT_CONFLICT(Lang, <<"Resource conflict">>)).
-define(STREAM_ERROR(Condition, Cdata),
-define(STREAM_ERROR(Condition), #xmlel{name = <<"stream:error">>, attrs = [],
{xmlelement, "stream:error", children =
[], [#xmlel{name = Condition,
[{xmlelement, Condition, [{"xmlns", ?NS_STREAMS}], []}]}). attrs = [{<<"xmlns">>, ?NS_STREAMS}],
children = [{xmlcdata, Cdata}]}]}).
-define(SERR_BAD_FORMAT, -define(SERR_BAD_FORMAT,
?STREAM_ERROR("bad-format")). ?STREAM_ERROR(<<"bad-format">>, <<"">>)).
-define(SERR_BAD_NAMESPACE_PREFIX,
?STREAM_ERROR("bad-namespace-prefix")).
-define(SERR_CONFLICT,
?STREAM_ERROR("conflict")).
-define(SERR_CONNECTION_TIMEOUT,
?STREAM_ERROR("connection-timeout")).
-define(SERR_HOST_GONE,
?STREAM_ERROR("host-gone")).
-define(SERR_HOST_UNKNOWN,
?STREAM_ERROR("host-unknown")).
-define(SERR_IMPROPER_ADDRESSING,
?STREAM_ERROR("improper-addressing")).
-define(SERR_INTERNAL_SERVER_ERROR,
?STREAM_ERROR("internal-server-error")).
-define(SERR_INVALID_FROM,
?STREAM_ERROR("invalid-from")).
-define(SERR_INVALID_ID,
?STREAM_ERROR("invalid-id")).
-define(SERR_INVALID_NAMESPACE,
?STREAM_ERROR("invalid-namespace")).
-define(SERR_INVALID_XML,
?STREAM_ERROR("invalid-xml")).
-define(SERR_NOT_AUTHORIZED,
?STREAM_ERROR("not-authorized")).
-define(SERR_POLICY_VIOLATION,
?STREAM_ERROR("policy-violation")).
-define(SERR_REMOTE_CONNECTION_FAILED,
?STREAM_ERROR("remote-connection-failed")).
-define(SERR_RESOURSE_CONSTRAINT,
?STREAM_ERROR("resource-constraint")).
-define(SERR_RESTRICTED_XML,
?STREAM_ERROR("restricted-xml")).
% TODO: include hostname or IP
-define(SERR_SEE_OTHER_HOST,
?STREAM_ERROR("see-other-host")).
-define(SERR_SYSTEM_SHUTDOWN,
?STREAM_ERROR("system-shutdown")).
-define(SERR_UNSUPPORTED_ENCODING,
?STREAM_ERROR("unsupported-encoding")).
-define(SERR_UNSUPPORTED_STANZA_TYPE,
?STREAM_ERROR("unsupported-stanza-type")).
-define(SERR_UNSUPPORTED_VERSION,
?STREAM_ERROR("unsupported-version")).
-define(SERR_XML_NOT_WELL_FORMED,
?STREAM_ERROR("xml-not-well-formed")).
%-define(SERR_,
% ?STREAM_ERROR("")).
-define(STREAM_ERRORT(Condition, Lang, Text), -define(SERR_BAD_NAMESPACE_PREFIX,
{xmlelement, "stream:error", ?STREAM_ERROR(<<"bad-namespace-prefix">>, <<"">>)).
[],
[{xmlelement, Condition, [{"xmlns", ?NS_STREAMS}], []}, -define(SERR_CONFLICT,
{xmlelement, "text", [{"xml:lang", Lang}, {"xmlns", ?NS_STREAMS}], ?STREAM_ERROR(<<"conflict">>, <<"">>)).
[{xmlcdata, translate:translate(Lang, Text)}]}]}).
-define(SERR_CONNECTION_TIMEOUT,
?STREAM_ERROR(<<"connection-timeout">>, <<"">>)).
-define(SERR_HOST_GONE,
?STREAM_ERROR(<<"host-gone">>, <<"">>)).
-define(SERR_HOST_UNKNOWN,
?STREAM_ERROR(<<"host-unknown">>, <<"">>)).
-define(SERR_IMPROPER_ADDRESSING,
?STREAM_ERROR(<<"improper-addressing">>, <<"">>)).
-define(SERR_INTERNAL_SERVER_ERROR,
?STREAM_ERROR(<<"internal-server-error">>, <<"">>)).
-define(SERR_INVALID_FROM,
?STREAM_ERROR(<<"invalid-from">>, <<"">>)).
-define(SERR_INVALID_ID,
?STREAM_ERROR(<<"invalid-id">>, <<"">>)).
-define(SERR_INVALID_NAMESPACE,
?STREAM_ERROR(<<"invalid-namespace">>, <<"">>)).
-define(SERR_INVALID_XML,
?STREAM_ERROR(<<"invalid-xml">>, <<"">>)).
-define(SERR_NOT_AUTHORIZED,
?STREAM_ERROR(<<"not-authorized">>, <<"">>)).
-define(SERR_POLICY_VIOLATION,
?STREAM_ERROR(<<"policy-violation">>, <<"">>)).
-define(SERR_REMOTE_CONNECTION_FAILED,
?STREAM_ERROR(<<"remote-connection-failed">>, <<"">>)).
-define(SERR_RESOURSE_CONSTRAINT,
?STREAM_ERROR(<<"resource-constraint">>, <<"">>)).
-define(SERR_RESTRICTED_XML,
?STREAM_ERROR(<<"restricted-xml">>, <<"">>)).
-define(SERR_SEE_OTHER_HOST(Host),
?STREAM_ERROR(<<"see-other-host">>, Host)).
-define(SERR_SYSTEM_SHUTDOWN,
?STREAM_ERROR(<<"system-shutdown">>, <<"">>)).
-define(SERR_UNSUPPORTED_ENCODING,
?STREAM_ERROR(<<"unsupported-encoding">>, <<"">>)).
-define(SERR_UNSUPPORTED_STANZA_TYPE,
?STREAM_ERROR(<<"unsupported-stanza-type">>, <<"">>)).
-define(SERR_UNSUPPORTED_VERSION,
?STREAM_ERROR(<<"unsupported-version">>, <<"">>)).
-define(SERR_XML_NOT_WELL_FORMED,
?STREAM_ERROR(<<"xml-not-well-formed">>, <<"">>)).
%-define(SERR_,
% ?STREAM_ERROR("", "")).
-define(STREAM_ERRORT(Condition, Cdata, Lang, Text),
#xmlel{name = <<"stream:error">>, attrs = [],
children =
[#xmlel{name = Condition,
attrs = [{<<"xmlns">>, ?NS_STREAMS}],
children = [{xmlcdata, Cdata}]},
#xmlel{name = <<"text">>,
attrs =
[{<<"xml:lang">>, Lang},
{<<"xmlns">>, ?NS_STREAMS}],
children =
[{xmlcdata,
translate:translate(Lang, Text)}]}]}).
-define(SERRT_BAD_FORMAT(Lang, Text), -define(SERRT_BAD_FORMAT(Lang, Text),
?STREAM_ERRORT("bad-format", Lang, Text)). ?STREAM_ERRORT(<<"bad-format">>, <<"">>, Lang, Text)).
-define(SERRT_BAD_NAMESPACE_PREFIX(Lang, Text), -define(SERRT_BAD_NAMESPACE_PREFIX(Lang, Text),
?STREAM_ERRORT("bad-namespace-prefix", Lang, Text)). ?STREAM_ERRORT(<<"bad-namespace-prefix">>, <<"">>, Lang,
Text)).
-define(SERRT_CONFLICT(Lang, Text), -define(SERRT_CONFLICT(Lang, Text),
?STREAM_ERRORT("conflict", Lang, Text)). ?STREAM_ERRORT(<<"conflict">>, <<"">>, Lang, Text)).
-define(SERRT_CONNECTION_TIMEOUT(Lang, Text), -define(SERRT_CONNECTION_TIMEOUT(Lang, Text),
?STREAM_ERRORT("connection-timeout", Lang, Text)). ?STREAM_ERRORT(<<"connection-timeout">>, <<"">>, Lang,
Text)).
-define(SERRT_HOST_GONE(Lang, Text), -define(SERRT_HOST_GONE(Lang, Text),
?STREAM_ERRORT("host-gone", Lang, Text)). ?STREAM_ERRORT(<<"host-gone">>, <<"">>, Lang, Text)).
-define(SERRT_HOST_UNKNOWN(Lang, Text), -define(SERRT_HOST_UNKNOWN(Lang, Text),
?STREAM_ERRORT("host-unknown", Lang, Text)). ?STREAM_ERRORT(<<"host-unknown">>, <<"">>, Lang, Text)).
-define(SERRT_IMPROPER_ADDRESSING(Lang, Text), -define(SERRT_IMPROPER_ADDRESSING(Lang, Text),
?STREAM_ERRORT("improper-addressing", Lang, Text)). ?STREAM_ERRORT(<<"improper-addressing">>, <<"">>, Lang,
Text)).
-define(SERRT_INTERNAL_SERVER_ERROR(Lang, Text), -define(SERRT_INTERNAL_SERVER_ERROR(Lang, Text),
?STREAM_ERRORT("internal-server-error", Lang, Text)). ?STREAM_ERRORT(<<"internal-server-error">>, <<"">>,
Lang, Text)).
-define(SERRT_INVALID_FROM(Lang, Text), -define(SERRT_INVALID_FROM(Lang, Text),
?STREAM_ERRORT("invalid-from", Lang, Text)). ?STREAM_ERRORT(<<"invalid-from">>, <<"">>, Lang, Text)).
-define(SERRT_INVALID_ID(Lang, Text), -define(SERRT_INVALID_ID(Lang, Text),
?STREAM_ERRORT("invalid-id", Lang, Text)). ?STREAM_ERRORT(<<"invalid-id">>, <<"">>, Lang, Text)).
-define(SERRT_INVALID_NAMESPACE(Lang, Text), -define(SERRT_INVALID_NAMESPACE(Lang, Text),
?STREAM_ERRORT("invalid-namespace", Lang, Text)). ?STREAM_ERRORT(<<"invalid-namespace">>, <<"">>, Lang,
Text)).
-define(SERRT_INVALID_XML(Lang, Text), -define(SERRT_INVALID_XML(Lang, Text),
?STREAM_ERRORT("invalid-xml", Lang, Text)). ?STREAM_ERRORT(<<"invalid-xml">>, <<"">>, Lang, Text)).
-define(SERRT_NOT_AUTHORIZED(Lang, Text), -define(SERRT_NOT_AUTHORIZED(Lang, Text),
?STREAM_ERRORT("not-authorized", Lang, Text)). ?STREAM_ERRORT(<<"not-authorized">>, <<"">>, Lang,
Text)).
-define(SERRT_POLICY_VIOLATION(Lang, Text), -define(SERRT_POLICY_VIOLATION(Lang, Text),
?STREAM_ERRORT("policy-violation", Lang, Text)). ?STREAM_ERRORT(<<"policy-violation">>, <<"">>, Lang,
Text)).
-define(SERRT_REMOTE_CONNECTION_FAILED(Lang, Text), -define(SERRT_REMOTE_CONNECTION_FAILED(Lang, Text),
?STREAM_ERRORT("remote-connection-failed", Lang, Text)). ?STREAM_ERRORT(<<"remote-connection-failed">>, <<"">>,
Lang, Text)).
-define(SERRT_RESOURSE_CONSTRAINT(Lang, Text), -define(SERRT_RESOURSE_CONSTRAINT(Lang, Text),
?STREAM_ERRORT("resource-constraint", Lang, Text)). ?STREAM_ERRORT(<<"resource-constraint">>, <<"">>, Lang,
Text)).
-define(SERRT_RESTRICTED_XML(Lang, Text), -define(SERRT_RESTRICTED_XML(Lang, Text),
?STREAM_ERRORT("restricted-xml", Lang, Text)). ?STREAM_ERRORT(<<"restricted-xml">>, <<"">>, Lang,
% TODO: include hostname or IP Text)).
-define(SERRT_SEE_OTHER_HOST(Lang, Text),
?STREAM_ERRORT("see-other-host", Lang, Text)). -define(SERRT_SEE_OTHER_HOST(Host, Lang, Text),
?STREAM_ERRORT(<<"see-other-host">>, Host, Lang, Text)).
-define(SERRT_SYSTEM_SHUTDOWN(Lang, Text), -define(SERRT_SYSTEM_SHUTDOWN(Lang, Text),
?STREAM_ERRORT("system-shutdown", Lang, Text)). ?STREAM_ERRORT(<<"system-shutdown">>, <<"">>, Lang,
Text)).
-define(SERRT_UNSUPPORTED_ENCODING(Lang, Text), -define(SERRT_UNSUPPORTED_ENCODING(Lang, Text),
?STREAM_ERRORT("unsupported-encoding", Lang, Text)). ?STREAM_ERRORT(<<"unsupported-encoding">>, <<"">>, Lang,
Text)).
-define(SERRT_UNSUPPORTED_STANZA_TYPE(Lang, Text), -define(SERRT_UNSUPPORTED_STANZA_TYPE(Lang, Text),
?STREAM_ERRORT("unsupported-stanza-type", Lang, Text)). ?STREAM_ERRORT(<<"unsupported-stanza-type">>, <<"">>,
Lang, Text)).
-define(SERRT_UNSUPPORTED_VERSION(Lang, Text), -define(SERRT_UNSUPPORTED_VERSION(Lang, Text),
?STREAM_ERRORT("unsupported-version", Lang, Text)). ?STREAM_ERRORT(<<"unsupported-version">>, <<"">>, Lang,
Text)).
-define(SERRT_XML_NOT_WELL_FORMED(Lang, Text), -define(SERRT_XML_NOT_WELL_FORMED(Lang, Text),
?STREAM_ERRORT("xml-not-well-formed", Lang, Text)). ?STREAM_ERRORT(<<"xml-not-well-formed">>, <<"">>, Lang,
%-define(SERRT_(Lang, Text), Text)).
% ?STREAM_ERRORT("", Lang, Text)).
-record(jid, {user = <<"">> :: binary(),
server = <<"">> :: binary(),
resource = <<"">> :: binary(),
luser = <<"">> :: binary(),
lserver = <<"">> :: binary(),
lresource = <<"">> :: binary()}).
-record(jid, {user, server, resource, -type(jid() :: #jid{}).
luser, lserver, lresource}).
-record(iq, {id = "", -type(ljid() :: {binary(), binary(), binary()}).
type,
xmlns = "",
lang = "",
sub_el}).
-record(rsm_in, {max, direction, id, index}). -record(xmlel,
-record(rsm_out, {count, index, first, last}). {
name = <<"">> :: binary(),
attrs = [] :: [attr()],
children = [] :: [xmlel() | cdata()]
}).
-type(cdata() :: {xmlcdata, CData::binary()}).
-type(attr() :: {Name::binary(), Value::binary()}).
-type(xmlel() :: #xmlel{}).
-record(iq, {id = <<"">> :: binary(),
type = get :: get | set | result | error,
xmlns = <<"">> :: binary(),
lang = <<"">> :: binary(),
sub_el = #xmlel{} :: xmlel() | [xmlel()]}).
-type(iq_get()
:: #iq{
id :: binary(),
type :: get,
xmlns :: binary(),
lang :: binary(),
sub_el :: xmlel()
}
).
-type(iq_set()
:: #iq{
id :: binary(),
type :: set,
xmlns :: binary(),
lang :: binary(),
sub_el :: xmlel()
}
).
-type iq_request() :: iq_get() | iq_set().
-type(iq_result()
:: #iq{
id :: binary(),
type :: result,
xmlns :: binary(),
lang :: binary(),
sub_el :: [xmlel()]
}
).
-type(iq_error()
:: #iq{
id :: binary(),
type :: error,
xmlns :: binary(),
lang :: binary(),
sub_el :: [xmlel()]
}
).
-type iq_reply() :: iq_result() | iq_error() .
-type(iq() :: iq_request() | iq_reply()).
-record(rsm_in, {max :: integer(),
direction :: before | aft,
id :: binary(),
index :: integer()}).
-record(rsm_out, {count :: integer(),
index :: integer(),
first :: binary(),
last :: binary()}).
-type(rsm_in() :: #rsm_in{}).
-type(rsm_out() :: #rsm_out{}).
-type broadcast() :: {broadcast, broadcast_data()}.
-type broadcast_data() ::
{rebind, pid(), binary()} | %% ejabberd_c2s
{item, ljid(), mod_roster:subscription()} | %% mod_roster/mod_shared_roster
{exit, binary()} | %% mod_roster/mod_shared_roster
{privacy_list, mod_privacy:userlist(), binary()} | %% mod_privacy
{blocking, unblock_all | {block | unblock, [ljid()]}}. %% mod_blocking
-record(xmlelement, {name = "" :: string(),
attrs = [] :: [{string(), string()}],
children = [] :: [{xmlcdata, iodata()} | xmlelement()]}).
-type xmlelement() :: #xmlelement{}.

View File

@ -25,129 +25,155 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(mod_adhoc). -module(mod_adhoc).
-author('henoch@dtek.chalmers.se'). -author('henoch@dtek.chalmers.se').
-behaviour(gen_mod). -behaviour(gen_mod).
-export([start/2, -export([start/2, stop/1, process_local_iq/3,
stop/1, process_sm_iq/3, get_local_commands/5,
process_local_iq/3, get_local_identity/5, get_local_features/5,
process_sm_iq/3, get_sm_commands/5, get_sm_identity/5, get_sm_features/5,
get_local_commands/5, ping_item/4, ping_command/4]).
get_local_identity/5,
get_local_features/5,
get_sm_commands/5,
get_sm_identity/5,
get_sm_features/5,
ping_item/4,
ping_command/4]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl"). -include("jlib.hrl").
-include("adhoc.hrl"). -include("adhoc.hrl").
start(Host, Opts) -> start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_COMMANDS, gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?MODULE, process_local_iq, IQDisc), ?NS_COMMANDS, ?MODULE, process_local_iq,
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_COMMANDS, IQDisc),
?MODULE, process_sm_iq, IQDisc), gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_COMMANDS, ?MODULE, process_sm_iq, IQDisc),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, get_local_identity, 99), ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, get_local_features, 99), get_local_identity, 99),
ejabberd_hooks:add(disco_local_items, Host, ?MODULE, get_local_commands, 99), ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, get_sm_identity, 99), get_local_features, 99),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 99), ejabberd_hooks:add(disco_local_items, Host, ?MODULE,
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, get_sm_commands, 99), get_local_commands, 99),
ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE, ping_item, 100), ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE,
ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE, ping_command, 100). get_sm_identity, 99),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
get_sm_features, 99),
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE,
get_sm_commands, 99),
ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE,
ping_item, 100),
ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE,
ping_command, 100).
stop(Host) -> stop(Host) ->
ejabberd_hooks:delete(adhoc_local_commands, Host, ?MODULE, ping_command, 100), ejabberd_hooks:delete(adhoc_local_commands, Host,
ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE, ping_item, 100), ?MODULE, ping_command, 100),
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, get_sm_commands, 99), ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE,
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 99), ping_item, 100),
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, get_sm_identity, 99), ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE,
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, get_local_commands, 99), get_sm_commands, 99),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_local_features, 99), ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, get_local_identity, 99), get_sm_features, 99),
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE,
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_COMMANDS), get_sm_identity, 99),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS). ejabberd_hooks:delete(disco_local_items, Host, ?MODULE,
get_local_commands, 99),
ejabberd_hooks:delete(disco_local_features, Host,
?MODULE, get_local_features, 99),
ejabberd_hooks:delete(disco_local_identity, Host,
?MODULE, get_local_identity, 99),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_COMMANDS),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
?NS_COMMANDS).
%------------------------------------------------------------------------- %-------------------------------------------------------------------------
get_local_commands(Acc, _From, #jid{server = Server, lserver = LServer} = _To, "", Lang) -> get_local_commands(Acc, _From,
Display = gen_mod:get_module_opt(LServer, ?MODULE, report_commands_node, false), #jid{server = Server, lserver = LServer} = _To, <<"">>,
Lang) ->
Display = gen_mod:get_module_opt(LServer, ?MODULE,
report_commands_node,
fun(B) when is_boolean(B) -> B end,
false),
case Display of case Display of
false -> false -> Acc;
Acc;
_ -> _ ->
Items = case Acc of Items = case Acc of
{result, I} -> I; {result, I} -> I;
_ -> [] _ -> []
end, end,
Nodes = [{xmlelement, Nodes = [#xmlel{name = <<"item">>,
"item", attrs =
[{"jid", Server}, [{<<"jid">>, Server}, {<<"node">>, ?NS_COMMANDS},
{"node", ?NS_COMMANDS}, {<<"name">>,
{"name", translate:translate(Lang, "Commands")}], translate:translate(Lang, <<"Commands">>)}],
[]}], children = []}],
{result, Items ++ Nodes} {result, Items ++ Nodes}
end; end;
get_local_commands(_Acc, From,
get_local_commands(_Acc, From, #jid{lserver = LServer} = To, ?NS_COMMANDS, Lang) -> #jid{lserver = LServer} = To, ?NS_COMMANDS, Lang) ->
ejabberd_hooks:run_fold(adhoc_local_items, LServer, {result, []}, [From, To, Lang]); ejabberd_hooks:run_fold(adhoc_local_items, LServer,
{result, []}, [From, To, Lang]);
get_local_commands(_Acc, _From, _To, "ping", _Lang) -> get_local_commands(_Acc, _From, _To, <<"ping">>,
_Lang) ->
{result, []}; {result, []};
get_local_commands(Acc, _From, _To, _Node, _Lang) -> get_local_commands(Acc, _From, _To, _Node, _Lang) ->
Acc. Acc.
%------------------------------------------------------------------------- %-------------------------------------------------------------------------
get_sm_commands(Acc, _From, #jid{lserver = LServer} = To, "", Lang) -> get_sm_commands(Acc, _From,
Display = gen_mod:get_module_opt(LServer, ?MODULE, report_commands_node, false), #jid{lserver = LServer} = To, <<"">>, Lang) ->
Display = gen_mod:get_module_opt(LServer, ?MODULE,
report_commands_node,
fun(B) when is_boolean(B) -> B end,
false),
case Display of case Display of
false -> false -> Acc;
Acc;
_ -> _ ->
Items = case Acc of Items = case Acc of
{result, I} -> I; {result, I} -> I;
_ -> [] _ -> []
end, end,
Nodes = [{xmlelement, Nodes = [#xmlel{name = <<"item">>,
"item", attrs =
[{"jid", jlib:jid_to_string(To)}, [{<<"jid">>, jlib:jid_to_string(To)},
{"node", ?NS_COMMANDS}, {<<"node">>, ?NS_COMMANDS},
{"name", translate:translate(Lang, "Commands")}], {<<"name">>,
[]}], translate:translate(Lang, <<"Commands">>)}],
children = []}],
{result, Items ++ Nodes} {result, Items ++ Nodes}
end; end;
get_sm_commands(_Acc, From,
get_sm_commands(_Acc, From, #jid{lserver = LServer} = To, ?NS_COMMANDS, Lang) -> #jid{lserver = LServer} = To, ?NS_COMMANDS, Lang) ->
ejabberd_hooks:run_fold(adhoc_sm_items, LServer, {result, []}, [From, To, Lang]); ejabberd_hooks:run_fold(adhoc_sm_items, LServer,
{result, []}, [From, To, Lang]);
get_sm_commands(Acc, _From, _To, _Node, _Lang) -> get_sm_commands(Acc, _From, _To, _Node, _Lang) -> Acc.
Acc.
%------------------------------------------------------------------------- %-------------------------------------------------------------------------
%% On disco info request to the ad-hoc node, return automation/command-list. %% On disco info request to the ad-hoc node, return automation/command-list.
get_local_identity(Acc, _From, _To, ?NS_COMMANDS, Lang) -> get_local_identity(Acc, _From, _To, ?NS_COMMANDS,
[{xmlelement, "identity", Lang) ->
[{"category", "automation"}, [#xmlel{name = <<"identity">>,
{"type", "command-list"}, attrs =
{"name", translate:translate(Lang, "Commands")}], []} | Acc]; [{<<"category">>, <<"automation">>},
{<<"type">>, <<"command-list">>},
get_local_identity(Acc, _From, _To, "ping", Lang) -> {<<"name">>,
[{xmlelement, "identity", translate:translate(Lang, <<"Commands">>)}],
[{"category", "automation"}, children = []}
{"type", "command-node"}, | Acc];
{"name", translate:translate(Lang, "Ping")}], []} | Acc]; get_local_identity(Acc, _From, _To, <<"ping">>, Lang) ->
[#xmlel{name = <<"identity">>,
attrs =
[{<<"category">>, <<"automation">>},
{<<"type">>, <<"command-node">>},
{<<"name">>, translate:translate(Lang, <<"Ping">>)}],
children = []}
| Acc];
get_local_identity(Acc, _From, _To, _Node, _Lang) -> get_local_identity(Acc, _From, _To, _Node, _Lang) ->
Acc. Acc.
@ -155,61 +181,57 @@ get_local_identity(Acc, _From, _To, _Node, _Lang) ->
%% On disco info request to the ad-hoc node, return automation/command-list. %% On disco info request to the ad-hoc node, return automation/command-list.
get_sm_identity(Acc, _From, _To, ?NS_COMMANDS, Lang) -> get_sm_identity(Acc, _From, _To, ?NS_COMMANDS, Lang) ->
[{xmlelement, "identity", [#xmlel{name = <<"identity">>,
[{"category", "automation"}, attrs =
{"type", "command-list"}, [{<<"category">>, <<"automation">>},
{"name", translate:translate(Lang, "Commands")}], []} | Acc]; {<<"type">>, <<"command-list">>},
{<<"name">>,
get_sm_identity(Acc, _From, _To, _Node, _Lang) -> translate:translate(Lang, <<"Commands">>)}],
Acc. children = []}
| Acc];
get_sm_identity(Acc, _From, _To, _Node, _Lang) -> Acc.
%------------------------------------------------------------------------- %-------------------------------------------------------------------------
get_local_features(Acc, _From, _To, "", _Lang) -> get_local_features(Acc, _From, _To, <<"">>, _Lang) ->
Feats = case Acc of Feats = case Acc of
{result, I} -> I; {result, I} -> I;
_ -> [] _ -> []
end, end,
{result, Feats ++ [?NS_COMMANDS]}; {result, Feats ++ [?NS_COMMANDS]};
get_local_features(_Acc, _From, _To, ?NS_COMMANDS,
get_local_features(_Acc, _From, _To, ?NS_COMMANDS, _Lang) -> _Lang) ->
%% override all lesser features...
{result, []}; {result, []};
get_local_features(_Acc, _From, _To, <<"ping">>,
get_local_features(_Acc, _From, _To, "ping", _Lang) -> _Lang) ->
%% override all lesser features...
{result, [?NS_COMMANDS]}; {result, [?NS_COMMANDS]};
get_local_features(Acc, _From, _To, _Node, _Lang) -> get_local_features(Acc, _From, _To, _Node, _Lang) ->
Acc. Acc.
%------------------------------------------------------------------------- %-------------------------------------------------------------------------
get_sm_features(Acc, _From, _To, "", _Lang) -> get_sm_features(Acc, _From, _To, <<"">>, _Lang) ->
Feats = case Acc of Feats = case Acc of
{result, I} -> I; {result, I} -> I;
_ -> [] _ -> []
end, end,
{result, Feats ++ [?NS_COMMANDS]}; {result, Feats ++ [?NS_COMMANDS]};
get_sm_features(_Acc, _From, _To, ?NS_COMMANDS,
get_sm_features(_Acc, _From, _To, ?NS_COMMANDS, _Lang) -> _Lang) ->
%% override all lesser features...
{result, []}; {result, []};
get_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc.
get_sm_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
%------------------------------------------------------------------------- %-------------------------------------------------------------------------
process_local_iq(From, To, IQ) -> process_local_iq(From, To, IQ) ->
process_adhoc_request(From, To, IQ, adhoc_local_commands). process_adhoc_request(From, To, IQ,
adhoc_local_commands).
process_sm_iq(From, To, IQ) -> process_sm_iq(From, To, IQ) ->
process_adhoc_request(From, To, IQ, adhoc_sm_commands). process_adhoc_request(From, To, IQ, adhoc_sm_commands).
process_adhoc_request(From, To,
process_adhoc_request(From, To, #iq{sub_el = SubEl} = IQ, Hook) -> #iq{sub_el = SubEl} = IQ, Hook) ->
?DEBUG("About to parse ~p...", [IQ]), ?DEBUG("About to parse ~p...", [IQ]),
case adhoc:parse_request(IQ) of case adhoc:parse_request(IQ) of
{error, Error} -> {error, Error} ->
@ -217,51 +239,42 @@ process_adhoc_request(From, To, #iq{sub_el = SubEl} = IQ, Hook) ->
#adhoc_request{} = AdhocRequest -> #adhoc_request{} = AdhocRequest ->
Host = To#jid.lserver, Host = To#jid.lserver,
case ejabberd_hooks:run_fold(Hook, Host, empty, case ejabberd_hooks:run_fold(Hook, Host, empty,
[From, To, AdhocRequest]) of [From, To, AdhocRequest])
ignore -> of
ignore; ignore -> ignore;
empty -> empty ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]}; IQ#iq{type = error,
sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
{error, Error} -> {error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]}; IQ#iq{type = error, sub_el = [SubEl, Error]};
Command -> Command -> IQ#iq{type = result, sub_el = [Command]}
IQ#iq{type = result, sub_el = [Command]}
end end
end. end.
ping_item(Acc, _From, #jid{server = Server} = _To,
ping_item(Acc, _From, #jid{server = Server} = _To, Lang) -> Lang) ->
Items = case Acc of Items = case Acc of
{result, I} -> {result, I} -> I;
I; _ -> []
_ ->
[]
end, end,
Nodes = [{xmlelement, "item", Nodes = [#xmlel{name = <<"item">>,
[{"jid", Server}, attrs =
{"node", "ping"}, [{<<"jid">>, Server}, {<<"node">>, <<"ping">>},
{"name", translate:translate(Lang, "Ping")}], {<<"name">>, translate:translate(Lang, <<"Ping">>)}],
[]}], children = []}],
{result, Items ++ Nodes}. {result, Items ++ Nodes}.
ping_command(_Acc, _From, _To, ping_command(_Acc, _From, _To,
#adhoc_request{lang = Lang, #adhoc_request{lang = Lang, node = <<"ping">>,
node = "ping", sessionid = _Sessionid, action = Action} =
sessionid = _Sessionid, Request) ->
action = Action} = Request) -> if Action == <<"">>; Action == <<"execute">> ->
if adhoc:produce_response(Request,
Action == ""; Action == "execute" ->
adhoc:produce_response(
Request,
#adhoc_response{status = completed, #adhoc_response{status = completed,
notes = [{"info", translate:translate( notes =
Lang, [{<<"info">>,
"Pong")}]}); translate:translate(Lang,
true -> <<"Pong">>)}]});
{error, ?ERR_BAD_REQUEST} true -> {error, ?ERR_BAD_REQUEST}
end; end;
ping_command(Acc, _From, _To, _Request) -> Acc.
ping_command(Acc, _From, _To, _Request) ->
Acc.

File diff suppressed because it is too large Load Diff

View File

@ -28,32 +28,34 @@
-behaviour(gen_mod). -behaviour(gen_mod).
-export([start/2, stop/1, -export([start/2, stop/1, process_iq/3,
process_iq/3, process_iq_set/4, process_iq_get/5]).
process_iq_set/4,
process_iq_get/5]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl"). -include("jlib.hrl").
-include("mod_privacy.hrl"). -include("mod_privacy.hrl").
start(Host, Opts) -> start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
ejabberd_hooks:add(privacy_iq_get, Host, one_queue),
?MODULE, process_iq_get, 40), ejabberd_hooks:add(privacy_iq_get, Host, ?MODULE,
ejabberd_hooks:add(privacy_iq_set, Host, process_iq_get, 40),
?MODULE, process_iq_set, 40), ejabberd_hooks:add(privacy_iq_set, Host, ?MODULE,
process_iq_set, 40),
mod_disco:register_feature(Host, ?NS_BLOCKING), mod_disco:register_feature(Host, ?NS_BLOCKING),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_BLOCKING, gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?MODULE, process_iq, IQDisc). ?NS_BLOCKING, ?MODULE, process_iq, IQDisc).
stop(Host) -> stop(Host) ->
ejabberd_hooks:delete(privacy_iq_get, Host, ejabberd_hooks:delete(privacy_iq_get, Host, ?MODULE,
?MODULE, process_iq_get, 40), process_iq_get, 40),
ejabberd_hooks:delete(privacy_iq_set, Host, ejabberd_hooks:delete(privacy_iq_set, Host, ?MODULE,
?MODULE, process_iq_set, 40), process_iq_set, 40),
mod_disco:unregister_feature(Host, ?NS_BLOCKING), mod_disco:unregister_feature(Host, ?NS_BLOCKING),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_BLOCKING). gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_BLOCKING).
process_iq(_From, _To, IQ) -> process_iq(_From, _To, IQ) ->
SubEl = IQ#iq.sub_el, SubEl = IQ#iq.sub_el,
@ -61,90 +63,73 @@ process_iq(_From, _To, IQ) ->
process_iq_get(_, From, _To, process_iq_get(_, From, _To,
#iq{xmlns = ?NS_BLOCKING, #iq{xmlns = ?NS_BLOCKING,
sub_el = {xmlelement, "blocklist", _, _}}, sub_el = #xmlel{name = <<"blocklist">>}},
_) -> _) ->
#jid{luser = LUser, lserver = LServer} = From, #jid{luser = LUser, lserver = LServer} = From,
{stop, process_blocklist_get(LUser, LServer)}; {stop, process_blocklist_get(LUser, LServer)};
process_iq_get(Acc, _, _, _, _) -> Acc.
process_iq_get(Acc, _, _, _, _) -> process_iq_set(_, From, _To,
Acc. #iq{xmlns = ?NS_BLOCKING,
sub_el =
process_iq_set(_, From, _To, #iq{xmlns = ?NS_BLOCKING, #xmlel{name = SubElName, children = SubEls}}) ->
sub_el = {xmlelement, SubElName, _, SubEls}}) ->
#jid{luser = LUser, lserver = LServer} = From, #jid{luser = LUser, lserver = LServer} = From,
Res = Res = case {SubElName, xml:remove_cdata(SubEls)} of
case {SubElName, xml:remove_cdata(SubEls)} of {<<"block">>, []} -> {error, ?ERR_BAD_REQUEST};
{"block", []} -> {<<"block">>, Els} ->
{error, ?ERR_BAD_REQUEST};
{"block", Els} ->
JIDs = parse_blocklist_items(Els, []), JIDs = parse_blocklist_items(Els, []),
process_blocklist_block(LUser, LServer, JIDs); process_blocklist_block(LUser, LServer, JIDs);
{"unblock", []} -> {<<"unblock">>, []} ->
process_blocklist_unblock_all(LUser, LServer); process_blocklist_unblock_all(LUser, LServer);
{"unblock", Els} -> {<<"unblock">>, Els} ->
JIDs = parse_blocklist_items(Els, []), JIDs = parse_blocklist_items(Els, []),
process_blocklist_unblock(LUser, LServer, JIDs); process_blocklist_unblock(LUser, LServer, JIDs);
_ -> _ -> {error, ?ERR_BAD_REQUEST}
{error, ?ERR_BAD_REQUEST}
end, end,
{stop, Res}; {stop, Res};
process_iq_set(Acc, _, _, _) -> Acc.
process_iq_set(Acc, _, _, _) -> list_to_blocklist_jids([], JIDs) -> JIDs;
Acc.
list_to_blocklist_jids([], JIDs) ->
JIDs;
list_to_blocklist_jids([#listitem{type = jid, list_to_blocklist_jids([#listitem{type = jid,
action = deny, action = deny, value = JID} =
value = JID} = Item | Items], JIDs) -> Item
| Items],
JIDs) ->
case Item of case Item of
#listitem{match_all = true} -> #listitem{match_all = true} -> Match = true;
#listitem{match_iq = true, match_message = true,
match_presence_in = true, match_presence_out = true} ->
Match = true; Match = true;
#listitem{match_iq = true, _ -> Match = false
match_message = true,
match_presence_in = true,
match_presence_out = true} ->
Match = true;
_ ->
Match = false
end, end,
if if Match -> list_to_blocklist_jids(Items, [JID | JIDs]);
Match -> true -> list_to_blocklist_jids(Items, JIDs)
list_to_blocklist_jids(Items, [JID | JIDs]);
true ->
list_to_blocklist_jids(Items, JIDs)
end; end;
% Skip Privacy List items than cannot be mapped to Blocking items % Skip Privacy List items than cannot be mapped to Blocking items
list_to_blocklist_jids([_ | Items], JIDs) -> list_to_blocklist_jids([_ | Items], JIDs) ->
list_to_blocklist_jids(Items, JIDs). list_to_blocklist_jids(Items, JIDs).
parse_blocklist_items([], JIDs) -> parse_blocklist_items([], JIDs) -> JIDs;
JIDs; parse_blocklist_items([#xmlel{name = <<"item">>,
attrs = Attrs}
parse_blocklist_items([{xmlelement, "item", Attrs, _} | Els], JIDs) -> | Els],
case xml:get_attr("jid", Attrs) of JIDs) ->
case xml:get_attr(<<"jid">>, Attrs) of
{value, JID1} -> {value, JID1} ->
JID = jlib:jid_tolower(jlib:string_to_jid(JID1)), JID = jlib:jid_tolower(jlib:string_to_jid(JID1)),
parse_blocklist_items(Els, [JID | JIDs]); parse_blocklist_items(Els, [JID | JIDs]);
false -> false -> parse_blocklist_items(Els, JIDs)
% Tolerate missing jid attribute
parse_blocklist_items(Els, JIDs)
end; end;
parse_blocklist_items([_ | Els], JIDs) -> parse_blocklist_items([_ | Els], JIDs) ->
% Tolerate unknown elements
parse_blocklist_items(Els, JIDs). parse_blocklist_items(Els, JIDs).
process_blocklist_block(LUser, LServer, JIDs) -> process_blocklist_block(LUser, LServer, JIDs) ->
Filter = fun (List) -> Filter = fun (List) ->
AlreadyBlocked = list_to_blocklist_jids(List, []), AlreadyBlocked = list_to_blocklist_jids(List, []),
lists:foldr( lists:foldr(fun (JID, List1) ->
fun(JID, List1) -> case lists:member(JID, AlreadyBlocked)
case lists:member(JID, AlreadyBlocked) of of
true -> true -> List1;
List1;
false -> false ->
[#listitem{type = jid, [#listitem{type = jid,
value = JID, value = JID,
@ -153,41 +138,38 @@ process_blocklist_block(LUser, LServer, JIDs) ->
match_all = true} match_all = true}
| List1] | List1]
end end
end, List, JIDs) end,
List, JIDs)
end, end,
case process_blocklist_block(LUser, LServer, Filter, case process_blocklist_block(LUser, LServer, Filter,
gen_mod:db_type(LServer, mod_privacy)) of gen_mod:db_type(LServer, mod_privacy))
of
{atomic, {ok, Default, List}} -> {atomic, {ok, Default, List}} ->
UserList = make_userlist(Default, List), UserList = make_userlist(Default, List),
broadcast_list_update(LUser, LServer, Default, UserList), broadcast_list_update(LUser, LServer, Default,
broadcast_blocklist_event(LUser, LServer, {block, JIDs}), UserList),
broadcast_blocklist_event(LUser, LServer,
{block, JIDs}),
{result, [], UserList}; {result, [], UserList};
_ -> _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
{error, ?ERR_INTERNAL_SERVER_ERROR}
end. end.
process_blocklist_block(LUser, LServer, Filter, mnesia) -> process_blocklist_block(LUser, LServer, Filter,
F = mnesia) ->
fun() -> F = fun () ->
case mnesia:wread({privacy, {LUser, LServer}}) of case mnesia:wread({privacy, {LUser, LServer}}) of
[] -> [] ->
% No lists yet
P = #privacy{us = {LUser, LServer}}, P = #privacy{us = {LUser, LServer}},
% TODO: i18n here: NewDefault = <<"Blocked contacts">>,
NewDefault = "Blocked contacts",
NewLists1 = [], NewLists1 = [],
List = []; List = [];
[#privacy{default = Default, [#privacy{default = Default, lists = Lists} = P] ->
lists = Lists} = P] ->
case lists:keysearch(Default, 1, Lists) of case lists:keysearch(Default, 1, Lists) of
{value, {_, List}} -> {value, {_, List}} ->
% Default list exists
NewDefault = Default, NewDefault = Default,
NewLists1 = lists:keydelete(Default, 1, Lists); NewLists1 = lists:keydelete(Default, 1, Lists);
false -> false ->
% No default list yet, create one NewDefault = <<"Blocked contacts">>,
% TODO: i18n here:
NewDefault = "Blocked contacts",
NewLists1 = Lists, NewLists1 = Lists,
List = [] List = []
end end
@ -201,79 +183,71 @@ process_blocklist_block(LUser, LServer, Filter, mnesia) ->
mnesia:transaction(F); mnesia:transaction(F);
process_blocklist_block(LUser, LServer, Filter, odbc) -> process_blocklist_block(LUser, LServer, Filter, odbc) ->
F = fun () -> F = fun () ->
Default = Default = case
case mod_privacy:sql_get_default_privacy_list_t(LUser) of mod_privacy:sql_get_default_privacy_list_t(LUser)
{selected, ["name"], []} -> of
Name = "Blocked contacts", {selected, [<<"name">>], []} ->
Name = <<"Blocked contacts">>,
mod_privacy:sql_add_privacy_list(LUser, Name), mod_privacy:sql_add_privacy_list(LUser, Name),
mod_privacy:sql_set_default_privacy_list( mod_privacy:sql_set_default_privacy_list(LUser,
LUser, Name), Name),
Name; Name;
{selected, ["name"], [{Name}]} -> {selected, [<<"name">>], [[Name]]} -> Name
Name
end, end,
{selected, ["id"], [{ID}]} = {selected, [<<"id">>], [[ID]]} =
mod_privacy:sql_get_privacy_list_id_t(LUser, Default), mod_privacy:sql_get_privacy_list_id_t(LUser, Default),
case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of case mod_privacy:sql_get_privacy_list_data_by_id_t(ID)
of
{selected, {selected,
["t", "value", "action", "ord", [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
"match_all", "match_iq", "match_message", <<"match_all">>, <<"match_iq">>, <<"match_message">>,
"match_presence_in", <<"match_presence_in">>, <<"match_presence_out">>],
"match_presence_out"],
RItems = [_ | _]} -> RItems = [_ | _]} ->
List = lists:map( List = lists:map(fun mod_privacy:raw_to_item/1, RItems);
fun mod_privacy:raw_to_item/1, _ -> List = []
RItems);
_ ->
List = []
end, end,
NewList = Filter(List), NewList = Filter(List),
NewRItems = lists:map( NewRItems = lists:map(fun mod_privacy:item_to_raw/1,
fun mod_privacy:item_to_raw/1,
NewList), NewList),
mod_privacy:sql_set_privacy_list( mod_privacy:sql_set_privacy_list(ID, NewRItems),
ID, NewRItems),
{ok, Default, NewList} {ok, Default, NewList}
end, end,
ejabberd_odbc:sql_transaction(LServer, F). ejabberd_odbc:sql_transaction(LServer, F).
process_blocklist_unblock_all(LUser, LServer) -> process_blocklist_unblock_all(LUser, LServer) ->
Filter = fun (List) -> Filter = fun (List) ->
lists:filter( lists:filter(fun (#listitem{action = A}) -> A =/= deny
fun(#listitem{action = A}) ->
A =/= deny
end, List)
end, end,
case process_blocklist_unblock_all( List)
LUser, LServer, Filter, gen_mod:db_type(LServer, mod_privacy)) of end,
{atomic, ok} -> case process_blocklist_unblock_all(LUser, LServer,
{result, []}; Filter,
gen_mod:db_type(LServer, mod_privacy))
of
{atomic, ok} -> {result, []};
{atomic, {ok, Default, List}} -> {atomic, {ok, Default, List}} ->
UserList = make_userlist(Default, List), UserList = make_userlist(Default, List),
broadcast_list_update(LUser, LServer, Default, UserList), broadcast_list_update(LUser, LServer, Default,
UserList),
broadcast_blocklist_event(LUser, LServer, unblock_all), broadcast_blocklist_event(LUser, LServer, unblock_all),
{result, [], UserList}; {result, [], UserList};
_ -> _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
{error, ?ERR_INTERNAL_SERVER_ERROR}
end. end.
process_blocklist_unblock_all(LUser, LServer, Filter, mnesia) -> process_blocklist_unblock_all(LUser, LServer, Filter,
F = mnesia) ->
fun() -> F = fun () ->
case mnesia:read({privacy, {LUser, LServer}}) of case mnesia:read({privacy, {LUser, LServer}}) of
[] -> [] ->
% No lists, nothing to unblock % No lists, nothing to unblock
ok; ok;
[#privacy{default = Default, [#privacy{default = Default, lists = Lists} = P] ->
lists = Lists} = P] ->
case lists:keysearch(Default, 1, Lists) of case lists:keysearch(Default, 1, Lists) of
{value, {_, List}} -> {value, {_, List}} ->
% Default list, remove all deny items
NewList = Filter(List), NewList = Filter(List),
NewLists1 = lists:keydelete(Default, 1, Lists), NewLists1 = lists:keydelete(Default, 1, Lists),
NewLists = [{Default, NewList} | NewLists1], NewLists = [{Default, NewList} | NewLists1],
mnesia:write(P#privacy{lists = NewLists}), mnesia:write(P#privacy{lists = NewLists}),
{ok, Default, NewList}; {ok, Default, NewList};
false -> false ->
% No default list, nothing to unblock % No default list, nothing to unblock
@ -282,82 +256,73 @@ process_blocklist_unblock_all(LUser, LServer, Filter, mnesia) ->
end end
end, end,
mnesia:transaction(F); mnesia:transaction(F);
process_blocklist_unblock_all(LUser, LServer, Filter, odbc) -> process_blocklist_unblock_all(LUser, LServer, Filter,
odbc) ->
F = fun () -> F = fun () ->
case mod_privacy:sql_get_default_privacy_list_t(LUser) of case mod_privacy:sql_get_default_privacy_list_t(LUser)
{selected, ["name"], []} -> of
ok; {selected, [<<"name">>], []} -> ok;
{selected, ["name"], [{Default}]} -> {selected, [<<"name">>], [[Default]]} ->
{selected, ["id"], [{ID}]} = {selected, [<<"id">>], [[ID]]} =
mod_privacy:sql_get_privacy_list_id_t( mod_privacy:sql_get_privacy_list_id_t(LUser, Default),
LUser, Default), case mod_privacy:sql_get_privacy_list_data_by_id_t(ID)
case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of of
{selected, {selected,
["t", "value", "action", "ord", [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
"match_all", "match_iq", "match_message", <<"match_all">>, <<"match_iq">>, <<"match_message">>,
"match_presence_in", <<"match_presence_in">>, <<"match_presence_out">>],
"match_presence_out"],
RItems = [_ | _]} -> RItems = [_ | _]} ->
List = lists:map( List = lists:map(fun mod_privacy:raw_to_item/1,
fun mod_privacy:raw_to_item/1,
RItems), RItems),
NewList = Filter(List), NewList = Filter(List),
NewRItems = lists:map( NewRItems = lists:map(fun mod_privacy:item_to_raw/1,
fun mod_privacy:item_to_raw/1,
NewList), NewList),
mod_privacy:sql_set_privacy_list( mod_privacy:sql_set_privacy_list(ID, NewRItems),
ID, NewRItems),
{ok, Default, NewList}; {ok, Default, NewList};
_ -> _ -> ok
ok
end; end;
_ -> _ -> ok
ok
end end
end, end,
ejabberd_odbc:sql_transaction(LServer, F). ejabberd_odbc:sql_transaction(LServer, F).
process_blocklist_unblock(LUser, LServer, JIDs) -> process_blocklist_unblock(LUser, LServer, JIDs) ->
Filter = fun (List) -> Filter = fun (List) ->
lists:filter( lists:filter(fun (#listitem{action = deny, type = jid,
fun(#listitem{action = deny,
type = jid,
value = JID}) -> value = JID}) ->
not(lists:member(JID, JIDs)); not lists:member(JID, JIDs);
(_) -> (_) -> true
true end,
end, List) List)
end, end,
case process_blocklist_unblock(LUser, LServer, Filter, case process_blocklist_unblock(LUser, LServer, Filter,
gen_mod:db_type(LServer, mod_privacy)) of gen_mod:db_type(LServer, mod_privacy))
{atomic, ok} -> of
{result, []}; {atomic, ok} -> {result, []};
{atomic, {ok, Default, List}} -> {atomic, {ok, Default, List}} ->
UserList = make_userlist(Default, List), UserList = make_userlist(Default, List),
broadcast_list_update(LUser, LServer, Default, UserList), broadcast_list_update(LUser, LServer, Default,
broadcast_blocklist_event(LUser, LServer, {unblock, JIDs}), UserList),
broadcast_blocklist_event(LUser, LServer,
{unblock, JIDs}),
{result, [], UserList}; {result, [], UserList};
_ -> _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
{error, ?ERR_INTERNAL_SERVER_ERROR}
end. end.
process_blocklist_unblock(LUser, LServer, Filter, mnesia) -> process_blocklist_unblock(LUser, LServer, Filter,
F = mnesia) ->
fun() -> F = fun () ->
case mnesia:read({privacy, {LUser, LServer}}) of case mnesia:read({privacy, {LUser, LServer}}) of
[] -> [] ->
% No lists, nothing to unblock % No lists, nothing to unblock
ok; ok;
[#privacy{default = Default, [#privacy{default = Default, lists = Lists} = P] ->
lists = Lists} = P] ->
case lists:keysearch(Default, 1, Lists) of case lists:keysearch(Default, 1, Lists) of
{value, {_, List}} -> {value, {_, List}} ->
% Default list, remove matching deny items
NewList = Filter(List), NewList = Filter(List),
NewLists1 = lists:keydelete(Default, 1, Lists), NewLists1 = lists:keydelete(Default, 1, Lists),
NewLists = [{Default, NewList} | NewLists1], NewLists = [{Default, NewList} | NewLists1],
mnesia:write(P#privacy{lists = NewLists}), mnesia:write(P#privacy{lists = NewLists}),
{ok, Default, NewList}; {ok, Default, NewList};
false -> false ->
% No default list, nothing to unblock % No default list, nothing to unblock
@ -366,37 +331,32 @@ process_blocklist_unblock(LUser, LServer, Filter, mnesia) ->
end end
end, end,
mnesia:transaction(F); mnesia:transaction(F);
process_blocklist_unblock(LUser, LServer, Filter, odbc) -> process_blocklist_unblock(LUser, LServer, Filter,
odbc) ->
F = fun () -> F = fun () ->
case mod_privacy:sql_get_default_privacy_list_t(LUser) of case mod_privacy:sql_get_default_privacy_list_t(LUser)
{selected, ["name"], []} -> of
ok; {selected, [<<"name">>], []} -> ok;
{selected, ["name"], [{Default}]} -> {selected, [<<"name">>], [[Default]]} ->
{selected, ["id"], [{ID}]} = {selected, [<<"id">>], [[ID]]} =
mod_privacy:sql_get_privacy_list_id_t( mod_privacy:sql_get_privacy_list_id_t(LUser, Default),
LUser, Default), case mod_privacy:sql_get_privacy_list_data_by_id_t(ID)
case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of of
{selected, {selected,
["t", "value", "action", "ord", [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
"match_all", "match_iq", "match_message", <<"match_all">>, <<"match_iq">>, <<"match_message">>,
"match_presence_in", <<"match_presence_in">>, <<"match_presence_out">>],
"match_presence_out"],
RItems = [_ | _]} -> RItems = [_ | _]} ->
List = lists:map( List = lists:map(fun mod_privacy:raw_to_item/1,
fun mod_privacy:raw_to_item/1,
RItems), RItems),
NewList = Filter(List), NewList = Filter(List),
NewRItems = lists:map( NewRItems = lists:map(fun mod_privacy:item_to_raw/1,
fun mod_privacy:item_to_raw/1,
NewList), NewList),
mod_privacy:sql_set_privacy_list( mod_privacy:sql_set_privacy_list(ID, NewRItems),
ID, NewRItems),
{ok, Default, NewList}; {ok, Default, NewList};
_ -> _ -> ok
ok
end; end;
_ -> _ -> ok
ok
end end
end, end,
ejabberd_odbc:sql_transaction(LServer, F). ejabberd_odbc:sql_transaction(LServer, F).
@ -406,66 +366,65 @@ make_userlist(Name, List) ->
#userlist{name = Name, list = List, needdb = NeedDb}. #userlist{name = Name, list = List, needdb = NeedDb}.
broadcast_list_update(LUser, LServer, Name, UserList) -> broadcast_list_update(LUser, LServer, Name, UserList) ->
ejabberd_router:route( ejabberd_sm:route(jlib:make_jid(LUser, LServer,
jlib:make_jid(LUser, LServer, ""), <<"">>),
jlib:make_jid(LUser, LServer, ""), jlib:make_jid(LUser, LServer, <<"">>),
{xmlelement, "broadcast", [], {broadcast, {privacy_list, UserList, Name}}).
[{privacy_list, UserList, Name}]}).
broadcast_blocklist_event(LUser, LServer, Event) -> broadcast_blocklist_event(LUser, LServer, Event) ->
JID = jlib:make_jid(LUser, LServer, ""), JID = jlib:make_jid(LUser, LServer, <<"">>),
ejabberd_router:route( ejabberd_sm:route(JID, JID,
JID, JID, {broadcast, {blocking, Event}}).
{xmlelement, "broadcast", [],
[{blocking, Event}]}).
process_blocklist_get(LUser, LServer) -> process_blocklist_get(LUser, LServer) ->
case process_blocklist_get( case process_blocklist_get(LUser, LServer,
LUser, LServer, gen_mod:db_type(LServer, mod_privacy)) of gen_mod:db_type(LServer, mod_privacy))
error -> of
{error, ?ERR_INTERNAL_SERVER_ERROR}; error -> {error, ?ERR_INTERNAL_SERVER_ERROR};
List -> List ->
JIDs = list_to_blocklist_jids(List, []), JIDs = list_to_blocklist_jids(List, []),
Items = lists:map( Items = lists:map(fun (JID) ->
fun(JID) ->
?DEBUG("JID: ~p", [JID]), ?DEBUG("JID: ~p", [JID]),
{xmlelement, "item", #xmlel{name = <<"item">>,
[{"jid", jlib:jid_to_string(JID)}], []} attrs =
end, JIDs), [{<<"jid">>,
jlib:jid_to_string(JID)}],
children = []}
end,
JIDs),
{result, {result,
[{xmlelement, "blocklist", [{"xmlns", ?NS_BLOCKING}], [#xmlel{name = <<"blocklist">>,
Items}]} attrs = [{<<"xmlns">>, ?NS_BLOCKING}],
children = Items}]}
end. end.
process_blocklist_get(LUser, LServer, mnesia) -> process_blocklist_get(LUser, LServer, mnesia) ->
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of case catch mnesia:dirty_read(privacy, {LUser, LServer})
{'EXIT', _Reason} -> of
error; {'EXIT', _Reason} -> error;
[] -> [] -> [];
[];
[#privacy{default = Default, lists = Lists}] -> [#privacy{default = Default, lists = Lists}] ->
case lists:keysearch(Default, 1, Lists) of case lists:keysearch(Default, 1, Lists) of
{value, {_, List}} -> {value, {_, List}} -> List;
List; _ -> []
_ ->
[]
end end
end; end;
process_blocklist_get(LUser, LServer, odbc) -> process_blocklist_get(LUser, LServer, odbc) ->
case catch mod_privacy:sql_get_default_privacy_list(LUser, LServer) of case catch
{selected, ["name"], []} -> mod_privacy:sql_get_default_privacy_list(LUser, LServer)
[]; of
{selected, ["name"], [{Default}]} -> {selected, [<<"name">>], []} -> [];
case catch mod_privacy:sql_get_privacy_list_data( {selected, [<<"name">>], [[Default]]} ->
LUser, LServer, Default) of case catch mod_privacy:sql_get_privacy_list_data(LUser,
{selected, ["t", "value", "action", "ord", "match_all", LServer, Default)
"match_iq", "match_message", of
"match_presence_in", "match_presence_out"], {selected,
[<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
<<"match_all">>, <<"match_iq">>, <<"match_message">>,
<<"match_presence_in">>, <<"match_presence_out">>],
RItems} -> RItems} ->
lists:map(fun mod_privacy:raw_to_item/1, RItems); lists:map(fun mod_privacy:raw_to_item/1, RItems);
{'EXIT', _} -> {'EXIT', _} -> error
error
end; end;
{'EXIT', _} -> {'EXIT', _} -> error
error
end. end.

View File

@ -27,30 +27,23 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(mod_caps). -module(mod_caps).
-author('henoch@dtek.chalmers.se'). -author('henoch@dtek.chalmers.se').
-behaviour(gen_server). -behaviour(gen_server).
-behaviour(gen_mod). -behaviour(gen_mod).
-export([read_caps/1, -export([read_caps/1, caps_stream_features/2,
caps_stream_features/2, disco_features/5, disco_identity/5, disco_info/5,
disco_features/5,
disco_identity/5,
disco_info/5,
get_features/1]). get_features/1]).
%% gen_mod callbacks %% gen_mod callbacks
-export([start/2, start_link/2, -export([start/2, start_link/2, stop/1]).
stop/1]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, -export([init/1, handle_info/2, handle_call/3,
handle_info/2, handle_cast/2, terminate/2, code_change/3]).
handle_call/3,
handle_cast/2,
terminate/2,
code_change/3
]).
%% hook handlers %% hook handlers
-export([user_send_packet/3, -export([user_send_packet/3,
@ -59,32 +52,45 @@
c2s_broadcast_recipients/5]). c2s_broadcast_recipients/5]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl"). -include("jlib.hrl").
-define(PROCNAME, ejabberd_mod_caps). -define(PROCNAME, ejabberd_mod_caps).
-define(BAD_HASH_LIFETIME, 600). %% in seconds
-record(caps, {node, version, hash, exts}). -define(BAD_HASH_LIFETIME, 600).
-record(caps_features, {node_pair, features = []}).
-record(state, {host}). -record(caps,
{
node = <<"">> :: binary(),
version = <<"">> :: binary(),
hash = <<"">> :: binary(),
exts = [] :: [binary()]
}).
-type caps() :: #caps{}.
-export_type([caps/0]).
-record(caps_features,
{
node_pair = {<<"">>, <<"">>} :: {binary(), binary()},
features = [] :: [binary()] | pos_integer()
}).
-record(state, {host = <<"">> :: binary()}).
%%==================================================================== %%====================================================================
%% API %% API
%%==================================================================== %%====================================================================
start_link(Host, Opts) -> start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME), Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). gen_server:start_link({local, Proc}, ?MODULE,
[Host, Opts], []).
start(Host, Opts) -> start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME), Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec = ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
{Proc, transient, 1000, worker, [?MODULE]},
{?MODULE, start_link, [Host, Opts]},
transient,
1000,
worker,
[?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec). supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) -> stop(Host) ->
@ -96,164 +102,170 @@ stop(Host) ->
%% get_features returns a list of features implied by the given caps %% get_features returns a list of features implied by the given caps
%% record (as extracted by read_caps) or 'unknown' if features are %% record (as extracted by read_caps) or 'unknown' if features are
%% not completely collected at the moment. %% not completely collected at the moment.
get_features(nothing) -> get_features(nothing) -> [];
[];
get_features(#caps{node = Node, version = Version, exts = Exts}) -> get_features(#caps{node = Node, version = Version, exts = Exts}) ->
SubNodes = [Version | Exts], SubNodes = [Version | Exts],
lists:foldl(
fun(SubNode, Acc) ->
BinaryNode = node_to_binary(Node, SubNode),
case cache_tab:lookup(caps_features, BinaryNode,
caps_read_fun(BinaryNode)) of
{ok, Features} when is_list(Features) ->
binary_to_features(Features) ++ Acc;
_ ->
Acc
end
end, [], SubNodes).
%% read_caps takes a list of XML elements (the child elements of a %% read_caps takes a list of XML elements (the child elements of a
%% <presence/> stanza) and returns an opaque value representing the %% <presence/> stanza) and returns an opaque value representing the
%% Entity Capabilities contained therein, or the atom nothing if no %% Entity Capabilities contained therein, or the atom nothing if no
%% capabilities are advertised. %% capabilities are advertised.
read_caps(Els) -> lists:foldl(fun (SubNode, Acc) ->
read_caps(Els, nothing). NodePair = {Node, SubNode},
case cache_tab:lookup(caps_features, NodePair,
caps_read_fun(NodePair))
of
{ok, Features} when is_list(Features) ->
Features ++ Acc;
_ -> Acc
end
end,
[], SubNodes).
read_caps([{xmlelement, "c", Attrs, _Els} | Tail], Result) -> read_caps(Els) -> read_caps(Els, nothing).
case xml:get_attr_s("xmlns", Attrs) of
read_caps([#xmlel{name = <<"c">>, attrs = Attrs}
| Tail],
Result) ->
case xml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_CAPS -> ?NS_CAPS ->
Node = xml:get_attr_s("node", Attrs), Node = xml:get_attr_s(<<"node">>, Attrs),
Version = xml:get_attr_s("ver", Attrs), Version = xml:get_attr_s(<<"ver">>, Attrs),
Hash = xml:get_attr_s("hash", Attrs), Hash = xml:get_attr_s(<<"hash">>, Attrs),
Exts = string:tokens(xml:get_attr_s("ext", Attrs), " "), Exts = str:tokens(xml:get_attr_s(<<"ext">>, Attrs),
read_caps(Tail, #caps{node = Node, hash = Hash, <<" ">>),
version = Version, exts = Exts}); read_caps(Tail,
_ -> #caps{node = Node, hash = Hash, version = Version,
read_caps(Tail, Result) exts = Exts});
_ -> read_caps(Tail, Result)
end; end;
read_caps([{xmlelement, "x", Attrs, _Els} | Tail], Result) -> read_caps([#xmlel{name = <<"x">>, attrs = Attrs}
case xml:get_attr_s("xmlns", Attrs) of | Tail],
?NS_MUC_USER -> Result) ->
nothing; case xml:get_attr_s(<<"xmlns">>, Attrs) of
_ -> ?NS_MUC_USER -> nothing;
read_caps(Tail, Result) _ -> read_caps(Tail, Result)
end; end;
read_caps([_ | Tail], Result) -> read_caps([_ | Tail], Result) ->
read_caps(Tail, Result); read_caps(Tail, Result);
read_caps([], Result) -> read_caps([], Result) -> Result.
Result.
%%==================================================================== %%====================================================================
%% Hooks %% Hooks
%%==================================================================== %%====================================================================
user_send_packet(#jid{luser = User, lserver = Server} = From, user_send_packet(
#jid{luser = User, lserver = Server, lresource = ""}, #jid{luser = User, lserver = Server} = From,
{xmlelement, "presence", Attrs, Els}) -> #jid{luser = User, lserver = Server, lresource = <<"">>},
Type = xml:get_attr_s("type", Attrs), #xmlel{name = <<"presence">>, attrs = Attrs, children = Els}) ->
if Type == ""; Type == "available" -> Type = xml:get_attr_s(<<"type">>, Attrs),
if Type == <<"">>; Type == <<"available">> ->
case read_caps(Els) of case read_caps(Els) of
nothing -> nothing -> ok;
ok;
#caps{version = Version, exts = Exts} = Caps -> #caps{version = Version, exts = Exts} = Caps ->
feature_request(Server, From, Caps, [Version | Exts]) feature_request(Server, From, Caps, [Version | Exts])
end; end;
true -> true -> ok
ok
end; end;
user_send_packet(_From, _To, _Packet) -> user_send_packet(_From, _To, _Packet) -> ok.
ok.
user_receive_packet(#jid{lserver = Server}, From, _To, user_receive_packet(#jid{lserver = Server}, From, _To,
{xmlelement, "presence", Attrs, Els}) -> #xmlel{name = <<"presence">>, attrs = Attrs,
Type = xml:get_attr_s("type", Attrs), children = Els}) ->
if Type == ""; Type == "available" -> Type = xml:get_attr_s(<<"type">>, Attrs),
IsRemote = not lists:member(From#jid.lserver, ?MYHOSTS),
if IsRemote and
((Type == <<"">>) or (Type == <<"available">>)) ->
case read_caps(Els) of case read_caps(Els) of
nothing -> nothing -> ok;
ok;
#caps{version = Version, exts = Exts} = Caps -> #caps{version = Version, exts = Exts} = Caps ->
feature_request(Server, From, Caps, [Version | Exts]) feature_request(Server, From, Caps, [Version | Exts])
end; end;
true -> true -> ok
ok
end; end;
user_receive_packet(_JID, _From, _To, _Packet) -> user_receive_packet(_JID, _From, _To, _Packet) ->
ok. ok.
caps_stream_features(Acc, MyHost) -> caps_stream_features(Acc, MyHost) ->
case make_my_disco_hash(MyHost) of case make_my_disco_hash(MyHost) of
"" -> <<"">> -> Acc;
Acc;
Hash -> Hash ->
[{xmlelement, "c", [{"xmlns", ?NS_CAPS}, [#xmlel{name = <<"c">>,
{"hash", "sha-1"}, attrs =
{"node", ?EJABBERD_URI}, [{<<"xmlns">>, ?NS_CAPS}, {<<"hash">>, <<"sha-1">>},
{"ver", Hash}], []} | Acc] {<<"node">>, ?EJABBERD_URI}, {<<"ver">>, Hash}],
children = []}
| Acc]
end. end.
disco_features(_Acc, From, To, ?EJABBERD_URI ++ "#" ++ [_|_], Lang) -> disco_features(Acc, From, To, Node, Lang) ->
case is_valid_node(Node) of
true ->
ejabberd_hooks:run_fold(disco_local_features, ejabberd_hooks:run_fold(disco_local_features,
To#jid.lserver, To#jid.lserver, empty,
empty, [From, To, <<"">>, Lang]);
[From, To, "", Lang]); false ->
disco_features(Acc, _From, _To, _Node, _Lang) -> Acc
Acc. end.
disco_identity(_Acc, From, To, ?EJABBERD_URI ++ "#" ++ [_|_], Lang) -> disco_identity(Acc, From, To, Node, Lang) ->
case is_valid_node(Node) of
true ->
ejabberd_hooks:run_fold(disco_local_identity, ejabberd_hooks:run_fold(disco_local_identity,
To#jid.lserver, To#jid.lserver, [],
[], [From, To, <<"">>, Lang]);
[From, To, "", Lang]); false ->
disco_identity(Acc, _From, _To, _Node, _Lang) -> Acc
Acc. end.
disco_info(_Acc, Host, Module, ?EJABBERD_URI ++ "#" ++ [_|_], Lang) -> disco_info(Acc, Host, Module, Node, Lang) ->
ejabberd_hooks:run_fold(disco_info, case is_valid_node(Node) of
Host, true ->
[], ejabberd_hooks:run_fold(disco_info, Host, [],
[Host, Module, "", Lang]); [Host, Module, <<"">>, Lang]);
disco_info(Acc, _Host, _Module, _Node, _Lang) -> false ->
Acc. Acc
end.
c2s_presence_in(C2SState, {From, To, {_, _, Attrs, Els}}) -> c2s_presence_in(C2SState,
Type = xml:get_attr_s("type", Attrs), {From, To, {_, _, Attrs, Els}}) ->
Subscription = ejabberd_c2s:get_subscription(From, C2SState), Type = xml:get_attr_s(<<"type">>, Attrs),
Insert = ((Type == "") or (Type == "available")) Subscription = ejabberd_c2s:get_subscription(From,
C2SState),
Insert = ((Type == <<"">>) or (Type == <<"available">>))
and ((Subscription == both) or (Subscription == to)), and ((Subscription == both) or (Subscription == to)),
Delete = (Type == "unavailable") or (Type == "error") or (Type == "invisible"), Delete = (Type == <<"unavailable">>) or
(Type == <<"error">>),
if Insert or Delete -> if Insert or Delete ->
LFrom = jlib:jid_tolower(From), LFrom = jlib:jid_tolower(From),
Rs = case ejabberd_c2s:get_aux_field(caps_resources, C2SState) of Rs = case ejabberd_c2s:get_aux_field(caps_resources,
{ok, Rs1} -> C2SState)
Rs1; of
error -> {ok, Rs1} -> Rs1;
gb_trees:empty() error -> gb_trees:empty()
end, end,
Caps = read_caps(Els), Caps = read_caps(Els),
{CapsUpdated, NewRs} = {CapsUpdated, NewRs} = case Caps of
case Caps of nothing when Insert == true -> {false, Rs};
nothing when Insert == true ->
{false, Rs};
_ when Insert == true -> _ when Insert == true ->
case gb_trees:lookup(LFrom, Rs) of case gb_trees:lookup(LFrom, Rs) of
{value, Caps} -> {value, Caps} -> {false, Rs};
{false, Rs};
none -> none ->
{true, gb_trees:insert(LFrom, Caps, Rs)}; {true,
gb_trees:insert(LFrom, Caps,
Rs)};
_ -> _ ->
{true, gb_trees:update(LFrom, Caps, Rs)} {true,
gb_trees:update(LFrom, Caps, Rs)}
end; end;
_ -> _ -> {false, gb_trees:delete_any(LFrom, Rs)}
{false, gb_trees:delete_any(LFrom, Rs)}
end, end,
if CapsUpdated -> if CapsUpdated ->
ejabberd_hooks:run(caps_update, To#jid.lserver, ejabberd_hooks:run(caps_update, To#jid.lserver,
[From, To, get_features(Caps)]); [From, To, get_features(Caps)]);
true -> true -> ok
ok
end, end,
ejabberd_c2s:set_aux_field(caps_resources, NewRs, C2SState); ejabberd_c2s:set_aux_field(caps_resources, NewRs,
true -> C2SState);
C2SState true -> C2SState
end. end.
c2s_broadcast_recipients(InAcc, C2SState, {pep_message, Feature}, c2s_broadcast_recipients(InAcc, C2SState, {pep_message, Feature},
@ -292,8 +304,8 @@ init([Host, Opts]) ->
{local_content, true}, {local_content, true},
{attributes, record_info(fields, caps_features)}]), {attributes, record_info(fields, caps_features)}]),
mnesia:add_table_copy(caps_features, node(), disc_only_copies), mnesia:add_table_copy(caps_features, node(), disc_only_copies),
MaxSize = gen_mod:get_opt(cache_size, Opts, 1000), MaxSize = gen_mod:get_opt(cache_size, Opts, fun(CS) when is_integer(CS) -> CS end, 1000),
LifeTime = gen_mod:get_opt(cache_life_time, Opts, timer:hours(24) div 1000), LifeTime = gen_mod:get_opt(cache_life_time, Opts, fun(CL) when is_integer(CL) -> CL end, timer:hours(24) div 1000),
cache_tab:new(caps_features, [{max_size, MaxSize}, {life_time, LifeTime}]), cache_tab:new(caps_features, [{max_size, MaxSize}, {life_time, LifeTime}]),
ejabberd_hooks:add(c2s_presence_in, Host, ejabberd_hooks:add(c2s_presence_in, Host,
?MODULE, c2s_presence_in, 75), ?MODULE, c2s_presence_in, 75),
@ -320,20 +332,18 @@ handle_call(stop, _From, State) ->
handle_call(_Req, _From, State) -> handle_call(_Req, _From, State) ->
{reply, {error, badarg}, State}. {reply, {error, badarg}, State}.
handle_cast(_Msg, State) -> handle_cast(_Msg, State) -> {noreply, State}.
{noreply, State}.
handle_info(_Info, State) -> handle_info(_Info, State) -> {noreply, State}.
{noreply, State}.
terminate(_Reason, State) -> terminate(_Reason, State) ->
Host = State#state.host, Host = State#state.host,
ejabberd_hooks:delete(c2s_presence_in, Host, ejabberd_hooks:delete(c2s_presence_in, Host, ?MODULE,
?MODULE, c2s_presence_in, 75), c2s_presence_in, 75),
ejabberd_hooks:delete(c2s_broadcast_recipients, Host, ejabberd_hooks:delete(c2s_broadcast_recipients, Host,
?MODULE, c2s_broadcast_recipients, 75), ?MODULE, c2s_broadcast_recipients, 75),
ejabberd_hooks:delete(user_send_packet, Host, ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
?MODULE, user_send_packet, 75), user_send_packet, 75),
ejabberd_hooks:delete(user_receive_packet, Host, ejabberd_hooks:delete(user_receive_packet, Host,
?MODULE, user_receive_packet, 75), ?MODULE, user_receive_packet, 75),
ejabberd_hooks:delete(c2s_stream_features, Host, ejabberd_hooks:delete(c2s_stream_features, Host,
@ -344,267 +354,251 @@ terminate(_Reason, State) ->
?MODULE, disco_features, 75), ?MODULE, disco_features, 75),
ejabberd_hooks:delete(disco_local_identity, Host, ejabberd_hooks:delete(disco_local_identity, Host,
?MODULE, disco_identity, 75), ?MODULE, disco_identity, 75),
ejabberd_hooks:delete(disco_info, Host, ejabberd_hooks:delete(disco_info, Host, ?MODULE,
?MODULE, disco_info, 75), disco_info, 75),
ok. ok.
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) -> {ok, State}.
{ok, State}.
%%==================================================================== %%====================================================================
%% Aux functions %% Aux functions
%%==================================================================== %%====================================================================
feature_request(Host, From, Caps, [SubNode | Tail] = SubNodes) -> feature_request(Host, From, Caps,
[SubNode | Tail] = SubNodes) ->
Node = Caps#caps.node, Node = Caps#caps.node,
BinaryNode = node_to_binary(Node, SubNode), NodePair = {Node, SubNode},
case cache_tab:lookup(caps_features, BinaryNode, case cache_tab:lookup(caps_features, NodePair,
caps_read_fun(BinaryNode)) of caps_read_fun(NodePair))
of
{ok, Fs} when is_list(Fs) -> {ok, Fs} when is_list(Fs) ->
feature_request(Host, From, Caps, Tail); feature_request(Host, From, Caps, Tail);
Other -> Other ->
NeedRequest = case Other of NeedRequest = case Other of
{ok, TS} -> {ok, TS} -> now_ts() >= TS + (?BAD_HASH_LIFETIME);
now_ts() >= TS + ?BAD_HASH_LIFETIME; _ -> true
_ ->
true
end, end,
if NeedRequest -> if NeedRequest ->
IQ = #iq{type = get, IQ = #iq{type = get, xmlns = ?NS_DISCO_INFO,
xmlns = ?NS_DISCO_INFO, sub_el =
sub_el = [{xmlelement, "query", [#xmlel{name = <<"query">>,
[{"xmlns", ?NS_DISCO_INFO}, attrs =
{"node", Node ++ "#" ++ SubNode}], [{<<"xmlns">>, ?NS_DISCO_INFO},
[]}]}, {<<"node">>,
%% We cache current timestamp in order to avoid <<Node/binary, "#",
%% caps requests flood SubNode/binary>>}],
cache_tab:insert(caps_features, BinaryNode, now_ts(), children = []}]},
caps_write_fun(BinaryNode, now_ts())), cache_tab:insert(caps_features, NodePair, now_ts(),
caps_write_fun(NodePair, now_ts())),
F = fun (IQReply) -> F = fun (IQReply) ->
feature_response( feature_response(IQReply, Host, From, Caps,
IQReply, Host, From, Caps, SubNodes) SubNodes)
end, end,
ejabberd_local:route_iq( ejabberd_local:route_iq(jlib:make_jid(<<"">>, Host,
jlib:make_jid("", Host, ""), From, IQ, F); <<"">>),
true -> From, IQ, F);
feature_request(Host, From, Caps, Tail) true -> feature_request(Host, From, Caps, Tail)
end end
end; end;
feature_request(_Host, _From, _Caps, []) -> feature_request(_Host, _From, _Caps, []) -> ok.
ok.
feature_response(#iq{type = result, feature_response(#iq{type = result,
sub_el = [{xmlelement, _, _, Els}]}, sub_el = [#xmlel{children = Els}]},
Host, From, Caps, [SubNode | SubNodes]) -> Host, From, Caps, [SubNode | SubNodes]) ->
BinaryNode = node_to_binary(Caps#caps.node, SubNode), NodePair = {Caps#caps.node, SubNode},
case check_hash(Caps, Els) of case check_hash(Caps, Els) of
true -> true ->
Features = lists:flatmap( Features = lists:flatmap(fun (#xmlel{name =
fun({xmlelement, "feature", FAttrs, _}) -> <<"feature">>,
[xml:get_attr_s("var", FAttrs)]; attrs = FAttrs}) ->
(_) -> [xml:get_attr_s(<<"var">>, FAttrs)];
[] (_) -> []
end, Els), end,
BinaryFeatures = features_to_binary(Features), Els),
cache_tab:insert( cache_tab:insert(caps_features, NodePair,
caps_features, BinaryNode, BinaryFeatures, Features,
caps_write_fun(BinaryNode, BinaryFeatures)); caps_write_fun(NodePair, Features));
false -> false -> ok
ok
end, end,
feature_request(Host, From, Caps, SubNodes); feature_request(Host, From, Caps, SubNodes);
feature_response(_IQResult, Host, From, Caps, [_SubNode | SubNodes]) -> feature_response(_IQResult, Host, From, Caps,
%% We got type=error or invalid type=result stanza or timeout. [_SubNode | SubNodes]) ->
feature_request(Host, From, Caps, SubNodes). feature_request(Host, From, Caps, SubNodes).
node_to_binary(Node, SubNode) ->
{list_to_binary(Node), list_to_binary(SubNode)}.
features_to_binary(L) -> [list_to_binary(I) || I <- L].
binary_to_features(L) -> [binary_to_list(I) || I <- L].
caps_read_fun(Node) -> caps_read_fun(Node) ->
fun () -> fun () ->
case mnesia:dirty_read({caps_features, Node}) of case mnesia:dirty_read({caps_features, Node}) of
[#caps_features{features = Features}] -> [#caps_features{features = Features}] -> {ok, Features};
{ok, Features}; _ -> error
_ ->
error
end end
end. end.
caps_write_fun(Node, Features) -> caps_write_fun(Node, Features) ->
fun () -> fun () ->
mnesia:dirty_write( mnesia:dirty_write(#caps_features{node_pair = Node,
#caps_features{node_pair = Node,
features = Features}) features = Features})
end. end.
make_my_disco_hash(Host) -> make_my_disco_hash(Host) ->
JID = jlib:make_jid("", Host, ""), JID = jlib:make_jid(<<"">>, Host, <<"">>),
case {ejabberd_hooks:run_fold(disco_local_features, case {ejabberd_hooks:run_fold(disco_local_features,
Host, Host, empty, [JID, JID, <<"">>, <<"">>]),
empty, ejabberd_hooks:run_fold(disco_local_identity, Host, [],
[JID, JID, "", ""]), [JID, JID, <<"">>, <<"">>]),
ejabberd_hooks:run_fold(disco_local_identity, ejabberd_hooks:run_fold(disco_info, Host, [],
Host, [Host, undefined, <<"">>, <<"">>])}
[], of
[JID, JID, "", ""]),
ejabberd_hooks:run_fold(disco_info,
Host,
[],
[Host, undefined, "", ""])} of
{{result, Features}, Identities, Info} -> {{result, Features}, Identities, Info} ->
Feats = lists:map( Feats = lists:map(fun ({{Feat, _Host}}) ->
fun({{Feat, _Host}}) -> #xmlel{name = <<"feature">>,
{xmlelement, "feature", [{"var", Feat}], []}; attrs = [{<<"var">>, Feat}],
children = []};
(Feat) -> (Feat) ->
{xmlelement, "feature", [{"var", Feat}], []} #xmlel{name = <<"feature">>,
end, Features), attrs = [{<<"var">>, Feat}],
children = []}
end,
Features),
make_disco_hash(Identities ++ Info ++ Feats, sha1); make_disco_hash(Identities ++ Info ++ Feats, sha1);
_Err -> _Err -> <<"">>
""
end. end.
-ifdef(HAVE_MD2). -ifdef(HAVE_MD2).
make_disco_hash(DiscoEls, Algo) -> make_disco_hash(DiscoEls, Algo) ->
Concat = [concat_identities(DiscoEls), Concat = list_to_binary([concat_identities(DiscoEls),
concat_features(DiscoEls), concat_features(DiscoEls), concat_info(DiscoEls)]),
concat_info(DiscoEls)], jlib:encode_base64(case Algo of
base64:encode_to_string( md2 -> sha:md2(Concat);
if Algo == md2 -> md5 -> crypto:md5(Concat);
sha:md2(Concat); sha1 -> crypto:sha(Concat);
Algo == md5 -> sha224 -> sha:sha224(Concat);
crypto:md5(Concat); sha256 -> sha:sha256(Concat);
Algo == sha1 -> sha384 -> sha:sha384(Concat);
crypto:sha(Concat); sha512 -> sha:sha512(Concat)
Algo == sha224 ->
sha:sha224(Concat);
Algo == sha256 ->
sha:sha256(Concat);
Algo == sha384 ->
sha:sha384(Concat);
Algo == sha512 ->
sha:sha512(Concat)
end). end).
check_hash(Caps, Els) -> check_hash(Caps, Els) ->
case Caps#caps.hash of case Caps#caps.hash of
"md2" -> <<"md2">> ->
Caps#caps.version == make_disco_hash(Els, md2); Caps#caps.version == make_disco_hash(Els, md2);
"md5" -> <<"md5">> ->
Caps#caps.version == make_disco_hash(Els, md5); Caps#caps.version == make_disco_hash(Els, md5);
"sha-1" -> <<"sha-1">> ->
Caps#caps.version == make_disco_hash(Els, sha1); Caps#caps.version == make_disco_hash(Els, sha1);
"sha-224" -> <<"sha-224">> ->
Caps#caps.version == make_disco_hash(Els, sha224); Caps#caps.version == make_disco_hash(Els, sha224);
"sha-256" -> <<"sha-256">> ->
Caps#caps.version == make_disco_hash(Els, sha256); Caps#caps.version == make_disco_hash(Els, sha256);
"sha-384" -> <<"sha-384">> ->
Caps#caps.version == make_disco_hash(Els, sha384); Caps#caps.version == make_disco_hash(Els, sha384);
"sha-512" -> <<"sha-512">> ->
Caps#caps.version == make_disco_hash(Els, sha512); Caps#caps.version == make_disco_hash(Els, sha512);
_ -> _ -> true
true
end. end.
-else. -else.
make_disco_hash(DiscoEls, Algo) -> make_disco_hash(DiscoEls, Algo) ->
Concat = [concat_identities(DiscoEls), Concat = list_to_binary([concat_identities(DiscoEls),
concat_features(DiscoEls), concat_features(DiscoEls), concat_info(DiscoEls)]),
concat_info(DiscoEls)], jlib:encode_base64(case Algo of
base64:encode_to_string( md5 -> crypto:md5(Concat);
if Algo == md5 -> sha1 -> crypto:sha(Concat);
crypto:md5(Concat); sha224 -> sha:sha224(Concat);
Algo == sha1 -> sha256 -> sha:sha256(Concat);
crypto:sha(Concat); sha384 -> sha:sha384(Concat);
Algo == sha224 -> sha512 -> sha:sha512(Concat)
sha:sha224(Concat);
Algo == sha256 ->
sha:sha256(Concat);
Algo == sha384 ->
sha:sha384(Concat);
Algo == sha512 ->
sha:sha512(Concat)
end). end).
check_hash(Caps, Els) -> check_hash(Caps, Els) ->
case Caps#caps.hash of case Caps#caps.hash of
"md5" -> <<"md5">> ->
Caps#caps.version == make_disco_hash(Els, md5); Caps#caps.version == make_disco_hash(Els, md5);
"sha-1" -> <<"sha-1">> ->
Caps#caps.version == make_disco_hash(Els, sha1); Caps#caps.version == make_disco_hash(Els, sha1);
"sha-224" -> <<"sha-224">> ->
Caps#caps.version == make_disco_hash(Els, sha224); Caps#caps.version == make_disco_hash(Els, sha224);
"sha-256" -> <<"sha-256">> ->
Caps#caps.version == make_disco_hash(Els, sha256); Caps#caps.version == make_disco_hash(Els, sha256);
"sha-384" -> <<"sha-384">> ->
Caps#caps.version == make_disco_hash(Els, sha384); Caps#caps.version == make_disco_hash(Els, sha384);
"sha-512" -> <<"sha-512">> ->
Caps#caps.version == make_disco_hash(Els, sha512); Caps#caps.version == make_disco_hash(Els, sha512);
_ -> _ -> true
true
end. end.
-endif. -endif.
concat_features(Els) -> concat_features(Els) ->
lists:usort( lists:usort(lists:flatmap(fun (#xmlel{name =
lists:flatmap( <<"feature">>,
fun({xmlelement, "feature", Attrs, _}) -> attrs = Attrs}) ->
[[xml:get_attr_s("var", Attrs), $<]]; [[xml:get_attr_s(<<"var">>, Attrs), $<]];
(_) -> (_) -> []
[] end,
end, Els)). Els)).
concat_identities(Els) -> concat_identities(Els) ->
lists:sort( lists:sort(lists:flatmap(fun (#xmlel{name =
lists:flatmap( <<"identity">>,
fun({xmlelement, "identity", Attrs, _}) -> attrs = Attrs}) ->
[[xml:get_attr_s("category", Attrs), $/, [[xml:get_attr_s(<<"category">>, Attrs),
xml:get_attr_s("type", Attrs), $/, $/, xml:get_attr_s(<<"type">>, Attrs),
xml:get_attr_s("xml:lang", Attrs), $/, $/,
xml:get_attr_s("name", Attrs), $<]]; xml:get_attr_s(<<"xml:lang">>, Attrs),
(_) -> $/, xml:get_attr_s(<<"name">>, Attrs),
[] $<]];
end, Els)). (_) -> []
end,
Els)).
concat_info(Els) -> concat_info(Els) ->
lists:sort( lists:sort(lists:flatmap(fun (#xmlel{name = <<"x">>,
lists:flatmap( attrs = Attrs, children = Fields}) ->
fun({xmlelement, "x", Attrs, Fields}) -> case {xml:get_attr_s(<<"xmlns">>, Attrs),
case {xml:get_attr_s("xmlns", Attrs), xml:get_attr_s(<<"type">>, Attrs)}
xml:get_attr_s("type", Attrs)} of of
{?NS_XDATA, "result"} -> {?NS_XDATA, <<"result">>} ->
[concat_xdata_fields(Fields)]; [concat_xdata_fields(Fields)];
_ -> _ -> []
[]
end; end;
(_) -> (_) -> []
[] end,
end, Els)). Els)).
concat_xdata_fields(Fields) -> concat_xdata_fields(Fields) ->
[Form, Res] = [Form, Res] = lists:foldl(fun (#xmlel{name =
lists:foldl( <<"field">>,
fun({xmlelement, "field", Attrs, Els} = El, attrs = Attrs, children = Els} =
El,
[FormType, VarFields] = Acc) -> [FormType, VarFields] = Acc) ->
case xml:get_attr_s("var", Attrs) of case xml:get_attr_s(<<"var">>, Attrs) of
"" -> <<"">> -> Acc;
Acc; <<"FORM_TYPE">> ->
"FORM_TYPE" -> [xml:get_subtag_cdata(El,
[xml:get_subtag_cdata(El, "value"), VarFields]; <<"value">>),
VarFields];
Var -> Var ->
[FormType, [FormType,
[[[Var, $<], [[[Var, $<],
lists:sort( lists:sort(lists:flatmap(fun
lists:flatmap( (#xmlel{name
fun({xmlelement, "value", _, VEls}) -> =
[[xml:get_cdata(VEls), $<]]; <<"value">>,
children
=
VEls}) ->
[[xml:get_cdata(VEls),
$<]];
(_) -> (_) ->
[] []
end, Els))] | VarFields]] end,
Els))]
| VarFields]]
end; end;
(_, Acc) -> (_, Acc) -> Acc
Acc end,
end, ["", []], Fields), [<<"">>, []], Fields),
[Form, $<, lists:sort(Res)]. [Form, $<, lists:sort(Res)].
gb_trees_fold(F, Acc, Tree) -> gb_trees_fold(F, Acc, Tree) ->
@ -616,10 +610,16 @@ gb_trees_fold_iter(F, Acc, Iter) ->
{Key, Val, NewIter} -> {Key, Val, NewIter} ->
NewAcc = F(Key, Val, Acc), NewAcc = F(Key, Val, Acc),
gb_trees_fold_iter(F, NewAcc, NewIter); gb_trees_fold_iter(F, NewAcc, NewIter);
_ -> _ -> Acc
Acc
end. end.
now_ts() -> now_ts() ->
{MegaSecs, Secs, _} = now(), {MegaSecs, Secs, _} = now(), MegaSecs * 1000000 + Secs.
MegaSecs*1000000 + Secs.
is_valid_node(Node) ->
case str:tokens(Node, <<"#">>) of
[?EJABBERD_URI|_] ->
true;
_ ->
false
end.

File diff suppressed because it is too large Load Diff

View File

@ -25,30 +25,35 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(mod_configure2). -module(mod_configure2).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(gen_mod). -behaviour(gen_mod).
-export([start/2, -export([start/2, stop/1, process_local_iq/3]).
stop/1,
process_local_iq/3]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl"). -include("jlib.hrl").
-define(NS_ECONFIGURE, "http://ejabberd.jabberstudio.org/protocol/configure"). -define(NS_ECONFIGURE,
<<"http://ejabberd.jabberstudio.org/protocol/con"
"figure">>).
start(Host, Opts) -> start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_ECONFIGURE, one_queue),
?MODULE, process_local_iq, IQDisc), gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_ECONFIGURE, ?MODULE, process_local_iq,
IQDisc),
ok. ok.
stop(Host) -> stop(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_ECONFIGURE). gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
?NS_ECONFIGURE).
process_local_iq(From, To,
process_local_iq(From, To, #iq{type = Type, lang = _Lang, sub_el = SubEl} = IQ) -> #iq{type = Type, lang = _Lang, sub_el = SubEl} = IQ) ->
case acl:match_rule(To#jid.lserver, configure, From) of case acl:match_rule(To#jid.lserver, configure, From) of
deny -> deny ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
@ -91,80 +96,102 @@ process_local_iq(From, To, #iq{type = Type, lang = _Lang, sub_el = SubEl} = IQ)
%%end; %%end;
get -> get ->
case process_get(SubEl) of case process_get(SubEl) of
{result, Res} -> {result, Res} -> IQ#iq{type = result, sub_el = [Res]};
IQ#iq{type = result, sub_el = [Res]};
{error, Error} -> {error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]} IQ#iq{type = error, sub_el = [SubEl, Error]}
end end
end end
end. end.
process_get(#xmlel{name = <<"info">>}) ->
process_get({xmlelement, "info", _Attrs, _SubEls}) ->
S2SConns = ejabberd_s2s:dirty_get_connections(), S2SConns = ejabberd_s2s:dirty_get_connections(),
TConns = lists:usort([element(2, C) || C <- S2SConns]), TConns = lists:usort([element(2, C) || C <- S2SConns]),
Attrs = [{"registered-users", Attrs = [{<<"registered-users">>,
integer_to_list(mnesia:table_info(passwd, size))}, iolist_to_binary(integer_to_list(mnesia:table_info(passwd,
{"online-users", size)))},
integer_to_list(mnesia:table_info(presence, size))}, {<<"online-users">>,
{"running-nodes", iolist_to_binary(integer_to_list(mnesia:table_info(presence,
integer_to_list(length(mnesia:system_info(running_db_nodes)))}, size)))},
{"stopped-nodes", {<<"running-nodes">>,
integer_to_list( iolist_to_binary(integer_to_list(length(mnesia:system_info(running_db_nodes))))},
length(lists:usort(mnesia:system_info(db_nodes) ++ {<<"stopped-nodes">>,
mnesia:system_info(extra_db_nodes)) -- iolist_to_binary(integer_to_list(length(lists:usort(mnesia:system_info(db_nodes)
mnesia:system_info(running_db_nodes)))}, ++
{"outgoing-s2s-servers", integer_to_list(length(TConns))}], mnesia:system_info(extra_db_nodes))
{result, {xmlelement, "info", --
[{"xmlns", ?NS_ECONFIGURE} | Attrs], []}}; mnesia:system_info(running_db_nodes))))},
process_get({xmlelement, "welcome-message", Attrs, _SubEls}) -> {<<"outgoing-s2s-servers">>,
{Subj, Body} = case ejabberd_config:get_local_option(welcome_message) of iolist_to_binary(integer_to_list(length(TConns)))}],
{_Subj, _Body} = SB -> SB; {result,
_ -> {"", ""} #xmlel{name = <<"info">>,
attrs = [{<<"xmlns">>, ?NS_ECONFIGURE} | Attrs],
children = []}};
process_get(#xmlel{name = <<"welcome-message">>,
attrs = Attrs}) ->
{Subj, Body} = ejabberd_config:get_local_option(
welcome_message,
fun({Subj, Body}) ->
{iolist_to_binary(Subj),
iolist_to_binary(Body)}
end, end,
{result, {xmlelement, "welcome-message", Attrs, {<<"">>, <<"">>}),
[{xmlelement, "subject", [], [{xmlcdata, Subj}]}, {result,
{xmlelement, "body", [], [{xmlcdata, Body}]}]}}; #xmlel{name = <<"welcome-message">>, attrs = Attrs,
process_get({xmlelement, "registration-watchers", Attrs, _SubEls}) -> children =
SubEls = [#xmlel{name = <<"subject">>, attrs = [],
case ejabberd_config:get_local_option(registration_watchers) of children = [{xmlcdata, Subj}]},
JIDs when is_list(JIDs) -> #xmlel{name = <<"body">>, attrs = [],
lists:map(fun(JID) -> children = [{xmlcdata, Body}]}]}};
{xmlelement, "jid", [], [{xmlcdata, JID}]} process_get(#xmlel{name = <<"registration-watchers">>,
end, JIDs); attrs = Attrs}) ->
_ -> SubEls = ejabberd_config:get_local_option(
[] registration_watchers,
end, fun(JIDs) when is_list(JIDs) ->
{result, {xmlelement, "registration_watchers", Attrs, SubEls}}; lists:map(
process_get({xmlelement, "acls", Attrs, _SubEls}) -> fun(J) ->
Str = lists:flatten(io_lib:format("~p.", [ets:tab2list(acl)])), #xmlel{name = <<"jid">>, attrs = [],
{result, {xmlelement, "acls", Attrs, [{xmlcdata, Str}]}}; children = [{xmlcdata,
process_get({xmlelement, "access", Attrs, _SubEls}) -> iolist_to_binary(J)}]}
Str = end, JIDs)
lists:flatten( end, []),
io_lib:format( {result,
"~p.", #xmlel{name = <<"registration_watchers">>,
attrs = Attrs, children = SubEls}};
process_get(#xmlel{name = <<"acls">>, attrs = Attrs}) ->
Str = iolist_to_binary(io_lib:format("~p.",
[ets:tab2list(acl)])),
{result,
#xmlel{name = <<"acls">>, attrs = Attrs,
children = [{xmlcdata, Str}]}};
process_get(#xmlel{name = <<"access">>,
attrs = Attrs}) ->
Str = iolist_to_binary(io_lib:format("~p.",
[ets:select(config, [ets:select(config,
[{{config, {access, '$1'}, '$2'}, [{{config, {access, '$1'},
'$2'},
[], [],
[{{access, '$1', '$2'}}]}]) [{{access, '$1',
])), '$2'}}]}])])),
{result, {xmlelement, "access", Attrs, [{xmlcdata, Str}]}}; {result,
process_get({xmlelement, "last", Attrs, _SubEls}) -> #xmlel{name = <<"access">>, attrs = Attrs,
case catch mnesia:dirty_select( children = [{xmlcdata, Str}]}};
last_activity, [{{last_activity, '_', '$1', '_'}, [], ['$1']}]) of process_get(#xmlel{name = <<"last">>, attrs = Attrs}) ->
case catch mnesia:dirty_select(last_activity,
[{{last_activity, '_', '$1', '_'}, [],
['$1']}])
of
{'EXIT', _Reason} -> {'EXIT', _Reason} ->
{error, ?ERR_INTERNAL_SERVER_ERROR}; {error, ?ERR_INTERNAL_SERVER_ERROR};
Vals -> Vals ->
{MegaSecs, Secs, _MicroSecs} = now(), {MegaSecs, Secs, _MicroSecs} = now(),
TimeStamp = MegaSecs * 1000000 + Secs, TimeStamp = MegaSecs * 1000000 + Secs,
Str = lists:flatten( Str = list_to_binary(
lists:append( [[jlib:integer_to_binary(TimeStamp - V),
[[integer_to_list(TimeStamp - V), " "] || V <- Vals])), <<" ">>] || V <- Vals]),
{result, {xmlelement, "last", Attrs, [{xmlcdata, Str}]}} {result,
#xmlel{name = <<"last">>, attrs = Attrs,
children = [{xmlcdata, Str}]}}
end; end;
%%process_get({xmlelement, Name, Attrs, SubEls}) -> %%process_get({xmlelement, Name, Attrs, SubEls}) ->
%% {result, }; %% {result, };
process_get(_) -> process_get(_) -> {error, ?ERR_BAD_REQUEST}.
{error, ?ERR_BAD_REQUEST}.

View File

@ -25,247 +25,275 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(mod_disco). -module(mod_disco).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(gen_mod). -behaviour(gen_mod).
-export([start/2, -export([start/2, stop/1, process_local_iq_items/3,
stop/1, process_local_iq_info/3, get_local_identity/5,
process_local_iq_items/3, get_local_features/5, get_local_services/5,
process_local_iq_info/3, process_sm_iq_items/3, process_sm_iq_info/3,
get_local_identity/5, get_sm_identity/5, get_sm_features/5, get_sm_items/5,
get_local_features/5, get_info/5, register_feature/2, unregister_feature/2,
get_local_services/5, register_extra_domain/2, unregister_extra_domain/2]).
process_sm_iq_items/3,
process_sm_iq_info/3,
get_sm_identity/5,
get_sm_features/5,
get_sm_items/5,
get_info/5,
register_feature/2,
unregister_feature/2,
register_extra_domain/2,
unregister_extra_domain/2]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl"). -include("jlib.hrl").
-include("mod_roster.hrl"). -include("mod_roster.hrl").
start(Host, Opts) -> start(Host, Opts) ->
ejabberd_local:refresh_iq_handlers(), ejabberd_local:refresh_iq_handlers(),
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), one_queue),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS, gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?MODULE, process_local_iq_items, IQDisc), ?NS_DISCO_ITEMS, ?MODULE,
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO, process_local_iq_items, IQDisc),
?MODULE, process_local_iq_info, IQDisc), gen_iq_handler:add_iq_handler(ejabberd_local, Host,
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS, ?NS_DISCO_INFO, ?MODULE,
?MODULE, process_sm_iq_items, IQDisc), process_local_iq_info, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO, gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?MODULE, process_sm_iq_info, IQDisc), ?NS_DISCO_ITEMS, ?MODULE, process_sm_iq_items,
IQDisc),
catch ets:new(disco_features, [named_table, ordered_set, public]), gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
register_feature(Host, "iq"), ?NS_DISCO_INFO, ?MODULE, process_sm_iq_info,
register_feature(Host, "presence"), IQDisc),
register_feature(Host, "presence-invisible"), catch ets:new(disco_features,
[named_table, ordered_set, public]),
catch ets:new(disco_extra_domains, [named_table, ordered_set, public]), register_feature(Host, <<"iq">>),
ExtraDomains = gen_mod:get_opt(extra_domains, Opts, []), register_feature(Host, <<"presence">>),
lists:foreach(fun(Domain) -> register_extra_domain(Host, Domain) end, catch ets:new(disco_extra_domains,
[named_table, ordered_set, public]),
ExtraDomains = gen_mod:get_opt(extra_domains, Opts,
fun(Hs) ->
[iolist_to_binary(H) || H <- Hs]
end, []),
lists:foreach(fun (Domain) ->
register_extra_domain(Host, Domain)
end,
ExtraDomains), ExtraDomains),
catch ets:new(disco_sm_features, [named_table, ordered_set, public]), catch ets:new(disco_sm_features,
catch ets:new(disco_sm_nodes, [named_table, ordered_set, public]), [named_table, ordered_set, public]),
ejabberd_hooks:add(disco_local_items, Host, ?MODULE, get_local_services, 100), catch ets:new(disco_sm_nodes,
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, get_local_features, 100), [named_table, ordered_set, public]),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, get_local_identity, 100), ejabberd_hooks:add(disco_local_items, Host, ?MODULE,
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, get_sm_items, 100), get_local_services, 100),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 100), ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, get_sm_identity, 100), get_local_features, 100),
ejabberd_hooks:add(disco_info, Host, ?MODULE, get_info, 100), ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
get_local_identity, 100),
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE,
get_sm_items, 100),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
get_sm_features, 100),
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE,
get_sm_identity, 100),
ejabberd_hooks:add(disco_info, Host, ?MODULE, get_info,
100),
ok. ok.
stop(Host) -> stop(Host) ->
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, get_sm_identity, 100), ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE,
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 100), get_sm_identity, 100),
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, get_sm_items, 100), ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, get_local_identity, 100), get_sm_features, 100),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_local_features, 100), ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE,
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, get_local_services, 100), get_sm_items, 100),
ejabberd_hooks:delete(disco_info, Host, ?MODULE, get_info, 100), ejabberd_hooks:delete(disco_local_identity, Host,
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS), ?MODULE, get_local_identity, 100),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO), ejabberd_hooks:delete(disco_local_features, Host,
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS), ?MODULE, get_local_features, 100),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO), ejabberd_hooks:delete(disco_local_items, Host, ?MODULE,
get_local_services, 100),
ejabberd_hooks:delete(disco_info, Host, ?MODULE,
get_info, 100),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
?NS_DISCO_INFO),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_DISCO_INFO),
catch ets:match_delete(disco_features, {{'_', Host}}), catch ets:match_delete(disco_features, {{'_', Host}}),
catch ets:match_delete(disco_extra_domains, {{'_', Host}}), catch ets:match_delete(disco_extra_domains,
{{'_', Host}}),
ok. ok.
register_feature(Host, Feature) -> register_feature(Host, Feature) ->
catch ets:new(disco_features, [named_table, ordered_set, public]), catch ets:new(disco_features,
[named_table, ordered_set, public]),
ets:insert(disco_features, {{Feature, Host}}). ets:insert(disco_features, {{Feature, Host}}).
unregister_feature(Host, Feature) -> unregister_feature(Host, Feature) ->
catch ets:new(disco_features, [named_table, ordered_set, public]), catch ets:new(disco_features,
[named_table, ordered_set, public]),
ets:delete(disco_features, {Feature, Host}). ets:delete(disco_features, {Feature, Host}).
register_extra_domain(Host, Domain) -> register_extra_domain(Host, Domain) ->
catch ets:new(disco_extra_domains, [named_table, ordered_set, public]), catch ets:new(disco_extra_domains,
[named_table, ordered_set, public]),
ets:insert(disco_extra_domains, {{Domain, Host}}). ets:insert(disco_extra_domains, {{Domain, Host}}).
unregister_extra_domain(Host, Domain) -> unregister_extra_domain(Host, Domain) ->
catch ets:new(disco_extra_domains, [named_table, ordered_set, public]), catch ets:new(disco_extra_domains,
[named_table, ordered_set, public]),
ets:delete(disco_extra_domains, {Domain, Host}). ets:delete(disco_extra_domains, {Domain, Host}).
process_local_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> process_local_iq_items(From, To,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of case Type of
set -> set ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
get -> get ->
Node = xml:get_tag_attr_s("node", SubEl), Node = xml:get_tag_attr_s(<<"node">>, SubEl),
Host = To#jid.lserver, Host = To#jid.lserver,
case ejabberd_hooks:run_fold(disco_local_items, Host,
case ejabberd_hooks:run_fold(disco_local_items, empty, [From, To, Node, Lang])
Host, of
empty,
[From, To, Node, Lang]) of
{result, Items} -> {result, Items} ->
ANode = case Node of ANode = case Node of
"" -> []; <<"">> -> [];
_ -> [{"node", Node}] _ -> [{<<"node">>, Node}]
end, end,
IQ#iq{type = result, IQ#iq{type = result,
sub_el = [{xmlelement, "query", sub_el =
[{"xmlns", ?NS_DISCO_ITEMS} | ANode], [#xmlel{name = <<"query">>,
Items attrs =
}]}; [{<<"xmlns">>, ?NS_DISCO_ITEMS} | ANode],
children = Items}]};
{error, Error} -> {error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]} IQ#iq{type = error, sub_el = [SubEl, Error]}
end end
end. end.
process_local_iq_info(From, To,
process_local_iq_info(From, To, #iq{type = Type, lang = Lang, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
sub_el = SubEl} = IQ) ->
case Type of case Type of
set -> set ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
get -> get ->
Host = To#jid.lserver, Host = To#jid.lserver,
Node = xml:get_tag_attr_s("node", SubEl), Node = xml:get_tag_attr_s(<<"node">>, SubEl),
Identity = ejabberd_hooks:run_fold(disco_local_identity, Identity = ejabberd_hooks:run_fold(disco_local_identity,
Host, Host, [], [From, To, Node, Lang]),
[],
[From, To, Node, Lang]),
Info = ejabberd_hooks:run_fold(disco_info, Host, [], Info = ejabberd_hooks:run_fold(disco_info, Host, [],
[Host, ?MODULE, Node, Lang]), [Host, ?MODULE, Node, Lang]),
case ejabberd_hooks:run_fold(disco_local_features, case ejabberd_hooks:run_fold(disco_local_features, Host,
Host, empty, [From, To, Node, Lang])
empty, of
[From, To, Node, Lang]) of
{result, Features} -> {result, Features} ->
ANode = case Node of ANode = case Node of
"" -> []; <<"">> -> [];
_ -> [{"node", Node}] _ -> [{<<"node">>, Node}]
end, end,
IQ#iq{type = result, IQ#iq{type = result,
sub_el = [{xmlelement, "query", sub_el =
[{"xmlns", ?NS_DISCO_INFO} | ANode], [#xmlel{name = <<"query">>,
attrs =
[{<<"xmlns">>, ?NS_DISCO_INFO} | ANode],
children =
Identity ++ Identity ++
Info ++ Info ++ features_to_xml(Features)}]};
features_to_xml(Features)
}]};
{error, Error} -> {error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]} IQ#iq{type = error, sub_el = [SubEl, Error]}
end end
end. end.
get_local_identity(Acc, _From, _To, [], _Lang) -> get_local_identity(Acc, _From, _To, <<>>, _Lang) ->
Acc ++ [{xmlelement, "identity", Acc ++
[{"category", "server"}, [#xmlel{name = <<"identity">>,
{"type", "im"}, attrs =
{"name", "ejabberd"}], []}]; [{<<"category">>, <<"server">>}, {<<"type">>, <<"im">>},
{<<"name">>, <<"ejabberd">>}],
children = []}];
get_local_identity(Acc, _From, _To, _Node, _Lang) -> get_local_identity(Acc, _From, _To, _Node, _Lang) ->
Acc. Acc.
get_local_features({error, _Error} = Acc, _From, _To, _Node, _Lang) -> get_local_features({error, _Error} = Acc, _From, _To,
_Node, _Lang) ->
Acc; Acc;
get_local_features(Acc, _From, To, <<>>, _Lang) ->
get_local_features(Acc, _From, To, [], _Lang) ->
Feats = case Acc of Feats = case Acc of
{result, Features} -> Features; {result, Features} -> Features;
empty -> [] empty -> []
end, end,
Host = To#jid.lserver, Host = To#jid.lserver,
{result, {result,
ets:select(disco_features, [{{{'_', Host}}, [], ['$_']}]) ++ Feats}; ets:select(disco_features,
[{{{'_', Host}}, [], ['$_']}])
++ Feats};
get_local_features(Acc, _From, _To, _Node, _Lang) -> get_local_features(Acc, _From, _To, _Node, _Lang) ->
case Acc of case Acc of
{result, _Features} -> {result, _Features} -> Acc;
Acc; empty -> {error, ?ERR_ITEM_NOT_FOUND}
empty ->
{error, ?ERR_ITEM_NOT_FOUND}
end. end.
features_to_xml(FeatureList) -> features_to_xml(FeatureList) ->
%% Avoid duplicating features [#xmlel{name = <<"feature">>,
[{xmlelement, "feature", [{"var", Feat}], []} || attrs = [{<<"var">>, Feat}], children = []}
Feat <- lists:usort( || Feat
lists:map( <- lists:usort(lists:map(fun ({{Feature, _Host}}) ->
fun({{Feature, _Host}}) ->
Feature; Feature;
(Feature) when is_list(Feature) -> (Feature) when is_binary(Feature) ->
Feature Feature
end, FeatureList))]. end,
FeatureList))].
domain_to_xml({Domain}) -> domain_to_xml({Domain}) ->
{xmlelement, "item", [{"jid", Domain}], []}; #xmlel{name = <<"item">>, attrs = [{<<"jid">>, Domain}],
children = []};
domain_to_xml(Domain) -> domain_to_xml(Domain) ->
{xmlelement, "item", [{"jid", Domain}], []}. #xmlel{name = <<"item">>, attrs = [{<<"jid">>, Domain}],
children = []}.
get_local_services({error, _Error} = Acc, _From, _To, _Node, _Lang) -> get_local_services({error, _Error} = Acc, _From, _To,
_Node, _Lang) ->
Acc; Acc;
get_local_services(Acc, _From, To, <<>>, _Lang) ->
get_local_services(Acc, _From, To, [], _Lang) ->
Items = case Acc of Items = case Acc of
{result, Its} -> Its; {result, Its} -> Its;
empty -> [] empty -> []
end, end,
Host = To#jid.lserver, Host = To#jid.lserver,
{result, {result,
lists:usort( lists:usort(lists:map(fun domain_to_xml/1,
lists:map(fun domain_to_xml/1,
get_vh_services(Host) ++ get_vh_services(Host) ++
ets:select(disco_extra_domains, ets:select(disco_extra_domains,
[{{{'$1', Host}}, [], ['$1']}])) [{{{'$1', Host}}, [], ['$1']}])))
) ++ Items}; ++ Items};
get_local_services({result, _} = Acc, _From, _To, _Node,
get_local_services({result, _} = Acc, _From, _To, _Node, _Lang) -> _Lang) ->
Acc; Acc;
get_local_services(empty, _From, _To, _Node, _Lang) -> get_local_services(empty, _From, _To, _Node, _Lang) ->
{error, ?ERR_ITEM_NOT_FOUND}. {error, ?ERR_ITEM_NOT_FOUND}.
get_vh_services(Host) -> get_vh_services(Host) ->
Hosts = lists:sort(fun(H1, H2) -> length(H1) >= length(H2) end, ?MYHOSTS), Hosts = lists:sort(fun (H1, H2) ->
byte_size(H1) >= byte_size(H2)
end,
?MYHOSTS),
lists:filter(fun (H) -> lists:filter(fun (H) ->
case lists:dropwhile( case lists:dropwhile(fun (VH) ->
fun(VH) -> not
not lists:suffix("." ++ VH, H) str:suffix(
end, Hosts) of <<".", VH/binary>>,
[] -> H)
false; end,
[VH | _] -> Hosts)
VH == Host of
[] -> false;
[VH | _] -> VH == Host
end end
end, ejabberd_router:dirty_get_all_routes()). end,
ejabberd_router:dirty_get_all_routes()).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
process_sm_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> process_sm_iq_items(From, To,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of case Type of
set -> set ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
@ -273,73 +301,72 @@ process_sm_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ
case is_presence_subscribed(From, To) of case is_presence_subscribed(From, To) of
true -> true ->
Host = To#jid.lserver, Host = To#jid.lserver,
Node = xml:get_tag_attr_s("node", SubEl), Node = xml:get_tag_attr_s(<<"node">>, SubEl),
case ejabberd_hooks:run_fold(disco_sm_items, case ejabberd_hooks:run_fold(disco_sm_items, Host,
Host, empty, [From, To, Node, Lang])
empty, of
[From, To, Node, Lang]) of
{result, Items} -> {result, Items} ->
ANode = case Node of ANode = case Node of
"" -> []; <<"">> -> [];
_ -> [{"node", Node}] _ -> [{<<"node">>, Node}]
end, end,
IQ#iq{type = result, IQ#iq{type = result,
sub_el = [{xmlelement, "query", sub_el =
[{"xmlns", ?NS_DISCO_ITEMS} | ANode], [#xmlel{name = <<"query">>,
Items attrs =
}]}; [{<<"xmlns">>, ?NS_DISCO_ITEMS}
| ANode],
children = Items}]};
{error, Error} -> {error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]} IQ#iq{type = error, sub_el = [SubEl, Error]}
end; end;
false -> false ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]} IQ#iq{type = error,
sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}
end end
end. end.
get_sm_items({error, _Error} = Acc, _From, _To, _Node, _Lang) -> get_sm_items({error, _Error} = Acc, _From, _To, _Node,
_Lang) ->
Acc; Acc;
get_sm_items(Acc, From, get_sm_items(Acc, From,
#jid{user = User, server = Server} = To, #jid{user = User, server = Server} = To, <<>>, _Lang) ->
[], _Lang) ->
Items = case Acc of Items = case Acc of
{result, Its} -> Its; {result, Its} -> Its;
empty -> [] empty -> []
end, end,
Items1 = case is_presence_subscribed(From, To) of Items1 = case is_presence_subscribed(From, To) of
true -> true -> get_user_resources(User, Server);
get_user_resources(User, Server); _ -> []
_ ->
[]
end, end,
{result, Items ++ Items1}; {result, Items ++ Items1};
get_sm_items({result, _} = Acc, _From, _To, _Node,
get_sm_items({result, _} = Acc, _From, _To, _Node, _Lang) -> _Lang) ->
Acc; Acc;
get_sm_items(empty, From, To, _Node, _Lang) -> get_sm_items(empty, From, To, _Node, _Lang) ->
#jid{luser = LFrom, lserver = LSFrom} = From, #jid{luser = LFrom, lserver = LSFrom} = From,
#jid{luser = LTo, lserver = LSTo} = To, #jid{luser = LTo, lserver = LSTo} = To,
case {LFrom, LSFrom} of case {LFrom, LSFrom} of
{LTo, LSTo} -> {LTo, LSTo} -> {error, ?ERR_ITEM_NOT_FOUND};
{error, ?ERR_ITEM_NOT_FOUND}; _ -> {error, ?ERR_NOT_ALLOWED}
_ ->
{error, ?ERR_NOT_ALLOWED}
end. end.
is_presence_subscribed(#jid{luser=User, lserver=Server}, #jid{luser=LUser, lserver=LServer}) -> is_presence_subscribed(#jid{luser = User,
lists:any(fun(#roster{jid = {TUser, TServer, _}, subscription = S}) -> lserver = Server},
if #jid{luser = LUser, lserver = LServer}) ->
LUser == TUser, LServer == TServer, S/=none -> lists:any(fun (#roster{jid = {TUser, TServer, _},
subscription = S}) ->
if LUser == TUser, LServer == TServer, S /= none ->
true; true;
true -> true -> false
false
end end
end, end,
ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}])) ejabberd_hooks:run_fold(roster_get, Server, [],
[{User, Server}]))
orelse User == LUser andalso Server == LServer. orelse User == LUser andalso Server == LServer.
process_sm_iq_info(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> process_sm_iq_info(From, To,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of case Type of
set -> set ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
@ -347,105 +374,107 @@ process_sm_iq_info(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ)
case is_presence_subscribed(From, To) of case is_presence_subscribed(From, To) of
true -> true ->
Host = To#jid.lserver, Host = To#jid.lserver,
Node = xml:get_tag_attr_s("node", SubEl), Node = xml:get_tag_attr_s(<<"node">>, SubEl),
Identity = ejabberd_hooks:run_fold(disco_sm_identity, Identity = ejabberd_hooks:run_fold(disco_sm_identity,
Host, Host, [],
[],
[From, To, Node, Lang]), [From, To, Node, Lang]),
case ejabberd_hooks:run_fold(disco_sm_features, case ejabberd_hooks:run_fold(disco_sm_features, Host,
Host, empty, [From, To, Node, Lang])
empty, of
[From, To, Node, Lang]) of
{result, Features} -> {result, Features} ->
ANode = case Node of ANode = case Node of
"" -> []; <<"">> -> [];
_ -> [{"node", Node}] _ -> [{<<"node">>, Node}]
end, end,
IQ#iq{type = result, IQ#iq{type = result,
sub_el = [{xmlelement, "query", sub_el =
[{"xmlns", ?NS_DISCO_INFO} | ANode], [#xmlel{name = <<"query">>,
attrs =
[{<<"xmlns">>, ?NS_DISCO_INFO}
| ANode],
children =
Identity ++ Identity ++
features_to_xml(Features) features_to_xml(Features)}]};
}]};
{error, Error} -> {error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]} IQ#iq{type = error, sub_el = [SubEl, Error]}
end; end;
false -> false ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]} IQ#iq{type = error,
sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}
end end
end. end.
get_sm_identity(Acc, _From, #jid{luser = LUser, lserver=LServer}, _Node, _Lang) -> get_sm_identity(Acc, _From,
Acc ++ case ejabberd_auth:is_user_exists(LUser, LServer) of #jid{luser = LUser, lserver = LServer}, _Node, _Lang) ->
Acc ++
case ejabberd_auth:is_user_exists(LUser, LServer) of
true -> true ->
[{xmlelement, "identity", [{"category", "account"}, [#xmlel{name = <<"identity">>,
{"type", "registered"}], []}]; attrs =
_ -> [{<<"category">>, <<"account">>},
[] {<<"type">>, <<"registered">>}],
children = []}];
_ -> []
end. end.
get_sm_features(empty, From, To, _Node, _Lang) -> get_sm_features(empty, From, To, _Node, _Lang) ->
#jid{luser = LFrom, lserver = LSFrom} = From, #jid{luser = LFrom, lserver = LSFrom} = From,
#jid{luser = LTo, lserver = LSTo} = To, #jid{luser = LTo, lserver = LSTo} = To,
case {LFrom, LSFrom} of case {LFrom, LSFrom} of
{LTo, LSTo} -> {LTo, LSTo} -> {error, ?ERR_ITEM_NOT_FOUND};
{error, ?ERR_ITEM_NOT_FOUND}; _ -> {error, ?ERR_NOT_ALLOWED}
_ ->
{error, ?ERR_NOT_ALLOWED}
end; end;
get_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc.
get_sm_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
get_user_resources(User, Server) -> get_user_resources(User, Server) ->
Rs = ejabberd_sm:get_user_resources(User, Server), Rs = ejabberd_sm:get_user_resources(User, Server),
lists:map(fun (R) -> lists:map(fun (R) ->
{xmlelement, "item", #xmlel{name = <<"item">>,
[{"jid", User ++ "@" ++ Server ++ "/" ++ R}, attrs =
{"name", User}], []} [{<<"jid">>,
end, lists:sort(Rs)). <<User/binary, "@", Server/binary, "/",
R/binary>>},
{<<"name">>, User}],
children = []}
end,
lists:sort(Rs)).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Support for: XEP-0157 Contact Addresses for XMPP Services %%% Support for: XEP-0157 Contact Addresses for XMPP Services
get_info(_A, Host, Mod, Node, _Lang) when Node == [] -> get_info(_A, Host, Mod, Node, _Lang) when Node == <<>> ->
Module = case Mod of Module = case Mod of
undefined -> undefined -> ?MODULE;
?MODULE; _ -> Mod
_ ->
Mod
end, end,
Serverinfo_fields = get_fields_xml(Host, Module), Serverinfo_fields = get_fields_xml(Host, Module),
[{xmlelement, "x", [#xmlel{name = <<"x">>,
[{"xmlns", ?NS_XDATA}, {"type", "result"}], attrs =
[{xmlelement, "field", [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}],
[{"var", "FORM_TYPE"}, {"type", "hidden"}], children =
[{xmlelement, "value", [#xmlel{name = <<"field">>,
[], attrs =
[{xmlcdata, ?NS_SERVERINFO}] [{<<"var">>, <<"FORM_TYPE">>},
}] {<<"type">>, <<"hidden">>}],
}] children =
++ Serverinfo_fields [#xmlel{name = <<"value">>, attrs = [],
}]; children = [{xmlcdata, ?NS_SERVERINFO}]}]}]
++ Serverinfo_fields}];
get_info(Acc, _, _, _Node, _) -> get_info(Acc, _, _, _Node, _) -> Acc.
Acc.
get_fields_xml(Host, Module) -> get_fields_xml(Host, Module) ->
Fields = gen_mod:get_module_opt(Host, ?MODULE, server_info, []), Fields = gen_mod:get_module_opt(Host, ?MODULE, server_info,
fun(L) when is_list(L) -> L end,
%% filter, and get only the ones allowed for this module []),
Fields_good = lists:filter( Fields_good = lists:filter(fun ({Modules, _, _}) ->
fun({Modules, _, _}) ->
case Modules of case Modules of
all -> true; all -> true;
Modules -> lists:member(Module, Modules) Modules ->
lists:member(Module, Modules)
end end
end, end,
Fields), Fields),
fields_to_xml(Fields_good). fields_to_xml(Fields_good).
fields_to_xml(Fields) -> fields_to_xml(Fields) ->
@ -453,18 +482,12 @@ fields_to_xml(Fields) ->
field_to_xml({_, Var, Values}) -> field_to_xml({_, Var, Values}) ->
Values_xml = values_to_xml(Values), Values_xml = values_to_xml(Values),
{xmlelement, "field", #xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}],
[{"var", Var}], children = Values_xml}.
Values_xml
}.
values_to_xml(Values) -> values_to_xml(Values) ->
lists:map( lists:map(fun (Value) ->
fun(Value) -> #xmlel{name = <<"value">>, attrs = [],
{xmlelement, "value", children = [{xmlcdata, Value}]}
[],
[{xmlcdata, Value}]
}
end, end,
Values Values).
).

View File

@ -25,22 +25,26 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(mod_echo). -module(mod_echo).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(gen_server). -behaviour(gen_server).
-behaviour(gen_mod). -behaviour(gen_mod).
%% API %% API
-export([start_link/2, start/2, stop/1, do_client_version/3]). -export([start_link/2, start/2, stop/1,
do_client_version/3]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2,
terminate/2, code_change/3]). handle_info/2, terminate/2, code_change/3]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl"). -include("jlib.hrl").
-record(state, {host}). -record(state, {host = <<"">> :: binary()}).
-define(PROCNAME, ejabberd_mod_echo). -define(PROCNAME, ejabberd_mod_echo).
@ -53,17 +57,13 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
start_link(Host, Opts) -> start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME), Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). gen_server:start_link({local, Proc}, ?MODULE,
[Host, Opts], []).
start(Host, Opts) -> start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME), Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec = ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
{Proc, temporary, 1000, worker, [?MODULE]},
{?MODULE, start_link, [Host, Opts]},
temporary,
1000,
worker,
[?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec). supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) -> stop(Host) ->
@ -72,7 +72,6 @@ stop(Host) ->
supervisor:terminate_child(ejabberd_sup, Proc), supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc). supervisor:delete_child(ejabberd_sup, Proc).
%%==================================================================== %%====================================================================
%% gen_server callbacks %% gen_server callbacks
%%==================================================================== %%====================================================================
@ -85,7 +84,8 @@ stop(Host) ->
%% Description: Initiates the server %% Description: Initiates the server
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([Host, Opts]) -> init([Host, Opts]) ->
MyHost = gen_mod:get_opt_host(Host, Opts, "echo.@HOST@"), MyHost = gen_mod:get_opt_host(Host, Opts,
<<"echo.@HOST@">>),
ejabberd_router:register_route(MyHost), ejabberd_router:register_route(MyHost),
{ok, #state{host = MyHost}}. {ok, #state{host = MyHost}}.
@ -107,8 +107,7 @@ handle_call(stop, _From, State) ->
%% {stop, Reason, State} %% {stop, Reason, State}
%% Description: Handling cast messages %% Description: Handling cast messages
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_cast(_Msg, State) -> handle_cast(_Msg, State) -> {noreply, State}.
{noreply, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} | %% Function: handle_info(Info, State) -> {noreply, State} |
@ -118,14 +117,14 @@ handle_cast(_Msg, State) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_info({route, From, To, Packet}, State) -> handle_info({route, From, To, Packet}, State) ->
Packet2 = case From#jid.user of Packet2 = case From#jid.user of
"" -> jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST); <<"">> ->
jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST);
_ -> Packet _ -> Packet
end, end,
do_client_version(disabled, To, From), % Put 'enabled' to enable it do_client_version(disabled, To, From),
ejabberd_router:route(To, From, Packet2), ejabberd_router:route(To, From, Packet2),
{noreply, State}; {noreply, State};
handle_info(_Info, State) -> handle_info(_Info, State) -> {noreply, State}.
{noreply, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void() %% Function: terminate(Reason, State) -> void()
@ -135,15 +134,13 @@ handle_info(_Info, State) ->
%% The return value is ignored. %% The return value is ignored.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
terminate(_Reason, State) -> terminate(_Reason, State) ->
ejabberd_router:unregister_route(State#state.host), ejabberd_router:unregister_route(State#state.host), ok.
ok.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed %% Description: Convert process state when code is changed
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) -> {ok, State}.
{ok, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Example of routing XMPP packets using Erlang's message passing %% Example of routing XMPP packets using Erlang's message passing
@ -168,36 +165,34 @@ code_change(_OldVsn, State, _Extra) ->
%% using exactly the same JID. We add a (mostly) random resource to %% using exactly the same JID. We add a (mostly) random resource to
%% try to guarantee that the received response matches the request sent. %% try to guarantee that the received response matches the request sent.
%% Finally, the received response is printed in the ejabberd log file. %% Finally, the received response is printed in the ejabberd log file.
do_client_version(disabled, _From, _To) -> do_client_version(disabled, _From, _To) -> ok;
ok;
do_client_version(enabled, From, To) -> do_client_version(enabled, From, To) ->
ToS = jlib:jid_to_string(To), ToS = jlib:jid_to_string(To),
%% It is important to identify this process and packet Random_resource =
Random_resource = integer_to_list(random:uniform(100000)), iolist_to_binary(integer_to_list(random:uniform(100000))),
From2 = From#jid{resource = Random_resource, From2 = From#jid{resource = Random_resource,
lresource = Random_resource}, lresource = Random_resource},
Packet = #xmlel{name = <<"iq">>,
%% Build an iq:query request attrs = [{<<"to">>, ToS}, {<<"type">>, <<"get">>}],
Packet = {xmlelement, "iq", children =
[{"to", ToS}, {"type", "get"}], [#xmlel{name = <<"query">>,
[{xmlelement, "query", [{"xmlns", ?NS_VERSION}], []}]}, attrs = [{<<"xmlns">>, ?NS_VERSION}],
children = []}]},
%% Send the request
ejabberd_router:route(From2, To, Packet), ejabberd_router:route(From2, To, Packet),
Els = receive
%% Wait to receive the response {route, To, From2, IQ} ->
%% It is very important to only accept a packet which is the #xmlel{name = <<"query">>, children = List} =
%% response to the request that he sent xml:get_subtag(IQ, <<"query">>),
Els = receive {route, To, From2, IQ} ->
{xmlelement, "query", _, List} = xml:get_subtag(IQ, "query"),
List List
after 5000 -> % Timeout in miliseconds: 5 seconds after 5000 -> % Timeout in miliseconds: 5 seconds
[] []
end, end,
Values = [{Name, Value} || {xmlelement,Name,[],[{xmlcdata,Value}]} <- Els], Values = [{Name, Value}
|| #xmlel{name = Name, attrs = [],
%% Print in log children = [{xmlcdata, Value}]}
Values_string1 = [io_lib:format("~n~s: ~p", [N, V]) || {N, V} <- Values], <- Els],
Values_string2 = lists:concat(Values_string1), Values_string1 = [io_lib:format("~n~s: ~p", [N, V])
?INFO_MSG("Information of the client: ~s~s", [ToS, Values_string2]). || {N, V} <- Values],
Values_string2 = iolist_to_binary(Values_string1),
?INFO_MSG("Information of the client: ~s~s",
[ToS, Values_string2]).

View File

@ -27,65 +27,61 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(mod_ip_blacklist). -module(mod_ip_blacklist).
-author('mremond@process-one.net'). -author('mremond@process-one.net').
-behaviour(gen_mod). -behaviour(gen_mod).
%% API: %% API:
-export([start/2, -export([start/2, preinit/2, init/1, stop/1]).
preinit/2,
init/1,
stop/1]).
-export([update_bl_c2s/0]). -export([update_bl_c2s/0]).
%% Hooks: %% Hooks:
-export([is_ip_in_c2s_blacklist/2]). -export([is_ip_in_c2s_blacklist/2]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-define(PROCNAME, ?MODULE). -define(PROCNAME, ?MODULE).
-define(BLC2S, "http://xaai.process-one.net/bl_c2s.txt").
-define(UPDATE_INTERVAL, 6). %% in hours -define(BLC2S,
<<"http://xaai.process-one.net/bl_c2s.txt">>).
-define(UPDATE_INTERVAL, 6).
-record(state, {timer}). -record(state, {timer}).
-record(bl_c2s, {ip}).
%% Start once for all vhost %% Start once for all vhost
-record(bl_c2s, {ip = <<"">> :: binary()}).
start(_Host, _Opts) -> start(_Host, _Opts) ->
Pid = spawn(?MODULE, preinit, [self(), #state{}]), Pid = spawn(?MODULE, preinit, [self(), #state{}]),
receive {ok, Pid, PreinitResult} -> receive {ok, Pid, PreinitResult} -> PreinitResult end.
PreinitResult
end.
preinit(Parent, State) -> preinit(Parent, State) ->
Pid = self(), Pid = self(),
try register(?PROCNAME, Pid) of try register(?PROCNAME, Pid) of
true -> true -> Parent ! {ok, Pid, true}, init(State)
Parent ! {ok, Pid, true}, catch
init(State) error:_ -> Parent ! {ok, Pid, true}
catch error:_ ->
Parent ! {ok, Pid, true}
end. end.
%% TODO: %% TODO:
stop(_Host) -> stop(_Host) -> ok.
ok.
init(State) -> init(State) ->
inets:start(), inets:start(),
ets:new(bl_c2s, [named_table, public, {keypos, #bl_c2s.ip}]), ets:new(bl_c2s,
[named_table, public, {keypos, #bl_c2s.ip}]),
update_bl_c2s(), update_bl_c2s(),
%% Register hooks for blacklist ejabberd_hooks:add(check_bl_c2s, ?MODULE,
ejabberd_hooks:add(check_bl_c2s, ?MODULE, is_ip_in_c2s_blacklist, 50), is_ip_in_c2s_blacklist, 50),
%% Set timer: Download the blacklist file every 6 hours timer:apply_interval(timer:hours(?UPDATE_INTERVAL),
timer:apply_interval(timer:hours(?UPDATE_INTERVAL), ?MODULE, update_bl_c2s, []), ?MODULE, update_bl_c2s, []),
loop(State). loop(State).
%% Remove timer when stop is received. %% Remove timer when stop is received.
loop(_State) -> loop(_State) -> receive stop -> ok end.
receive
stop ->
ok
end.
%% Download blacklist file from ProcessOne XAAI %% Download blacklist file from ProcessOne XAAI
%% and update the table internal table %% and update the table internal table
@ -93,15 +89,17 @@ loop(_State) ->
update_bl_c2s() -> update_bl_c2s() ->
?INFO_MSG("Updating C2S Blacklist", []), ?INFO_MSG("Updating C2S Blacklist", []),
case httpc:request(?BLC2S) of case httpc:request(?BLC2S) of
{ok, {{_Version, 200, _Reason}, _Headers, Body}} -> {ok, 200, _Headers, Body} ->
IPs = string:tokens(Body,"\n"), IPs = str:tokens(Body, <<"\n">>),
ets:delete_all_objects(bl_c2s), ets:delete_all_objects(bl_c2s),
lists:foreach( lists:foreach(fun (IP) ->
fun(IP) -> ets:insert(bl_c2s,
ets:insert(bl_c2s, #bl_c2s{ip=list_to_binary(IP)}) #bl_c2s{ip = IP})
end, IPs); end,
IPs);
{error, Reason} -> {error, Reason} ->
?ERROR_MSG("Cannot download C2S blacklist file. Reason: ~p", ?ERROR_MSG("Cannot download C2S blacklist file. "
"Reason: ~p",
[Reason]) [Reason])
end. end.
@ -111,16 +109,15 @@ update_bl_c2s() ->
%% true: IP is blacklisted %% true: IP is blacklisted
%% IPV4 IP tuple: %% IPV4 IP tuple:
is_ip_in_c2s_blacklist(_Val, IP) when is_tuple(IP) -> is_ip_in_c2s_blacklist(_Val, IP) when is_tuple(IP) ->
BinaryIP = list_to_binary(jlib:ip_to_list(IP)), BinaryIP = jlib:ip_to_list(IP),
case ets:lookup(bl_c2s, BinaryIP) of case ets:lookup(bl_c2s, BinaryIP) of
[] -> %% Not in blacklist [] -> %% Not in blacklist
false; false;
[_] -> %% Blacklisted! [_] -> {stop, true}
{stop, true}
end; end;
is_ip_in_c2s_blacklist(_Val, _IP) -> is_ip_in_c2s_blacklist(_Val, _IP) -> false.
false.
%% TODO: %% TODO:
%% - For now, we do not kick user already logged on a given IP after %% - For now, we do not kick user already logged on a given IP after
%% we update the blacklist. %% we update the blacklist.

View File

@ -24,7 +24,7 @@ EFLAGS += -pz ..
# make debug=true to compile Erlang module with debug informations. # make debug=true to compile Erlang module with debug informations.
ifdef debug ifdef debug
EFLAGS+=+debug_info +export_all EFLAGS+=+debug_info
endif endif
ERLSHLIBS = ../iconv_erl.so ERLSHLIBS = ../iconv_erl.so

View File

@ -25,6 +25,7 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(iconv). -module(iconv).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(gen_server). -behaviour(gen_server).
@ -32,23 +33,20 @@
-export([start/0, start_link/0, convert/3]). -export([start/0, start_link/0, convert/3]).
%% Internal exports, call-back functions. %% Internal exports, call-back functions.
-export([init/1, -export([init/1, handle_call/3, handle_cast/2,
handle_call/3, handle_info/2, code_change/3, terminate/2]).
handle_cast/2,
handle_info/2,
code_change/3,
terminate/2]).
start() -> start() ->
gen_server:start({local, ?MODULE}, ?MODULE, [], []). gen_server:start({local, ?MODULE}, ?MODULE, [], []).
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [],
[]).
init([]) -> init([]) ->
case erl_ddll:load_driver(ejabberd:get_so_path(), iconv_erl) of case erl_ddll:load_driver(ejabberd:get_so_path(),
iconv_erl)
of
ok -> ok; ok -> ok;
{error, already_loaded} -> ok {error, already_loaded} -> ok
end, end,
@ -57,38 +55,28 @@ init([]) ->
ets:insert(iconv_table, {port, Port}), ets:insert(iconv_table, {port, Port}),
{ok, Port}. {ok, Port}.
%%% -------------------------------------------------------- %%% --------------------------------------------------------
%%% The call-back functions. %%% The call-back functions.
%%% -------------------------------------------------------- %%% --------------------------------------------------------
handle_call(_, _, State) -> handle_call(_, _, State) -> {noreply, State}.
{noreply, State}.
handle_cast(_, State) -> handle_cast(_, State) -> {noreply, State}.
{noreply, State}.
handle_info({'EXIT', Port, Reason}, Port) -> handle_info({'EXIT', Port, Reason}, Port) ->
{stop, {port_died, Reason}, Port}; {stop, {port_died, Reason}, Port};
handle_info({'EXIT', _Pid, _Reason}, Port) -> handle_info({'EXIT', _Pid, _Reason}, Port) ->
{noreply, Port}; {noreply, Port};
handle_info(_, State) -> handle_info(_, State) -> {noreply, State}.
{noreply, State}.
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) -> {ok, State}.
{ok, State}.
terminate(_Reason, Port) ->
Port ! {self, close},
ok.
terminate(_Reason, Port) -> Port ! {self, close}, ok.
-spec convert(binary(), binary(), binary()) -> binary().
convert(From, To, String) -> convert(From, To, String) ->
[{port, Port} | _] = ets:lookup(iconv_table, port), [{port, Port} | _] = ets:lookup(iconv_table, port),
Bin = term_to_binary({From, To, String}), Bin = term_to_binary({From, To, String}),
BRes = port_control(Port, 1, Bin), BRes = port_control(Port, 1, Bin),
binary_to_list(BRes). (BRes).

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -25,28 +25,28 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(mod_last). -module(mod_last).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(gen_mod). -behaviour(gen_mod).
-export([start/2, -export([start/2, stop/1, process_local_iq/3, export/1,
stop/1, process_sm_iq/3, on_presence_update/4,
process_local_iq/3, store_last_info/4, get_last_info/2, remove_user/2]).
process_sm_iq/3,
on_presence_update/4,
store_last_info/4,
get_last_info/2,
remove_user/2]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl"). -include("jlib.hrl").
-include("mod_privacy.hrl"). -include("mod_privacy.hrl").
-record(last_activity, {us, timestamp, status}). -record(last_activity, {us = {<<"">>, <<"">>} :: {binary(), binary()},
timestamp = 0 :: non_neg_integer(),
status = <<"">> :: binary()}).
start(Host, Opts) -> start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
case gen_mod:db_type(Opts) of case gen_mod:db_type(Opts) of
mnesia -> mnesia ->
mnesia:create_table(last_activity, mnesia:create_table(last_activity,
@ -54,63 +54,75 @@ start(Host, Opts) ->
{attributes, {attributes,
record_info(fields, last_activity)}]), record_info(fields, last_activity)}]),
update_table(); update_table();
_ -> _ -> ok
ok
end, end,
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_LAST, gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?MODULE, process_local_iq, IQDisc), ?NS_LAST, ?MODULE, process_local_iq, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_LAST, gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?MODULE, process_sm_iq, IQDisc), ?NS_LAST, ?MODULE, process_sm_iq, IQDisc),
ejabberd_hooks:add(remove_user, Host, ejabberd_hooks:add(remove_user, Host, ?MODULE,
?MODULE, remove_user, 50), remove_user, 50),
ejabberd_hooks:add(unset_presence_hook, Host, ejabberd_hooks:add(unset_presence_hook, Host, ?MODULE,
?MODULE, on_presence_update, 50). on_presence_update, 50).
stop(Host) -> stop(Host) ->
ejabberd_hooks:delete(remove_user, Host, ejabberd_hooks:delete(remove_user, Host, ?MODULE,
?MODULE, remove_user, 50), remove_user, 50),
ejabberd_hooks:delete(unset_presence_hook, Host, ejabberd_hooks:delete(unset_presence_hook, Host,
?MODULE, on_presence_update, 50), ?MODULE, on_presence_update, 50),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_LAST), gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_LAST). ?NS_LAST),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_LAST).
%%% %%%
%%% Uptime of ejabberd node %%% Uptime of ejabberd node
%%% %%%
process_local_iq(_From, _To, #iq{type = Type, sub_el = SubEl} = IQ) -> process_local_iq(_From, _To,
#iq{type = Type, sub_el = SubEl} = IQ) ->
case Type of case Type of
set -> set ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
get -> get ->
Sec = get_node_uptime(), Sec = get_node_uptime(),
IQ#iq{type = result, IQ#iq{type = result,
sub_el = [{xmlelement, "query", sub_el =
[{"xmlns", ?NS_LAST}, [#xmlel{name = <<"query">>,
{"seconds", integer_to_list(Sec)}], attrs =
[]}]} [{<<"xmlns">>, ?NS_LAST},
{<<"seconds">>,
iolist_to_binary(integer_to_list(Sec))}],
children = []}]}
end. end.
%% @spec () -> integer() %% @spec () -> integer()
%% @doc Get the uptime of the ejabberd node, expressed in seconds. %% @doc Get the uptime of the ejabberd node, expressed in seconds.
%% When ejabberd is starting, ejabberd_config:start/0 stores the datetime. %% When ejabberd is starting, ejabberd_config:start/0 stores the datetime.
get_node_uptime() -> get_node_uptime() ->
case ejabberd_config:get_local_option(node_start) of case ejabberd_config:get_local_option(
{_, _, _} = StartNow -> node_start,
now_to_seconds(now()) - now_to_seconds(StartNow); fun({MegaSecs, Secs, MicroSecs} = Now)
_undefined -> when is_integer(MegaSecs), MegaSecs >= 0,
trunc(element(1, erlang:statistics(wall_clock))/1000) is_integer(Secs), Secs >= 0,
is_integer(MicroSecs), MicroSecs >= 0 ->
Now
end) of
undefined ->
trunc(element(1, erlang:statistics(wall_clock)) / 1000);
StartNow ->
now_to_seconds(now()) - now_to_seconds(StartNow)
end. end.
now_to_seconds({MegaSecs, Secs, _MicroSecs}) -> now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
MegaSecs * 1000000 + Secs. MegaSecs * 1000000 + Secs.
%%% %%%
%%% Serve queries about user last online %%% Serve queries about user last online
%%% %%%
process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) -> process_sm_iq(From, To,
#iq{type = Type, sub_el = SubEl} = IQ) ->
case Type of case Type of
set -> set ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
@ -118,64 +130,61 @@ process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
User = To#jid.luser, User = To#jid.luser,
Server = To#jid.lserver, Server = To#jid.lserver,
{Subscription, _Groups} = {Subscription, _Groups} =
ejabberd_hooks:run_fold( ejabberd_hooks:run_fold(roster_get_jid_info, Server,
roster_get_jid_info, Server,
{none, []}, [User, Server, From]), {none, []}, [User, Server, From]),
if if (Subscription == both) or (Subscription == from) or
(Subscription == both) or (Subscription == from) (From#jid.luser == To#jid.luser) and
or ((From#jid.luser == To#jid.luser) (From#jid.lserver == To#jid.lserver) ->
and (From#jid.lserver == To#jid.lserver)) -> UserListRecord =
UserListRecord = ejabberd_hooks:run_fold( ejabberd_hooks:run_fold(privacy_get_user_list, Server,
privacy_get_user_list, Server, #userlist{}, [User, Server]),
#userlist{}, case ejabberd_hooks:run_fold(privacy_check_packet,
[User, Server]), Server, allow,
case ejabberd_hooks:run_fold(
privacy_check_packet, Server,
allow,
[User, Server, UserListRecord, [User, Server, UserListRecord,
{To, From, {To, From,
{xmlelement, "presence", [], []}}, #xmlel{name = <<"presence">>,
out]) of attrs = [],
allow -> children = []}},
get_last_iq(IQ, SubEl, User, Server); out])
of
allow -> get_last_iq(IQ, SubEl, User, Server);
deny -> deny ->
IQ#iq{type = error, IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
sub_el = [SubEl, ?ERR_FORBIDDEN]}
end; end;
true -> true ->
IQ#iq{type = error, IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
sub_el = [SubEl, ?ERR_FORBIDDEN]}
end end
end. end.
%% @spec (LUser::string(), LServer::string()) -> %% @spec (LUser::string(), LServer::string()) ->
%% {ok, TimeStamp::integer(), Status::string()} | not_found | {error, Reason} %% {ok, TimeStamp::integer(), Status::string()} | not_found | {error, Reason}
get_last(LUser, LServer) -> get_last(LUser, LServer) ->
get_last(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)). get_last(LUser, LServer,
gen_mod:db_type(LServer, ?MODULE)).
get_last(LUser, LServer, mnesia) -> get_last(LUser, LServer, mnesia) ->
case catch mnesia:dirty_read(last_activity, {LUser, LServer}) of case catch mnesia:dirty_read(last_activity,
{'EXIT', Reason} -> {LUser, LServer})
{error, Reason}; of
[] -> {'EXIT', Reason} -> {error, Reason};
not_found; [] -> not_found;
[#last_activity{timestamp = TimeStamp, status = Status}] -> [#last_activity{timestamp = TimeStamp,
status = Status}] ->
{ok, TimeStamp, Status} {ok, TimeStamp, Status}
end; end;
get_last(LUser, LServer, odbc) -> get_last(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser), Username = ejabberd_odbc:escape(LUser),
case catch odbc_queries:get_last(LServer, Username) of case catch odbc_queries:get_last(LServer, Username) of
{selected, ["seconds","state"], []} -> {selected, [<<"seconds">>, <<"state">>], []} ->
not_found; not_found;
{selected, ["seconds","state"], [{STimeStamp, Status}]} -> {selected, [<<"seconds">>, <<"state">>],
case catch list_to_integer(STimeStamp) of [[STimeStamp, Status]]} ->
case catch jlib:binary_to_integer(STimeStamp) of
TimeStamp when is_integer(TimeStamp) -> TimeStamp when is_integer(TimeStamp) ->
{ok, TimeStamp, Status}; {ok, TimeStamp, Status};
Reason -> Reason -> {error, {invalid_timestamp, Reason}}
{error, {invalid_timestamp, Reason}}
end; end;
Reason -> Reason -> {error, {invalid_result, Reason}}
{error, {invalid_result, Reason}}
end. end.
get_last_iq(IQ, SubEl, LUser, LServer) -> get_last_iq(IQ, SubEl, LUser, LServer) ->
@ -183,24 +192,31 @@ get_last_iq(IQ, SubEl, LUser, LServer) ->
[] -> [] ->
case get_last(LUser, LServer) of case get_last(LUser, LServer) of
{error, _Reason} -> {error, _Reason} ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}; IQ#iq{type = error,
sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
not_found -> not_found ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}; IQ#iq{type = error,
sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
{ok, TimeStamp, Status} -> {ok, TimeStamp, Status} ->
TimeStamp2 = now_to_seconds(now()), TimeStamp2 = now_to_seconds(now()),
Sec = TimeStamp2 - TimeStamp, Sec = TimeStamp2 - TimeStamp,
IQ#iq{type = result, IQ#iq{type = result,
sub_el = [{xmlelement, "query", sub_el =
[{"xmlns", ?NS_LAST}, [#xmlel{name = <<"query">>,
{"seconds", integer_to_list(Sec)}], attrs =
[{xmlcdata, Status}]}]} [{<<"xmlns">>, ?NS_LAST},
{<<"seconds">>,
iolist_to_binary(integer_to_list(Sec))}],
children = [{xmlcdata, Status}]}]}
end; end;
_ -> _ ->
IQ#iq{type = result, IQ#iq{type = result,
sub_el = [{xmlelement, "query", sub_el =
[{"xmlns", ?NS_LAST}, [#xmlel{name = <<"query">>,
{"seconds", "0"}], attrs =
[]}]} [{<<"xmlns">>, ?NS_LAST},
{<<"seconds">>, <<"0">>}],
children = []}]}
end. end.
on_presence_update(User, Server, _Resource, Status) -> on_presence_update(User, Server, _Resource, Status) ->
@ -211,9 +227,11 @@ store_last_info(User, Server, TimeStamp, Status) ->
LUser = jlib:nodeprep(User), LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
DBType = gen_mod:db_type(LServer, ?MODULE), DBType = gen_mod:db_type(LServer, ?MODULE),
store_last_info(LUser, LServer, TimeStamp, Status, DBType). store_last_info(LUser, LServer, TimeStamp, Status,
DBType).
store_last_info(LUser, LServer, TimeStamp, Status, mnesia) -> store_last_info(LUser, LServer, TimeStamp, Status,
mnesia) ->
US = {LUser, LServer}, US = {LUser, LServer},
F = fun () -> F = fun () ->
mnesia:write(#last_activity{us = US, mnesia:write(#last_activity{us = US,
@ -221,20 +239,21 @@ store_last_info(LUser, LServer, TimeStamp, Status, mnesia) ->
status = Status}) status = Status})
end, end,
mnesia:transaction(F); mnesia:transaction(F);
store_last_info(LUser, LServer, TimeStamp, Status, odbc) -> store_last_info(LUser, LServer, TimeStamp, Status,
odbc) ->
Username = ejabberd_odbc:escape(LUser), Username = ejabberd_odbc:escape(LUser),
Seconds = ejabberd_odbc:escape(integer_to_list(TimeStamp)), Seconds =
ejabberd_odbc:escape(iolist_to_binary(integer_to_list(TimeStamp))),
State = ejabberd_odbc:escape(Status), State = ejabberd_odbc:escape(Status),
odbc_queries:set_last_t(LServer, Username, Seconds, State). odbc_queries:set_last_t(LServer, Username, Seconds,
State).
%% @spec (LUser::string(), LServer::string()) -> %% @spec (LUser::string(), LServer::string()) ->
%% {ok, TimeStamp::integer(), Status::string()} | not_found %% {ok, TimeStamp::integer(), Status::string()} | not_found
get_last_info(LUser, LServer) -> get_last_info(LUser, LServer) ->
case get_last(LUser, LServer) of case get_last(LUser, LServer) of
{error, _Reason} -> {error, _Reason} -> not_found;
not_found; Res -> Res
Res ->
Res
end. end.
remove_user(User, Server) -> remove_user(User, Server) ->
@ -245,9 +264,7 @@ remove_user(User, Server) ->
remove_user(LUser, LServer, mnesia) -> remove_user(LUser, LServer, mnesia) ->
US = {LUser, LServer}, US = {LUser, LServer},
F = fun() -> F = fun () -> mnesia:delete({last_activity, US}) end,
mnesia:delete({last_activity, US})
end,
mnesia:transaction(F); mnesia:transaction(F);
remove_user(LUser, LServer, odbc) -> remove_user(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser), Username = ejabberd_odbc:escape(LUser),
@ -257,47 +274,33 @@ update_table() ->
Fields = record_info(fields, last_activity), Fields = record_info(fields, last_activity),
case mnesia:table_info(last_activity, attributes) of case mnesia:table_info(last_activity, attributes) of
Fields -> Fields ->
ok; ejabberd_config:convert_table_to_binary(
[user, timestamp, status] -> last_activity, Fields, set,
?INFO_MSG("Converting last_activity table from {user, timestamp, status} format", []), fun(#last_activity{us = {U, _}}) -> U end,
Host = ?MYNAME, fun(#last_activity{us = {U, S}, status = Status} = R) ->
mnesia:transform_table(last_activity, ignore, Fields), R#last_activity{us = {iolist_to_binary(U),
F = fun() -> iolist_to_binary(S)},
mnesia:write_lock_table(last_activity), status = iolist_to_binary(Status)}
mnesia:foldl( end);
fun({_, U, T, S} = R, _) ->
mnesia:delete_object(R),
mnesia:write(
#last_activity{us = {U, Host},
timestamp = T,
status = S})
end, ok, last_activity)
end,
mnesia:transaction(F);
[user, timestamp] ->
?INFO_MSG("Converting last_activity table from {user, timestamp} format", []),
Host = ?MYNAME,
mnesia:transform_table(
last_activity,
fun({_, U, T}) ->
#last_activity{us = U,
timestamp = T,
status = ""}
end, Fields),
F = fun() ->
mnesia:write_lock_table(last_activity),
mnesia:foldl(
fun({_, U, T, S} = R, _) ->
mnesia:delete_object(R),
mnesia:write(
#last_activity{us = {U, Host},
timestamp = T,
status = S})
end, ok, last_activity)
end,
mnesia:transaction(F);
_ -> _ ->
?INFO_MSG("Recreating last_activity table", []), ?INFO_MSG("Recreating last_activity table", []),
mnesia:transform_table(last_activity, ignore, Fields) mnesia:transform_table(last_activity, ignore, Fields)
end. end.
export(_Server) ->
[{last_activity,
fun(Host, #last_activity{us = {LUser, LServer},
timestamp = TimeStamp, status = Status})
when LServer == Host ->
Username = ejabberd_odbc:escape(LUser),
Seconds =
ejabberd_odbc:escape(jlib:integer_to_binary(TimeStamp)),
State = ejabberd_odbc:escape(Status),
[[<<"delete from last where username='">>, Username, <<"';">>],
[<<"insert into last(username, seconds, "
"state) values ('">>,
Username, <<"', '">>, Seconds, <<"', '">>, State,
<<"');">>]];
(_Host, _R) ->
[]
end}].

View File

@ -14,7 +14,7 @@ EFLAGS += -pz ..
# make debug=true to compile Erlang module with debug informations. # make debug=true to compile Erlang module with debug informations.
ifdef debug ifdef debug
EFLAGS+=+debug_info +export_all EFLAGS+=+debug_info
endif endif
ifeq (@transient_supervisors@, false) ifeq (@transient_supervisors@, false)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -22,69 +22,95 @@
-define(MAX_USERS_DEFAULT, 200). -define(MAX_USERS_DEFAULT, 200).
-define(SETS, gb_sets). -define(SETS, gb_sets).
-define(DICT, dict). -define(DICT, dict).
-record(lqueue, {queue, len, max}). -record(lqueue,
{
-record(config, {title = "", queue :: queue(),
description = "", len :: integer(),
allow_change_subj = true, max :: integer()
allow_query_users = true,
allow_private_messages = true,
allow_private_messages_from_visitors = anyone,
allow_visitor_status = true,
allow_visitor_nickchange = true,
public = true,
public_list = true,
persistent = false,
moderated = true,
captcha_protected = false,
members_by_default = true,
members_only = false,
allow_user_invites = false,
password_protected = false,
password = "",
anonymous = true,
allow_voice_requests = true,
voice_request_min_interval = 1800,
max_users = ?MAX_USERS_DEFAULT,
logging = false,
captcha_whitelist = ?SETS:empty()
}). }).
-record(user, {jid, -type lqueue() :: #lqueue{}.
nick,
role,
last_presence}).
-record(activity, {message_time = 0, -record(config,
presence_time = 0, {
message_shaper, title = <<"">> :: binary(),
presence_shaper, description = <<"">> :: binary(),
message, allow_change_subj = true :: boolean(),
presence}). allow_query_users = true :: boolean(),
allow_private_messages = true :: boolean(),
allow_private_messages_from_visitors = anyone :: anyone | moderators | nobody ,
allow_visitor_status = true :: boolean(),
allow_visitor_nickchange = true :: boolean(),
public = true :: boolean(),
public_list = true :: boolean(),
persistent = false :: boolean(),
moderated = true :: boolean(),
captcha_protected = false :: boolean(),
members_by_default = true :: boolean(),
members_only = false :: boolean(),
allow_user_invites = false :: boolean(),
password_protected = false :: boolean(),
password = <<"">> :: binary(),
anonymous = true :: boolean(),
allow_voice_requests = true :: boolean(),
voice_request_min_interval = 1800 :: non_neg_integer(),
max_users = ?MAX_USERS_DEFAULT :: non_neg_integer() | none,
logging = false :: boolean(),
captcha_whitelist = (?SETS):empty() :: gb_set()
}).
-record(state, {room, -type config() :: #config{}.
host,
server_host,
mod,
access,
jid,
config = #config{},
users = ?DICT:new(),
last_voice_request_time = treap:empty(),
robots = ?DICT:new(),
nicks = ?DICT:new(),
affiliations = ?DICT:new(),
history,
subject = "",
subject_author = "",
just_created = false,
activity = treap:empty(),
room_shaper,
room_queue = queue:new()}).
-record(muc_online_users, {us, -type role() :: moderator | participant | visitor | none.
resource,
room, -record(user,
host}). {
jid :: jid(),
nick :: binary(),
role :: role(),
last_presence :: xmlel()
}).
-record(activity,
{
message_time = 0 :: integer(),
presence_time = 0 :: integer(),
message_shaper :: shaper:shaper(),
presence_shaper :: shaper:shaper(),
message :: xmlel(),
presence :: {binary(), xmlel()}
}).
-record(state,
{
room = <<"">> :: binary(),
host = <<"">> :: binary(),
server_host = <<"">> :: binary(),
access = {none,none,none,none} :: {atom(), atom(), atom(), atom()},
jid = #jid{} :: jid(),
config = #config{} :: config(),
users = (?DICT):new() :: dict(),
last_voice_request_time = treap:empty() :: treap:treap(),
robots = (?DICT):new() :: dict(),
nicks = (?DICT):new() :: dict(),
affiliations = (?DICT):new() :: dict(),
history :: lqueue(),
subject = <<"">> :: binary(),
subject_author = <<"">> :: binary(),
just_created = false :: boolean(),
activity = treap:empty() :: treap:treap(),
room_shaper = none :: shaper:shaper(),
room_queue = queue:new() :: queue()
}).
-record(muc_online_users, {us = {<<>>, <<>>} :: {binary(), binary()},
resource = <<>> :: binary() | '_',
room = <<>> :: binary() | '_',
host = <<>> :: binary() | '_'}).
-type muc_online_users() :: #muc_online_users{}.
-type muc_room_state() :: #state{}.

File diff suppressed because it is too large Load Diff

View File

@ -25,18 +25,24 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(mod_ping). -module(mod_ping).
-author('bjc@kublai.com'). -author('bjc@kublai.com').
-behavior(gen_mod). -behavior(gen_mod).
-behavior(gen_server). -behavior(gen_server).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl"). -include("jlib.hrl").
-define(SUPERVISOR, ejabberd_sup). -define(SUPERVISOR, ejabberd_sup).
-define(NS_PING, "urn:xmpp:ping").
-define(DEFAULT_SEND_PINGS, false). % bool() -define(NS_PING, <<"urn:xmpp:ping">>).
-define(DEFAULT_PING_INTERVAL, 60). % seconds
-define(DEFAULT_SEND_PINGS, false).
-define(DEFAULT_PING_INTERVAL, 60).
-define(DICT, dict). -define(DICT, dict).
@ -47,24 +53,27 @@
-export([start/2, stop/1]). -export([start/2, stop/1]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, terminate/2, handle_call/3, handle_cast/2, -export([init/1, terminate/2, handle_call/3,
handle_info/2, code_change/3]). handle_cast/2, handle_info/2, code_change/3]).
%% Hook callbacks %% Hook callbacks
-export([iq_ping/3, user_online/3, user_offline/3, user_send/3]). -export([iq_ping/3, user_online/3, user_offline/3,
user_send/3]).
-record(state, {host = "", -record(state,
send_pings = ?DEFAULT_SEND_PINGS, {host = <<"">>,
ping_interval = ?DEFAULT_PING_INTERVAL, send_pings = ?DEFAULT_SEND_PINGS :: boolean(),
timeout_action = none, ping_interval = ?DEFAULT_PING_INTERVAL :: non_neg_integer(),
timers = ?DICT:new()}). timeout_action = none :: none | kill,
timers = (?DICT):new() :: dict()}).
%%==================================================================== %%====================================================================
%% API %% API
%%==================================================================== %%====================================================================
start_link(Host, Opts) -> start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE), Proc = gen_mod:get_module_proc(Host, ?MODULE),
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). gen_server:start_link({local, Proc}, ?MODULE,
[Host, Opts], []).
start_ping(Host, JID) -> start_ping(Host, JID) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE), Proc = gen_mod:get_module_proc(Host, ?MODULE),
@ -92,43 +101,50 @@ stop(Host) ->
%% gen_server callbacks %% gen_server callbacks
%%==================================================================== %%====================================================================
init([Host, Opts]) -> init([Host, Opts]) ->
SendPings = gen_mod:get_opt(send_pings, Opts, ?DEFAULT_SEND_PINGS), SendPings = gen_mod:get_opt(send_pings, Opts,
PingInterval = gen_mod:get_opt(ping_interval, Opts, ?DEFAULT_PING_INTERVAL), fun(B) when is_boolean(B) -> B end,
TimeoutAction = gen_mod:get_opt(timeout_action, Opts, none), ?DEFAULT_SEND_PINGS),
IQDisc = gen_mod:get_opt(iqdisc, Opts, no_queue), PingInterval = gen_mod:get_opt(ping_interval, Opts,
fun(I) when is_integer(I), I>0 -> I end,
?DEFAULT_PING_INTERVAL),
TimeoutAction = gen_mod:get_opt(timeout_action, Opts,
fun(none) -> none;
(kill) -> kill
end, none),
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
no_queue),
mod_disco:register_feature(Host, ?NS_PING), mod_disco:register_feature(Host, ?NS_PING),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PING, gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?MODULE, iq_ping, IQDisc), ?NS_PING, ?MODULE, iq_ping, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PING, gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?MODULE, iq_ping, IQDisc), ?NS_PING, ?MODULE, iq_ping, IQDisc),
case SendPings of case SendPings of
true -> true ->
%% Ping requests are sent to all entities, whether they
%% announce 'urn:xmpp:ping' in their caps or not
ejabberd_hooks:add(sm_register_connection_hook, Host, ejabberd_hooks:add(sm_register_connection_hook, Host,
?MODULE, user_online, 100), ?MODULE, user_online, 100),
ejabberd_hooks:add(sm_remove_connection_hook, Host, ejabberd_hooks:add(sm_remove_connection_hook, Host,
?MODULE, user_offline, 100), ?MODULE, user_offline, 100),
ejabberd_hooks:add(user_send_packet, Host, ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
?MODULE, user_send, 100); user_send, 100);
_ -> _ -> ok
ok
end, end,
{ok, #state{host = Host, {ok,
send_pings = SendPings, #state{host = Host, send_pings = SendPings,
ping_interval = PingInterval, ping_interval = PingInterval,
timeout_action = TimeoutAction, timeout_action = TimeoutAction,
timers = ?DICT:new()}}. timers = (?DICT):new()}}.
terminate(_Reason, #state{host = Host}) -> terminate(_Reason, #state{host = Host}) ->
ejabberd_hooks:delete(sm_remove_connection_hook, Host, ejabberd_hooks:delete(sm_remove_connection_hook, Host,
?MODULE, user_offline, 100), ?MODULE, user_offline, 100),
ejabberd_hooks:delete(sm_register_connection_hook, Host, ejabberd_hooks:delete(sm_register_connection_hook, Host,
?MODULE, user_online, 100), ?MODULE, user_online, 100),
ejabberd_hooks:delete(user_send_packet, Host, ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
?MODULE, user_send, 100), user_send, 100),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PING), gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PING), ?NS_PING),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_PING),
mod_disco:unregister_feature(Host, ?NS_PING). mod_disco:unregister_feature(Host, ?NS_PING).
handle_call(stop, _From, State) -> handle_call(stop, _From, State) ->
@ -137,56 +153,60 @@ handle_call(_Req, _From, State) ->
{reply, {error, badarg}, State}. {reply, {error, badarg}, State}.
handle_cast({start_ping, JID}, State) -> handle_cast({start_ping, JID}, State) ->
Timers = add_timer(JID, State#state.ping_interval, State#state.timers), Timers = add_timer(JID, State#state.ping_interval,
State#state.timers),
{noreply, State#state{timers = Timers}}; {noreply, State#state{timers = Timers}};
handle_cast({stop_ping, JID}, State) -> handle_cast({stop_ping, JID}, State) ->
Timers = del_timer(JID, State#state.timers), Timers = del_timer(JID, State#state.timers),
{noreply, State#state{timers = Timers}}; {noreply, State#state{timers = Timers}};
handle_cast({iq_pong, JID, timeout}, State) -> handle_cast({iq_pong, JID, timeout}, State) ->
Timers = del_timer(JID, State#state.timers), Timers = del_timer(JID, State#state.timers),
ejabberd_hooks:run(user_ping_timeout, State#state.host, [JID]), ejabberd_hooks:run(user_ping_timeout, State#state.host,
[JID]),
case State#state.timeout_action of case State#state.timeout_action of
kill -> kill ->
#jid{user = User, server = Server, resource = Resource} = JID, #jid{user = User, server = Server,
case ejabberd_sm:get_session_pid(User, Server, Resource) of resource = Resource} =
Pid when is_pid(Pid) -> JID,
ejabberd_c2s:stop(Pid); case ejabberd_sm:get_session_pid(User, Server, Resource)
_ -> of
ok Pid when is_pid(Pid) -> ejabberd_c2s:stop(Pid);
_ -> ok
end; end;
_ -> _ -> ok
ok
end, end,
{noreply, State#state{timers = Timers}}; {noreply, State#state{timers = Timers}};
handle_cast(_Msg, State) -> handle_cast(_Msg, State) -> {noreply, State}.
{noreply, State}.
handle_info({timeout, _TRef, {ping, JID}}, State) -> handle_info({timeout, _TRef, {ping, JID}}, State) ->
IQ = #iq{type = get, IQ = #iq{type = get,
sub_el = [{xmlelement, "ping", [{"xmlns", ?NS_PING}], []}]}, sub_el =
[#xmlel{name = <<"ping">>,
attrs = [{<<"xmlns">>, ?NS_PING}], children = []}]},
Pid = self(), Pid = self(),
F = fun (Response) -> F = fun (Response) ->
gen_server:cast(Pid, {iq_pong, JID, Response}) gen_server:cast(Pid, {iq_pong, JID, Response})
end, end,
From = jlib:make_jid("", State#state.host, ""), From = jlib:make_jid(<<"">>, State#state.host, <<"">>),
ejabberd_local:route_iq(From, JID, IQ, F), ejabberd_local:route_iq(From, JID, IQ, F),
Timers = add_timer(JID, State#state.ping_interval, State#state.timers), Timers = add_timer(JID, State#state.ping_interval,
State#state.timers),
{noreply, State#state{timers = Timers}}; {noreply, State#state{timers = Timers}};
handle_info(_Info, State) -> handle_info(_Info, State) -> {noreply, State}.
{noreply, State}.
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) -> {ok, State}.
{ok, State}.
%%==================================================================== %%====================================================================
%% Hook callbacks %% Hook callbacks
%%==================================================================== %%====================================================================
iq_ping(_From, _To, #iq{type = Type, sub_el = SubEl} = IQ) -> iq_ping(_From, _To,
#iq{type = Type, sub_el = SubEl} = IQ) ->
case {Type, SubEl} of case {Type, SubEl} of
{get, {xmlelement, "ping", _, _}} -> {get, #xmlel{name = <<"ping">>}} ->
IQ#iq{type = result, sub_el = []}; IQ#iq{type = result, sub_el = []};
_ -> _ ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]} IQ#iq{type = error,
sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]}
end. end.
user_online(_SID, JID, _Info) -> user_online(_SID, JID, _Info) ->
@ -203,35 +223,26 @@ user_send(JID, _From, _Packet) ->
%%==================================================================== %%====================================================================
add_timer(JID, Interval, Timers) -> add_timer(JID, Interval, Timers) ->
LJID = jlib:jid_tolower(JID), LJID = jlib:jid_tolower(JID),
NewTimers = case ?DICT:find(LJID, Timers) of NewTimers = case (?DICT):find(LJID, Timers) of
{ok, OldTRef} -> {ok, OldTRef} ->
cancel_timer(OldTRef), cancel_timer(OldTRef), (?DICT):erase(LJID, Timers);
?DICT:erase(LJID, Timers); _ -> Timers
_ ->
Timers
end, end,
TRef = erlang:start_timer(Interval * 1000, self(), {ping, JID}), TRef = erlang:start_timer(Interval * 1000, self(),
?DICT:store(LJID, TRef, NewTimers). {ping, JID}),
(?DICT):store(LJID, TRef, NewTimers).
del_timer(JID, Timers) -> del_timer(JID, Timers) ->
LJID = jlib:jid_tolower(JID), LJID = jlib:jid_tolower(JID),
case ?DICT:find(LJID, Timers) of case (?DICT):find(LJID, Timers) of
{ok, TRef} -> {ok, TRef} ->
cancel_timer(TRef), cancel_timer(TRef), (?DICT):erase(LJID, Timers);
?DICT:erase(LJID, Timers); _ -> Timers
_ ->
Timers
end. end.
cancel_timer(TRef) -> cancel_timer(TRef) ->
case erlang:cancel_timer(TRef) of case erlang:cancel_timer(TRef) of
false -> false ->
receive receive {timeout, TRef, _} -> ok after 0 -> ok end;
{timeout, TRef, _} -> _ -> ok
ok
after 0 ->
ok
end;
_ ->
ok
end. end.

View File

@ -28,18 +28,18 @@
-behavior(gen_mod). -behavior(gen_mod).
-export([start/2, -export([start/2, stop/1, check_packet/6]).
stop/1,
check_packet/6]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl"). -include("jlib.hrl").
-record(pres_counter, {dir, start, count, logged = false}). -record(pres_counter,
{dir, start, count, logged = false}).
start(Host, _Opts) -> start(Host, _Opts) ->
ejabberd_hooks:add(privacy_check_packet, Host, ejabberd_hooks:add(privacy_check_packet, Host, ?MODULE,
?MODULE, check_packet, 25), check_packet, 25),
ok. ok.
stop(Host) -> stop(Host) ->
@ -47,88 +47,75 @@ stop(Host) ->
?MODULE, check_packet, 25), ?MODULE, check_packet, 25),
ok. ok.
check_packet(_, _User, Server, check_packet(_, _User, Server, _PrivacyList,
_PrivacyList, {From, To, #xmlel{name = Name, attrs = Attrs}}, Dir) ->
{From, To, {xmlelement, Name, Attrs, _}},
Dir) ->
case Name of case Name of
"presence" -> <<"presence">> ->
IsSubscription = IsSubscription = case xml:get_attr_s(<<"type">>, Attrs)
case xml:get_attr_s("type", Attrs) of of
"subscribe" -> true; <<"subscribe">> -> true;
"subscribed" -> true; <<"subscribed">> -> true;
"unsubscribe" -> true; <<"unsubscribe">> -> true;
"unsubscribed" -> true; <<"unsubscribed">> -> true;
_ -> false _ -> false
end, end,
if if IsSubscription ->
IsSubscription ->
JID = case Dir of JID = case Dir of
in -> To; in -> To;
out -> From out -> From
end, end,
update(Server, JID, Dir); update(Server, JID, Dir);
true -> true -> allow
allow
end; end;
_ -> _ -> allow
allow
end. end.
update(Server, JID, Dir) -> update(Server, JID, Dir) ->
%% get options StormCount = gen_mod:get_module_opt(Server, ?MODULE, count,
StormCount = gen_mod:get_module_opt(Server, ?MODULE, count, 5), fun(I) when is_integer(I), I>0 -> I end,
TimeInterval = gen_mod:get_module_opt(Server, ?MODULE, interval, 60), 5),
TimeInterval = gen_mod:get_module_opt(Server, ?MODULE, interval,
fun(I) when is_integer(I), I>0 -> I end,
60),
{MegaSecs, Secs, _MicroSecs} = now(), {MegaSecs, Secs, _MicroSecs} = now(),
TimeStamp = MegaSecs * 1000000 + Secs, TimeStamp = MegaSecs * 1000000 + Secs,
case read(Dir) of case read(Dir) of
undefined -> undefined ->
write(Dir, #pres_counter{dir = Dir, write(Dir,
start = TimeStamp, #pres_counter{dir = Dir, start = TimeStamp, count = 1}),
count = 1}),
allow; allow;
#pres_counter{start = TimeStart, count = Count, logged = Logged} = R -> #pres_counter{start = TimeStart, count = Count,
%% record for this key exists, check if we're logged = Logged} =
%% within TimeInterval seconds, and whether the StormCount is R ->
%% high enough. or else just increment the count. if TimeStamp - TimeStart > TimeInterval ->
if write(Dir,
TimeStamp - TimeStart > TimeInterval -> R#pres_counter{start = TimeStamp, count = 1}),
write(Dir, R#pres_counter{
start = TimeStamp,
count = 1}),
allow; allow;
(Count =:= StormCount) and Logged -> (Count =:= StormCount) and Logged -> {stop, deny};
{stop, deny};
Count =:= StormCount -> Count =:= StormCount ->
write(Dir, R#pres_counter{logged = true}), write(Dir, R#pres_counter{logged = true}),
case Dir of case Dir of
in -> in ->
?WARNING_MSG( ?WARNING_MSG("User ~s is being flooded, ignoring received "
"User ~s is being flooded, " "presence subscriptions",
"ignoring received presence subscriptions",
[jlib:jid_to_string(JID)]); [jlib:jid_to_string(JID)]);
out -> out ->
IP = ejabberd_sm:get_user_ip( IP = ejabberd_sm:get_user_ip(JID#jid.luser,
JID#jid.luser,
JID#jid.lserver, JID#jid.lserver,
JID#jid.lresource), JID#jid.lresource),
?WARNING_MSG( ?WARNING_MSG("Flooder detected: ~s, on IP: ~s ignoring "
"Flooder detected: ~s, on IP: ~s " "sent presence subscriptions~n",
"ignoring sent presence subscriptions~n",
[jlib:jid_to_string(JID), [jlib:jid_to_string(JID),
jlib:ip_to_list(IP)]) jlib:ip_to_list(IP)])
end, end,
{stop, deny}; {stop, deny};
true -> true ->
write(Dir, R#pres_counter{ write(Dir,
start = TimeStamp, R#pres_counter{start = TimeStamp, count = Count + 1}),
count = Count + 1}),
allow allow
end end
end. end.
read(K)-> read(K) -> get({pres_counter, K}).
get({pres_counter, K}).
write(K, V)-> write(K, V) -> put({pres_counter, K}, V).
put({pres_counter, K}, V).

File diff suppressed because it is too large Load Diff

View File

@ -19,19 +19,26 @@
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-record(privacy, {us, -record(privacy, {us = {<<"">>, <<"">>} :: {binary(), binary()},
default = none, default = none :: none | binary(),
lists = []}). lists = [] :: [{binary(), [listitem()]}]}).
-record(listitem, {type = none, -record(listitem, {type = none :: none | jid | group | subscription,
value = none, value = none :: none | both | from | to | ljid() | binary(),
action, action = allow :: allow | deny,
order, order = 0 :: integer(),
match_all = false, match_all = false :: boolean(),
match_iq = false, match_iq = false :: boolean(),
match_message = false, match_message = false :: boolean(),
match_presence_in = false, match_presence_in = false :: boolean(),
match_presence_out = false match_presence_out = false :: boolean()}).
}).
-record(userlist, {name = none, list = [], needdb = false }). -type listitem() :: #listitem{}.
-record(userlist, {name = none :: none | binary(),
list = [] :: [listitem()],
needdb = false :: boolean()}).
-type userlist() :: #userlist{}.
-export_type([userlist/0]).

View File

@ -25,27 +25,30 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(mod_private). -module(mod_private).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(gen_mod). -behaviour(gen_mod).
-export([start/2, -export([start/2, stop/1, process_sm_iq/3,
stop/1, remove_user/2, get_data/2, export/1]).
process_sm_iq/3,
remove_user/2]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl"). -include("jlib.hrl").
-record(private_storage, {usns, xml}). -record(private_storage,
{usns = {<<"">>, <<"">>, <<"">>} :: {binary(), binary(), binary() |
'$1' | '_'},
xml = #xmlel{} :: xmlel() | '_' | '$1'}).
-define(Xmlel_Query(Attrs, Children), -define(Xmlel_Query(Attrs, Children),
( #xmlel{name = <<"query">>, attrs = Attrs,
{xmlelement, "query", Attrs, Children} children = Children}).
)).
start(Host, Opts) -> start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
case gen_mod:db_type(Opts) of case gen_mod:db_type(Opts) of
mnesia -> mnesia ->
mnesia:create_table(private_storage, mnesia:create_table(private_storage,
@ -53,118 +56,113 @@ start(Host, Opts) ->
{attributes, {attributes,
record_info(fields, private_storage)}]), record_info(fields, private_storage)}]),
update_table(); update_table();
_ -> _ -> ok
ok
end, end,
ejabberd_hooks:add(remove_user, Host, ejabberd_hooks:add(remove_user, Host, ?MODULE,
?MODULE, remove_user, 50), remove_user, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PRIVATE, gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?MODULE, process_sm_iq, IQDisc). ?NS_PRIVATE, ?MODULE, process_sm_iq, IQDisc).
stop(Host) -> stop(Host) ->
ejabberd_hooks:delete(remove_user, Host, ejabberd_hooks:delete(remove_user, Host, ?MODULE,
?MODULE, remove_user, 50), remove_user, 50),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PRIVATE). gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_PRIVATE).
process_sm_iq(#jid{luser = LUser, lserver = LServer}, process_sm_iq(#jid{luser = LUser, lserver = LServer},
#jid{luser = LUser, lserver = LServer}, IQ) #jid{luser = LUser, lserver = LServer}, IQ)
when IQ#iq.type == 'set' -> when IQ#iq.type == set ->
case IQ#iq.sub_el of case IQ#iq.sub_el of
{xmlelement, "query", _, Xmlels} -> #xmlel{name = <<"query">>, children = Xmlels} ->
case filter_xmlels(Xmlels) of case filter_xmlels(Xmlels) of
[] -> [] ->
IQ#iq{ IQ#iq{type = error,
type = error, sub_el = [IQ#iq.sub_el, ?ERR_NOT_ACCEPTABLE]};
sub_el = [IQ#iq.sub_el, ?ERR_NOT_ACCEPTABLE]
};
Data -> Data ->
DBType = gen_mod:db_type(LServer, ?MODULE), DBType = gen_mod:db_type(LServer, ?MODULE),
F = fun () -> F = fun () ->
lists:foreach( lists:foreach(fun (Datum) ->
fun
(Datum) ->
set_data(LUser, LServer, set_data(LUser, LServer,
Datum, DBType) Datum, DBType)
end, Data) end,
Data)
end, end,
case DBType of case DBType of
odbc -> odbc -> ejabberd_odbc:sql_transaction(LServer, F);
ejabberd_odbc:sql_transaction(LServer, F); mnesia -> mnesia:transaction(F)
mnesia ->
mnesia:transaction(F)
end, end,
IQ#iq{type = result, sub_el = []} IQ#iq{type = result, sub_el = []}
end; end;
_ -> _ ->
IQ#iq{type = error, sub_el = [IQ#iq.sub_el, ?ERR_NOT_ACCEPTABLE]} IQ#iq{type = error,
sub_el = [IQ#iq.sub_el, ?ERR_NOT_ACCEPTABLE]}
end; end;
%% %%
process_sm_iq(#jid{luser = LUser, lserver = LServer}, process_sm_iq(#jid{luser = LUser, lserver = LServer},
#jid{luser = LUser, lserver = LServer}, IQ) #jid{luser = LUser, lserver = LServer}, IQ)
when IQ#iq.type == 'get' -> when IQ#iq.type == get ->
case IQ#iq.sub_el of case IQ#iq.sub_el of
{xmlelement, "query", Attrs, Xmlels} -> #xmlel{name = <<"query">>, attrs = Attrs,
children = Xmlels} ->
case filter_xmlels(Xmlels) of case filter_xmlels(Xmlels) of
[] -> [] ->
IQ#iq{ IQ#iq{type = error,
type = error, sub_el = [IQ#iq.sub_el, ?ERR_BAD_FORMAT]};
sub_el = [IQ#iq.sub_el, ?ERR_BAD_FORMAT]
};
Data -> Data ->
case catch get_data(LUser, LServer, Data) of case catch get_data(LUser, LServer, Data) of
{'EXIT', _Reason} -> {'EXIT', _Reason} ->
IQ#iq{ IQ#iq{type = error,
type = error, sub_el =
sub_el = [IQ#iq.sub_el, ?ERR_INTERNAL_SERVER_ERROR] [IQ#iq.sub_el, ?ERR_INTERNAL_SERVER_ERROR]};
};
Storage_Xmlels -> Storage_Xmlels ->
IQ#iq{ IQ#iq{type = result,
type = result, sub_el = [?Xmlel_Query(Attrs, Storage_Xmlels)]}
sub_el = [?Xmlel_Query(Attrs, Storage_Xmlels)]
}
end end
end; end;
_ -> _ ->
IQ#iq{type = error, sub_el = [IQ#iq.sub_el, ?ERR_BAD_FORMAT]} IQ#iq{type = error,
sub_el = [IQ#iq.sub_el, ?ERR_BAD_FORMAT]}
end; end;
%% %%
process_sm_iq(_From, _To, IQ) -> process_sm_iq(_From, _To, IQ) ->
IQ#iq{type = error, sub_el = [IQ#iq.sub_el, ?ERR_FORBIDDEN]}. IQ#iq{type = error,
sub_el = [IQ#iq.sub_el, ?ERR_FORBIDDEN]}.
filter_xmlels(Xmlels) -> filter_xmlels(Xmlels, []).
filter_xmlels(Xmlels) -> filter_xmlels([], Data) -> lists:reverse(Data);
filter_xmlels(Xmlels, []). filter_xmlels([#xmlel{attrs = Attrs} = Xmlel | Xmlels],
Data) ->
filter_xmlels([], Data) -> case xml:get_attr_s(<<"xmlns">>, Attrs) of
lists:reverse(Data); <<"">> -> [];
filter_xmlels([{xmlelement, _, Attrs, _} = Xmlel | Xmlels], Data) ->
case xml:get_attr_s("xmlns", Attrs) of
"" -> [];
XmlNS -> filter_xmlels(Xmlels, [{XmlNS, Xmlel} | Data]) XmlNS -> filter_xmlels(Xmlels, [{XmlNS, Xmlel} | Data])
end; end;
filter_xmlels([_ | Xmlels], Data) -> filter_xmlels([_ | Xmlels], Data) ->
filter_xmlels(Xmlels, Data). filter_xmlels(Xmlels, Data).
set_data(LUser, LServer, {XmlNS, Xmlel}, mnesia) -> set_data(LUser, LServer, {XmlNS, Xmlel}, mnesia) ->
mnesia:write(#private_storage{ mnesia:write(#private_storage{usns =
usns = {LUser, LServer, XmlNS}, {LUser, LServer, XmlNS},
xml = Xmlel}); xml = Xmlel});
set_data(LUser, LServer, {XMLNS, El}, odbc) -> set_data(LUser, LServer, {XMLNS, El}, odbc) ->
Username = ejabberd_odbc:escape(LUser), Username = ejabberd_odbc:escape(LUser),
LXMLNS = ejabberd_odbc:escape(XMLNS), LXMLNS = ejabberd_odbc:escape(XMLNS),
SData = ejabberd_odbc:escape( SData = ejabberd_odbc:escape(xml:element_to_binary(El)),
xml:element_to_binary(El)), odbc_queries:set_private_data(LServer, Username, LXMLNS,
odbc_queries:set_private_data(LServer, Username, LXMLNS, SData). SData).
get_data(LUser, LServer, Data) -> get_data(LUser, LServer, Data) ->
get_data(LUser, LServer, gen_mod:db_type(LServer, ?MODULE), Data, []). get_data(LUser, LServer,
gen_mod:db_type(LServer, ?MODULE), Data, []).
get_data(_LUser, _LServer, _DBType, [], Storage_Xmlels) -> get_data(_LUser, _LServer, _DBType, [],
Storage_Xmlels) ->
lists:reverse(Storage_Xmlels); lists:reverse(Storage_Xmlels);
get_data(LUser, LServer, mnesia, [{XmlNS, Xmlel} | Data], Storage_Xmlels) -> get_data(LUser, LServer, mnesia,
case mnesia:dirty_read(private_storage, {LUser, LServer, XmlNS}) of [{XmlNS, Xmlel} | Data], Storage_Xmlels) ->
case mnesia:dirty_read(private_storage,
{LUser, LServer, XmlNS})
of
[#private_storage{xml = Storage_Xmlel}] -> [#private_storage{xml = Storage_Xmlel}] ->
get_data(LUser, LServer, mnesia, Data, get_data(LUser, LServer, mnesia, Data,
[Storage_Xmlel | Storage_Xmlels]); [Storage_Xmlel | Storage_Xmlels]);
@ -172,86 +170,109 @@ get_data(LUser, LServer, mnesia, [{XmlNS, Xmlel} | Data], Storage_Xmlels) ->
get_data(LUser, LServer, mnesia, Data, get_data(LUser, LServer, mnesia, Data,
[Xmlel | Storage_Xmlels]) [Xmlel | Storage_Xmlels])
end; end;
get_data(LUser, LServer, odbc, [{XMLNS, El} | Els], Res) -> get_data(LUser, LServer, odbc, [{XMLNS, El} | Els],
Res) ->
Username = ejabberd_odbc:escape(LUser), Username = ejabberd_odbc:escape(LUser),
LXMLNS = ejabberd_odbc:escape(XMLNS), LXMLNS = ejabberd_odbc:escape(XMLNS),
case catch odbc_queries:get_private_data(LServer, Username, LXMLNS) of case catch odbc_queries:get_private_data(LServer,
{selected, ["data"], [{SData}]} -> Username, LXMLNS)
of
{selected, [<<"data">>], [[SData]]} ->
case xml_stream:parse_element(SData) of case xml_stream:parse_element(SData) of
Data when element(1, Data) == xmlelement -> Data when is_record(Data, xmlel) ->
get_data(LUser, LServer, odbc, Els, [Data | Res]) get_data(LUser, LServer, odbc, Els, [Data | Res])
end; end;
%% MREMOND: I wonder when the query could return a vcard ? %% MREMOND: I wonder when the query could return a vcard ?
{selected, ["vcard"], []} -> {selected, [<<"vcard">>], []} ->
get_data(LUser, LServer, odbc, Els, [El | Res]); get_data(LUser, LServer, odbc, Els, [El | Res]);
_ -> get_data(LUser, LServer, odbc, Els, [El | Res])
end.
get_data(LUser, LServer) ->
get_all_data(LUser, LServer,
gen_mod:db_type(LServer, ?MODULE)).
get_all_data(LUser, LServer, mnesia) ->
lists:flatten(
mnesia:dirty_select(private_storage,
[{#private_storage{usns = {LUser, LServer, '_'},
xml = '$1'},
[], ['$1']}]));
get_all_data(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
case catch odbc_queries:get_private_data(LServer, Username) of
{selected, [<<"namespace">>, <<"data">>], Res} ->
lists:flatmap(
fun([_, SData]) ->
case xml_stream:parse_element(SData) of
#xmlel{} = El ->
[El];
_ -> _ ->
get_data(LUser, LServer, odbc, Els, [El | Res]) []
end
end, Res);
_ ->
[]
end. end.
remove_user(User, Server) -> remove_user(User, Server) ->
LUser = jlib:nodeprep(User), LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
remove_user(LUser, LServer, gen_mod:db_type(Server, ?MODULE)). remove_user(LUser, LServer,
gen_mod:db_type(Server, ?MODULE)).
remove_user(LUser, LServer, mnesia) -> remove_user(LUser, LServer, mnesia) ->
F = fun () -> F = fun () ->
Namespaces = mnesia:select( Namespaces = mnesia:select(private_storage,
private_storage, [{#private_storage{usns =
[{#private_storage{usns={LUser, LServer, '$1'}, {LUser,
LServer,
'$1'},
_ = '_'}, _ = '_'},
[], [], ['$$']}]),
['$$']}]), lists:foreach(fun ([Namespace]) ->
lists:foreach(
fun([Namespace]) ->
mnesia:delete({private_storage, mnesia:delete({private_storage,
{LUser, LServer, Namespace}}) {LUser, LServer,
end, Namespaces) Namespace}})
end,
Namespaces)
end, end,
mnesia:transaction(F); mnesia:transaction(F);
remove_user(LUser, LServer, odbc) -> remove_user(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser), Username = ejabberd_odbc:escape(LUser),
odbc_queries:del_user_private_storage(LServer, Username). odbc_queries:del_user_private_storage(LServer,
Username).
update_table() -> update_table() ->
Fields = record_info(fields, private_storage), Fields = record_info(fields, private_storage),
case mnesia:table_info(private_storage, attributes) of case mnesia:table_info(private_storage, attributes) of
Fields -> Fields ->
ok; ejabberd_config:convert_table_to_binary(
[userns, xml] -> private_storage, Fields, set,
?INFO_MSG("Converting private_storage table from " fun(#private_storage{usns = {U, _, _}}) -> U end,
"{user, default, lists} format", []), fun(#private_storage{usns = {U, S, NS}, xml = El} = R) ->
Host = ?MYNAME, R#private_storage{usns = {iolist_to_binary(U),
{atomic, ok} = mnesia:create_table( iolist_to_binary(S),
mod_private_tmp_table, iolist_to_binary(NS)},
[{disc_only_copies, [node()]}, xml = xml:to_xmlel(El)}
{type, bag}, end);
{local_content, true},
{record_name, private_storage},
{attributes, record_info(fields, private_storage)}]),
mnesia:transform_table(private_storage, ignore, Fields),
F1 = fun() ->
mnesia:write_lock_table(mod_private_tmp_table),
mnesia:foldl(
fun(#private_storage{usns = {U, NS}} = R, _) ->
mnesia:dirty_write(
mod_private_tmp_table,
R#private_storage{usns = {U, Host, NS}})
end, ok, private_storage)
end,
mnesia:transaction(F1),
mnesia:clear_table(private_storage),
F2 = fun() ->
mnesia:write_lock_table(private_storage),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, mod_private_tmp_table)
end,
mnesia:transaction(F2),
mnesia:delete_table(mod_private_tmp_table);
_ -> _ ->
?INFO_MSG("Recreating private_storage table", []), ?INFO_MSG("Recreating private_storage table", []),
mnesia:transform_table(private_storage, ignore, Fields) mnesia:transform_table(private_storage, ignore, Fields)
end. end.
export(_Server) ->
[{private_storage,
fun(Host, #private_storage{usns = {LUser, LServer, XMLNS},
xml = Data})
when LServer == Host ->
Username = ejabberd_odbc:escape(LUser),
LXMLNS = ejabberd_odbc:escape(XMLNS),
SData =
ejabberd_odbc:escape(xml:element_to_binary(Data)),
odbc_queries:set_private_data_sql(Username, LXMLNS,
SData);
(_Host, _R) ->
[]
end}].

View File

@ -14,7 +14,7 @@ EFLAGS += -pz ..
# make debug=true to compile Erlang module with debug informations. # make debug=true to compile Erlang module with debug informations.
ifdef debug ifdef debug
EFLAGS+=+debug_info +export_all EFLAGS+=+debug_info
endif endif
OUTDIR = .. OUTDIR = ..

View File

@ -25,9 +25,11 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(mod_proxy65). -module(mod_proxy65).
-author('xram@jabber.ru'). -author('xram@jabber.ru').
-behaviour(gen_mod). -behaviour(gen_mod).
-behaviour(supervisor). -behaviour(supervisor).
%% gen_mod callbacks. %% gen_mod callbacks.
@ -43,14 +45,11 @@
start(Host, Opts) -> start(Host, Opts) ->
case mod_proxy65_service:add_listener(Host, Opts) of case mod_proxy65_service:add_listener(Host, Opts) of
{error, _} = Err -> {error, _} = Err -> erlang:error(Err);
erlang:error(Err);
_ -> _ ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME), Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec = { ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
Proc, {?MODULE, start_link, [Host, Opts]}, transient, infinity, supervisor, [?MODULE]},
transient, infinity, supervisor, [?MODULE]
},
supervisor:start_child(ejabberd_sup, ChildSpec) supervisor:start_child(ejabberd_sup, ChildSpec)
end. end.
@ -62,20 +61,22 @@ stop(Host) ->
start_link(Host, Opts) -> start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME), Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
supervisor:start_link({local, Proc}, ?MODULE, [Host, Opts]). supervisor:start_link({local, Proc}, ?MODULE,
[Host, Opts]).
init([Host, Opts]) -> init([Host, Opts]) ->
Service = Service = {mod_proxy65_service,
{mod_proxy65_service, {mod_proxy65_service, start_link, [Host, Opts]}, {mod_proxy65_service, start_link, [Host, Opts]},
transient, 5000, worker, [mod_proxy65_service]}, transient, 5000, worker, [mod_proxy65_service]},
StreamSupervisor = StreamSupervisor = {ejabberd_mod_proxy65_sup,
{ejabberd_mod_proxy65_sup,
{ejabberd_tmp_sup, start_link, {ejabberd_tmp_sup, start_link,
[gen_mod:get_module_proc(Host, ejabberd_mod_proxy65_sup), [gen_mod:get_module_proc(Host,
ejabberd_mod_proxy65_sup),
mod_proxy65_stream]}, mod_proxy65_stream]},
transient, infinity, supervisor, [ejabberd_tmp_sup]}, transient, infinity, supervisor, [ejabberd_tmp_sup]},
StreamManager = StreamManager = {mod_proxy65_sm,
{mod_proxy65_sm, {mod_proxy65_sm, start_link, [Host, Opts]}, {mod_proxy65_sm, start_link, [Host, Opts]}, transient,
transient, 5000, worker, [mod_proxy65_sm]}, 5000, worker, [mod_proxy65_sm]},
{ok, {{one_for_one, 10, 1}, {ok,
{{one_for_one, 10, 1},
[StreamManager, StreamSupervisor, Service]}}. [StreamManager, StreamSupervisor, Service]}}.

View File

@ -26,36 +26,49 @@
%% Authentication methods %% Authentication methods
-define(AUTH_ANONYMOUS, 0). -define(AUTH_ANONYMOUS, 0).
-define(AUTH_GSSAPI, 1). -define(AUTH_GSSAPI, 1).
-define(AUTH_PLAIN, 2). -define(AUTH_PLAIN, 2).
-define(AUTH_NO_METHODS, 16#FF).
%% Address Type %% Address Type
-define(AUTH_NO_METHODS, 255).
-define(ATYP_IPV4, 1). -define(ATYP_IPV4, 1).
-define(ATYP_DOMAINNAME, 3). -define(ATYP_DOMAINNAME, 3).
-define(ATYP_IPV6, 4). -define(ATYP_IPV6, 4).
%% Commands %% Commands
-define(CMD_CONNECT, 1). -define(CMD_CONNECT, 1).
-define(CMD_BIND, 2). -define(CMD_BIND, 2).
-define(CMD_UDP, 3). -define(CMD_UDP, 3).
%% RFC 1928 replies %% RFC 1928 replies
-define(SUCCESS, 0). -define(SUCCESS, 0).
-define(ERR_GENERAL_FAILURE, 1). -define(ERR_GENERAL_FAILURE, 1).
-define(ERR_NOT_ALLOWED, 2). -define(ERR_NOT_ALLOWED, 2).
-define(ERR_NETWORK_UNREACHABLE, 3). -define(ERR_NETWORK_UNREACHABLE, 3).
-define(ERR_HOST_UNREACHABLE, 4). -define(ERR_HOST_UNREACHABLE, 4).
-define(ERR_CONNECTION_REFUSED, 5). -define(ERR_CONNECTION_REFUSED, 5).
-define(ERR_TTL_EXPIRED, 6). -define(ERR_TTL_EXPIRED, 6).
-define(ERR_COMMAND_NOT_SUPPORTED, 7). -define(ERR_COMMAND_NOT_SUPPORTED, 7).
-define(ERR_ADDRESS_TYPE_NOT_SUPPORTED, 8). -define(ERR_ADDRESS_TYPE_NOT_SUPPORTED, 8).
%% RFC 1928 defined timeout. %% RFC 1928 defined timeout.
-define(SOCKS5_REPLY_TIMEOUT, 10000). -define(SOCKS5_REPLY_TIMEOUT, 10000).
-record(s5_request, { -record(s5_request, {rsv = 0 :: integer(),
rsv = 0, cmd = connect :: connect | udp,
cmd, sha1 = <<"">> :: binary()}).
sha1
}).

View File

@ -25,59 +25,49 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(mod_proxy65_lib). -module(mod_proxy65_lib).
-author('xram@jabber.ru'). -author('xram@jabber.ru').
-include("mod_proxy65.hrl"). -include("mod_proxy65.hrl").
-export([ -export([unpack_init_message/1, unpack_auth_request/1,
unpack_init_message/1, unpack_request/1, make_init_reply/1, make_auth_reply/1,
unpack_auth_request/1, make_reply/1, make_error_reply/1, make_error_reply/2]).
unpack_request/1,
make_init_reply/1,
make_auth_reply/1,
make_reply/1,
make_error_reply/1,
make_error_reply/2
]).
unpack_init_message(<<?VERSION_5, N, AuthMethodList:N/binary>>) unpack_init_message(<<(?VERSION_5), N,
AuthMethodList:N/binary>>)
when N > 0, N < 256 -> when N > 0, N < 256 ->
{ok, binary_to_list(AuthMethodList)}; {ok, binary_to_list(AuthMethodList)};
unpack_init_message(_) -> error.
unpack_init_message(_) -> unpack_auth_request(<<1, ULen, User:ULen/binary, PLen,
error. Pass:PLen/binary>>)
when ULen < 256, PLen < 256 ->
{(User), (Pass)};
unpack_auth_request(_) -> error.
unpack_auth_request(<<1, ULen, User:ULen/binary, unpack_request(<<(?VERSION_5), CMD, RSV,
PLen, Pass:PLen/binary>>) when ULen < 256, PLen < 256 -> (?ATYP_DOMAINNAME), 40, SHA1:40/binary, 0, 0>>)
{binary_to_list(User), binary_to_list(Pass)}; when CMD == (?CMD_CONNECT); CMD == (?CMD_UDP) ->
Command = if CMD == (?CMD_CONNECT) -> connect;
unpack_auth_request(_) -> CMD == (?CMD_UDP) -> udp
error.
unpack_request(<<?VERSION_5, CMD, RSV,
?ATYP_DOMAINNAME, 40,
SHA1:40/binary, 0, 0>>) when CMD == ?CMD_CONNECT;
CMD == ?CMD_UDP ->
Command = if
CMD == ?CMD_CONNECT -> connect;
CMD == ?CMD_UDP -> udp
end, end,
#s5_request{cmd = Command, rsv = RSV, sha1 = binary_to_list(SHA1)}; #s5_request{cmd = Command, rsv = RSV, sha1 = (SHA1)};
unpack_request(_) -> error.
unpack_request(_) -> make_init_reply(Method) -> [?VERSION_5, Method].
error.
make_init_reply(Method) ->
[?VERSION_5, Method].
make_auth_reply(true) -> [1, ?SUCCESS]; make_auth_reply(true) -> [1, ?SUCCESS];
make_auth_reply(false) -> [1, ?ERR_NOT_ALLOWED]. make_auth_reply(false) -> [1, ?ERR_NOT_ALLOWED].
make_reply(#s5_request{rsv = RSV, sha1 = SHA1}) -> make_reply(#s5_request{rsv = RSV, sha1 = SHA1}) ->
[?VERSION_5, ?SUCCESS, RSV, ?ATYP_DOMAINNAME, length(SHA1), SHA1, 0,0]. [?VERSION_5, ?SUCCESS, RSV, ?ATYP_DOMAINNAME,
byte_size(SHA1), SHA1, 0, 0].
make_error_reply(Request) -> make_error_reply(Request) ->
make_error_reply(Request, ?ERR_NOT_ALLOWED). make_error_reply(Request, ?ERR_NOT_ALLOWED).
make_error_reply(#s5_request{rsv = RSV, sha1 = SHA1}, Reason) -> make_error_reply(#s5_request{rsv = RSV, sha1 = SHA1},
[?VERSION_5, Reason, RSV, ?ATYP_DOMAINNAME, length(SHA1), SHA1, 0,0]. Reason) ->
[?VERSION_5, Reason, RSV, ?ATYP_DOMAINNAME,
byte_size(SHA1), SHA1, 0, 0].

View File

@ -25,37 +25,33 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(mod_proxy65_service). -module(mod_proxy65_service).
-author('xram@jabber.ru'). -author('xram@jabber.ru').
-behaviour(gen_server). -behaviour(gen_server).
%% gen_server callbacks. %% gen_server callbacks.
-export([init/1, -export([init/1, handle_info/2, handle_call/3,
handle_info/2, handle_cast/2, terminate/2, code_change/3]).
handle_call/3,
handle_cast/2,
terminate/2,
code_change/3
]).
%% API. %% API.
-export([start_link/2, add_listener/2, delete_listener/1]). -export([start_link/2, add_listener/2,
delete_listener/1]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl"). -include("jlib.hrl").
-define(PROCNAME, ejabberd_mod_proxy65_service). -define(PROCNAME, ejabberd_mod_proxy65_service).
-record(state, { -record(state,
myhost, {myhost = <<"">> :: binary(),
serverhost, serverhost = <<"">> :: binary(),
name, name = <<"">> :: binary(),
stream_addr, stream_addr = [] :: [attr()],
port, port = 0 :: inet:port_number(),
ip, ip = {127,0,0,1} :: inet:ip_address(),
acl acl = none :: atom()}).
}).
%%%------------------------ %%%------------------------
%%% gen_server callbacks %%% gen_server callbacks
@ -63,7 +59,8 @@
start_link(Host, Opts) -> start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME), Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). gen_server:start_link({local, Proc}, ?MODULE,
[Host, Opts], []).
init([Host, Opts]) -> init([Host, Opts]) ->
State = parse_options(Host, Opts), State = parse_options(Host, Opts),
@ -71,35 +68,35 @@ init([Host, Opts]) ->
{ok, State}. {ok, State}.
terminate(_Reason, #state{myhost = MyHost}) -> terminate(_Reason, #state{myhost = MyHost}) ->
ejabberd_router:unregister_route(MyHost), ejabberd_router:unregister_route(MyHost), ok.
ok.
handle_info({route, From, To, {xmlelement, "iq", _, _} = Packet}, State) -> handle_info({route, From, To,
#xmlel{name = <<"iq">>} = Packet},
State) ->
IQ = jlib:iq_query_info(Packet), IQ = jlib:iq_query_info(Packet),
case catch process_iq(From, IQ, State) of case catch process_iq(From, IQ, State) of
Result when is_record(Result, iq) -> Result when is_record(Result, iq) ->
ejabberd_router:route(To, From, jlib:iq_to_xml(Result)); ejabberd_router:route(To, From, jlib:iq_to_xml(Result));
{'EXIT', Reason} -> {'EXIT', Reason} ->
?ERROR_MSG("Error when processing IQ stanza: ~p", [Reason]), ?ERROR_MSG("Error when processing IQ stanza: ~p",
Err = jlib:make_error_reply(Packet, ?ERR_INTERNAL_SERVER_ERROR), [Reason]),
Err = jlib:make_error_reply(Packet,
?ERR_INTERNAL_SERVER_ERROR),
ejabberd_router:route(To, From, Err); ejabberd_router:route(To, From, Err);
_ -> _ -> ok
ok
end, end,
{noreply, State}; {noreply, State};
handle_info(_Info, State) -> handle_info(_Info, State) -> {noreply, State}.
{noreply, State}.
handle_call(get_port_ip, _From, State) -> handle_call(get_port_ip, _From, State) ->
{reply, {port_ip, State#state.port, State#state.ip}, State}; {reply, {port_ip, State#state.port, State#state.ip},
State};
handle_call(_Request, _From, State) -> handle_call(_Request, _From, State) ->
{reply, ok, State}. {reply, ok, State}.
handle_cast(_Request, State) -> handle_cast(_Request, State) -> {noreply, State}.
{noreply, State}.
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) -> {ok, State}.
{ok, State}.
%%%------------------------ %%%------------------------
%%% Listener management %%% Listener management
@ -108,72 +105,106 @@ code_change(_OldVsn, State, _Extra) ->
add_listener(Host, Opts) -> add_listener(Host, Opts) ->
State = parse_options(Host, Opts), State = parse_options(Host, Opts),
NewOpts = [Host | Opts], NewOpts = [Host | Opts],
ejabberd_listener:add_listener({State#state.port, State#state.ip}, mod_proxy65_stream, NewOpts). ejabberd_listener:add_listener({State#state.port,
State#state.ip},
mod_proxy65_stream, NewOpts).
delete_listener(Host) -> delete_listener(Host) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME), Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
{port_ip, Port, IP} = gen_server:call(Proc, get_port_ip), {port_ip, Port, IP} = gen_server:call(Proc,
catch ejabberd_listener:delete_listener({Port, IP}, mod_proxy65_stream). get_port_ip),
catch ejabberd_listener:delete_listener({Port, IP},
mod_proxy65_stream).
%%%------------------------ %%%------------------------
%%% IQ Processing %%% IQ Processing
%%%------------------------ %%%------------------------
%% disco#info request %% disco#info request
process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} = IQ, process_iq(_,
#iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} =
IQ,
#state{name = Name, serverhost = ServerHost}) -> #state{name = Name, serverhost = ServerHost}) ->
Info = ejabberd_hooks:run_fold( Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
disco_info, ServerHost, [], [ServerHost, ?MODULE, "", ""]), [], [ServerHost, ?MODULE, <<"">>, <<"">>]),
IQ#iq{type = result, sub_el = IQ#iq{type = result,
[{xmlelement, "query", [{"xmlns", ?NS_DISCO_INFO}], sub_el =
iq_disco_info(Lang, Name) ++ Info}]}; [#xmlel{name = <<"query">>,
attrs = [{<<"xmlns">>, ?NS_DISCO_INFO}],
children = iq_disco_info(Lang, Name) ++ Info}]};
%% disco#items request %% disco#items request
process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ, _) -> process_iq(_,
IQ#iq{type = result, sub_el = #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ, _) ->
[{xmlelement, "query", [{"xmlns", ?NS_DISCO_ITEMS}], []}]}; IQ#iq{type = result,
sub_el =
[#xmlel{name = <<"query">>,
attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}],
children = []}]};
%% vCard request %% vCard request
process_iq(_, #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} = IQ, _) -> process_iq(_,
IQ#iq{type = result, sub_el = #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} = IQ,
[{xmlelement, "vCard", [{"xmlns", ?NS_VCARD}], iq_vcard(Lang)}]}; _) ->
IQ#iq{type = result,
sub_el =
[#xmlel{name = <<"vCard">>,
attrs = [{<<"xmlns">>, ?NS_VCARD}],
children = iq_vcard(Lang)}]};
%% bytestreams info request %% bytestreams info request
process_iq(JID, #iq{type = get, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS} = IQ, process_iq(JID,
#state{acl = ACL, stream_addr = StreamAddr, serverhost = ServerHost}) -> #iq{type = get, sub_el = SubEl,
xmlns = ?NS_BYTESTREAMS} =
IQ,
#state{acl = ACL, stream_addr = StreamAddr,
serverhost = ServerHost}) ->
case acl:match_rule(ServerHost, ACL, JID) of case acl:match_rule(ServerHost, ACL, JID) of
allow -> allow ->
StreamHostEl = [{xmlelement, "streamhost", StreamAddr, []}], StreamHostEl = [#xmlel{name = <<"streamhost">>,
IQ#iq{type = result, sub_el = attrs = StreamAddr, children = []}],
[{xmlelement, "query", [{"xmlns", ?NS_BYTESTREAMS}], StreamHostEl}]}; IQ#iq{type = result,
sub_el =
[#xmlel{name = <<"query">>,
attrs = [{<<"xmlns">>, ?NS_BYTESTREAMS}],
children = StreamHostEl}]};
deny -> deny ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]} IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
end; end;
%% bytestream activation request %% bytestream activation request
process_iq(InitiatorJID, #iq{type = set, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS} = IQ, process_iq(InitiatorJID,
#iq{type = set, sub_el = SubEl,
xmlns = ?NS_BYTESTREAMS} =
IQ,
#state{acl = ACL, serverhost = ServerHost}) -> #state{acl = ACL, serverhost = ServerHost}) ->
case acl:match_rule(ServerHost, ACL, InitiatorJID) of case acl:match_rule(ServerHost, ACL, InitiatorJID) of
allow -> allow ->
ActivateEl = xml:get_path_s(SubEl, [{elem, "activate"}]), ActivateEl = xml:get_path_s(SubEl,
SID = xml:get_tag_attr_s("sid", SubEl), [{elem, <<"activate">>}]),
case catch jlib:string_to_jid(xml:get_tag_cdata(ActivateEl)) of SID = xml:get_tag_attr_s(<<"sid">>, SubEl),
TargetJID when is_record(TargetJID, jid), SID /= "", case catch
length(SID) =< 128, TargetJID /= InitiatorJID -> jlib:string_to_jid(xml:get_tag_cdata(ActivateEl))
Target = jlib:jid_to_string(jlib:jid_tolower(TargetJID)), of
Initiator = jlib:jid_to_string(jlib:jid_tolower(InitiatorJID)), TargetJID
SHA1 = sha:sha(SID ++ Initiator ++ Target), when is_record(TargetJID, jid), SID /= <<"">>,
case mod_proxy65_sm:activate_stream(SHA1, InitiatorJID, TargetJID, ServerHost) of byte_size(SID) =< 128, TargetJID /= InitiatorJID ->
ok -> Target =
IQ#iq{type = result, sub_el = []}; jlib:jid_to_string(jlib:jid_tolower(TargetJID)),
Initiator =
jlib:jid_to_string(jlib:jid_tolower(InitiatorJID)),
SHA1 = sha:sha(<<SID/binary, Initiator/binary, Target/binary>>),
case mod_proxy65_sm:activate_stream(SHA1, InitiatorJID,
TargetJID, ServerHost)
of
ok -> IQ#iq{type = result, sub_el = []};
false -> false ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]}; IQ#iq{type = error,
sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
limit -> limit ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_RESOURCE_CONSTRAINT]}; IQ#iq{type = error,
sub_el = [SubEl, ?ERR_RESOURCE_CONSTRAINT]};
conflict -> conflict ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_CONFLICT]}; IQ#iq{type = error, sub_el = [SubEl, ?ERR_CONFLICT]};
_ -> _ ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]} IQ#iq{type = error,
sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
end; end;
_ -> _ ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]} IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
@ -181,64 +212,70 @@ process_iq(InitiatorJID, #iq{type = set, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS
deny -> deny ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]} IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
end; end;
%% Unknown "set" or "get" request %% Unknown "set" or "get" request
process_iq(_, #iq{type=Type, sub_el=SubEl} = IQ, _) when Type==get; Type==set -> process_iq(_, #iq{type = Type, sub_el = SubEl} = IQ, _)
IQ#iq{type = error, sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}; when Type == get; Type == set ->
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
%% IQ "result" or "error". %% IQ "result" or "error".
process_iq(_, _, _) -> process_iq(_, _, _) -> ok.
ok.
%%%------------------------- %%%-------------------------
%%% Auxiliary functions. %%% Auxiliary functions.
%%%------------------------- %%%-------------------------
-define(FEATURE(Feat), {xmlelement,"feature",[{"var", Feat}],[]}). -define(FEATURE(Feat),
#xmlel{name = <<"feature">>,
attrs = [{<<"var">>, Feat}], children = []}).
iq_disco_info(Lang, Name) -> iq_disco_info(Lang, Name) ->
[{xmlelement, "identity", [#xmlel{name = <<"identity">>,
[{"category", "proxy"}, attrs =
{"type", "bytestreams"}, [{<<"category">>, <<"proxy">>},
{"name", translate:translate(Lang, Name)}], []}, {<<"type">>, <<"bytestreams">>},
?FEATURE(?NS_DISCO_INFO), {<<"name">>, translate:translate(Lang, Name)}],
?FEATURE(?NS_VCARD), children = []},
?FEATURE(?NS_BYTESTREAMS)]. ?FEATURE((?NS_DISCO_INFO)), ?FEATURE((?NS_VCARD)),
?FEATURE((?NS_BYTESTREAMS))].
iq_vcard(Lang) -> iq_vcard(Lang) ->
[{xmlelement, "FN", [], [#xmlel{name = <<"FN">>, attrs = [],
[{xmlcdata, "ejabberd/mod_proxy65"}]}, children = [{xmlcdata, <<"ejabberd/mod_proxy65">>}]},
{xmlelement, "URL", [], #xmlel{name = <<"URL">>, attrs = [],
[{xmlcdata, ?EJABBERD_URI}]}, children = [{xmlcdata, ?EJABBERD_URI}]},
{xmlelement, "DESC", [], #xmlel{name = <<"DESC">>, attrs = [],
[{xmlcdata, translate:translate(Lang, "ejabberd SOCKS5 Bytestreams module") ++ children =
"\nCopyright (c) 2003-2013 ProcessOne"}]}]. [{xmlcdata,
<<(translate:translate(Lang,
<<"ejabberd SOCKS5 Bytestreams module">>))/binary,
"\nCopyright (c) 2003-2013 ProcessOne">>}]}].
parse_options(ServerHost, Opts) -> parse_options(ServerHost, Opts) ->
MyHost = gen_mod:get_opt_host(ServerHost, Opts, "proxy.@HOST@"), MyHost = gen_mod:get_opt_host(ServerHost, Opts,
Port = gen_mod:get_opt(port, Opts, 7777), <<"proxy.@HOST@">>),
ACL = gen_mod:get_opt(access, Opts, all), Port = gen_mod:get_opt(port, Opts,
Name = gen_mod:get_opt(name, Opts, "SOCKS5 Bytestreams"), fun(P) when is_integer(P), P>0, P<65536 -> P end,
IP = case gen_mod:get_opt(ip, Opts, none) of 7777),
none -> get_my_ip(); ACL = gen_mod:get_opt(access, Opts, fun(A) when is_atom(A) -> A end,
Addr -> Addr all),
end, Name = gen_mod:get_opt(name, Opts, fun iolist_to_binary/1,
HostName = case gen_mod:get_opt(hostname, Opts, none) of <<"SOCKS5 Bytestreams">>),
none -> IP = gen_mod:get_opt(ip, Opts,
inet_parse:ntoa(IP); fun(Addr) ->
HostAddr when is_tuple(HostAddr) -> jlib:ip_to_list(Addr),
inet_parse:ntoa(HostAddr); Addr
HostNameStr -> end, get_my_ip()),
HostNameStr HostName = gen_mod:get_opt(hostname, Opts,
end, fun(Addr) when is_tuple(Addr) ->
StreamAddr = [{"jid", MyHost}, {"host", HostName}, jlib:ip_to_list(Addr);
{"port", integer_to_list(Port)}], (S) ->
#state{myhost = MyHost, iolist_to_binary(S)
serverhost = ServerHost, end, jlib:ip_to_list(IP)),
name = Name, StreamAddr = [{<<"jid">>, MyHost},
port = Port, {<<"host">>, HostName},
ip = IP, {<<"port">>, jlib:integer_to_binary(Port)}],
stream_addr = StreamAddr, #state{myhost = MyHost, serverhost = ServerHost,
acl = ACL}. name = Name, port = Port, ip = IP,
stream_addr = StreamAddr, acl = ACL}.
get_my_ip() -> get_my_ip() ->
{ok, MyHostName} = inet:gethostname(), {ok, MyHostName} = inet:gethostname(),

Some files were not shown because too many files have changed in this diff Show More