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:
parent
9c41abde10
commit
9deb294328
@ -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))
|
||||||
|
245
src/acl.erl
245
src/acl.erl
@ -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.
|
||||||
|
141
src/adhoc.erl
141
src/adhoc.erl
@ -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
|
||||||
|
}.
|
||||||
|
@ -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{}.
|
||||||
|
@ -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) ->
|
||||||
|
@ -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'
|
||||||
|
148
src/cyrsasl.erl
148
src/cyrsasl.erl
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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))).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -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]).
|
||||||
|
@ -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.
|
|
||||||
|
|
||||||
|
@ -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)).
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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).
|
||||||
|
@ -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.
|
||||||
|
@ -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).
|
||||||
|
@ -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}].
|
||||||
|
@ -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
|
|
||||||
}.
|
|
||||||
|
@ -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.
|
||||||
|
@ -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).
|
||||||
|
2256
src/ejabberd_c2s.erl
2256
src/ejabberd_c2s.erl
File diff suppressed because it is too large
Load Diff
@ -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).
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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]).
|
||||||
|
@ -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()}).
|
||||||
|
@ -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 ->
|
||||||
|
@ -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).
|
||||||
|
@ -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
|
||||||
|
@ -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}] ->
|
||||||
|
@ -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)).
|
||||||
|
@ -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.
|
||||||
|
@ -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
@ -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.
|
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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
@ -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.
|
||||||
|
@ -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 ->
|
||||||
|
@ -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
|
||||||
%%====================================================================
|
%%====================================================================
|
||||||
|
%====================================================================
|
||||||
|
@ -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,
|
||||||
|
@ -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).
|
||||||
|
@ -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]}]}}.
|
||||||
|
@ -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() ->
|
||||||
|
@ -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
|
||||||
|
@ -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).
|
||||||
|
@ -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
|
||||||
|
582
src/ejd2odbc.erl
582
src/ejd2odbc.erl
@ -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.
|
||||||
|
@ -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
@ -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{}.
|
||||||
|
@ -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))).
|
||||||
|
@ -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).
|
||||||
|
@ -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>>).
|
||||||
|
@ -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)]).
|
||||||
|
@ -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);
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
235
src/gen_mod.erl
235
src/gen_mod.erl
@ -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}).
|
||||||
|
|
||||||
|
184
src/idna.erl
184
src/idna.erl
@ -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.
|
||||||
|
152
src/jd2ejd.erl
152
src/jd2ejd.erl
@ -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).
|
||||||
|
960
src/jlib.erl
960
src/jlib.erl
File diff suppressed because it is too large
Load Diff
809
src/jlib.hrl
809
src/jlib.hrl
@ -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{}.
|
||||||
|
@ -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
@ -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.
|
||||||
|
628
src/mod_caps.erl
628
src/mod_caps.erl
@ -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
@ -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}.
|
|
||||||
|
|
||||||
|
@ -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).
|
||||||
).
|
|
||||||
|
@ -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]).
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
269
src/mod_last.erl
269
src/mod_last.erl
@ -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}].
|
||||||
|
@ -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
@ -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
161
src/mod_ping.erl
161
src/mod_ping.erl
@ -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.
|
||||||
|
@ -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).
|
|
||||||
|
1131
src/mod_privacy.erl
1131
src/mod_privacy.erl
File diff suppressed because it is too large
Load Diff
@ -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]).
|
||||||
|
@ -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}].
|
||||||
|
@ -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 = ..
|
||||||
|
@ -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]}}.
|
||||||
|
@ -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
|
|
||||||
}).
|
|
||||||
|
@ -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].
|
||||||
|
@ -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
Loading…
Reference in New Issue
Block a user