mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-20 16:15:59 +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.
|
||||
ifdef debug
|
||||
EFLAGS+=+debug_info +export_all
|
||||
EFLAGS+=+debug_info
|
||||
endif
|
||||
|
||||
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@
|
||||
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_MISC = $(ERLBEHAVS) $(DEBUGTOOLS)
|
||||
SOURCES += $(filter-out $(SOURCES_MISC),$(SOURCES_ALL))
|
||||
|
253
src/acl.erl
253
src/acl.erl
@ -25,160 +25,181 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(acl).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/0,
|
||||
to_record/3,
|
||||
add/3,
|
||||
add_list/3,
|
||||
match_rule/3,
|
||||
% for debugging only
|
||||
match_acl/3]).
|
||||
-export([start/0, to_record/3, add/3, add_list/3,
|
||||
match_rule/3, match_acl/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
-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() ->
|
||||
mnesia:create_table(acl,
|
||||
[{disc_copies, [node()]},
|
||||
{type, bag},
|
||||
[{disc_copies, [node()]}, {type, bag},
|
||||
{attributes, record_info(fields, acl)}]),
|
||||
mnesia:add_table_copy(acl, node(), ram_copies),
|
||||
update_table(),
|
||||
ok.
|
||||
|
||||
-spec to_record(binary(), atom(), aclspec()) -> acl().
|
||||
|
||||
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) ->
|
||||
F = fun() ->
|
||||
F = fun () ->
|
||||
mnesia:write(#acl{aclname = {ACLName, Host},
|
||||
aclspec = normalize_spec(ACLSpec)})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
-spec add_list(binary(), [acl()], boolean()) -> false | ok.
|
||||
|
||||
add_list(Host, ACLs, Clear) ->
|
||||
F = fun() ->
|
||||
if
|
||||
Clear ->
|
||||
Ks = mnesia:select(
|
||||
acl, [{{acl, {'$1', Host}, '$2'}, [], ['$1']}]),
|
||||
lists:foreach(fun(K) ->
|
||||
mnesia:delete({acl, {K, Host}})
|
||||
end, Ks);
|
||||
true ->
|
||||
ok
|
||||
F = fun () ->
|
||||
if Clear ->
|
||||
Ks = mnesia:select(acl,
|
||||
[{{acl, {'$1', Host}, '$2'}, [],
|
||||
['$1']}]),
|
||||
lists:foreach(fun (K) -> mnesia:delete({acl, {K, Host}})
|
||||
end,
|
||||
lists:foreach(fun(ACL) ->
|
||||
Ks);
|
||||
true -> ok
|
||||
end,
|
||||
lists:foreach(fun (ACL) ->
|
||||
case ACL of
|
||||
#acl{aclname = ACLName,
|
||||
aclspec = ACLSpec} ->
|
||||
mnesia:write(
|
||||
#acl{aclname = {ACLName, Host},
|
||||
aclspec = normalize_spec(ACLSpec)})
|
||||
mnesia:write(#acl{aclname =
|
||||
{ACLName,
|
||||
Host},
|
||||
aclspec =
|
||||
normalize_spec(ACLSpec)})
|
||||
end
|
||||
end, ACLs)
|
||||
end,
|
||||
ACLs)
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, _} ->
|
||||
ok;
|
||||
_ ->
|
||||
false
|
||||
{atomic, _} -> ok;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
normalize(A) ->
|
||||
jlib:nodeprep(A).
|
||||
normalize_spec({A, B}) ->
|
||||
{A, normalize(B)};
|
||||
normalize(A) -> jlib:nodeprep(iolist_to_binary(A)).
|
||||
|
||||
normalize_spec({A, B}) -> {A, normalize(B)};
|
||||
normalize_spec({A, B, C}) ->
|
||||
{A, normalize(B), normalize(C)};
|
||||
normalize_spec(all) ->
|
||||
all;
|
||||
normalize_spec(none) ->
|
||||
none.
|
||||
|
||||
normalize_spec(all) -> all;
|
||||
normalize_spec(none) -> none.
|
||||
|
||||
-spec match_rule(global | binary(), atom(), jid() | ljid()) -> any().
|
||||
|
||||
match_rule(global, Rule, JID) ->
|
||||
case Rule of
|
||||
all -> allow;
|
||||
none -> deny;
|
||||
_ ->
|
||||
case ejabberd_config:get_global_option({access, Rule, global}) of
|
||||
undefined ->
|
||||
deny;
|
||||
GACLs ->
|
||||
match_acls(GACLs, JID, global)
|
||||
case ejabberd_config:get_global_option(
|
||||
{access, Rule, global}, fun(V) -> V end)
|
||||
of
|
||||
undefined -> deny;
|
||||
GACLs -> match_acls(GACLs, JID, global)
|
||||
end
|
||||
end;
|
||||
|
||||
match_rule(Host, Rule, JID) ->
|
||||
case Rule of
|
||||
all -> allow;
|
||||
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 ->
|
||||
case ejabberd_config:get_global_option({access, Rule, Host}) of
|
||||
undefined ->
|
||||
deny;
|
||||
ACLs ->
|
||||
match_acls(ACLs, JID, Host)
|
||||
case ejabberd_config:get_global_option(
|
||||
{access, Rule, Host}, fun(V) -> V end)
|
||||
of
|
||||
undefined -> deny;
|
||||
ACLs -> match_acls(ACLs, JID, Host)
|
||||
end;
|
||||
GACLs ->
|
||||
case ejabberd_config:get_global_option({access, Rule, Host}) of
|
||||
undefined ->
|
||||
match_acls(GACLs, JID, Host);
|
||||
case ejabberd_config:get_global_option(
|
||||
{access, Rule, Host}, fun(V) -> V end)
|
||||
of
|
||||
undefined -> match_acls(GACLs, JID, Host);
|
||||
ACLs ->
|
||||
case lists:reverse(GACLs) of
|
||||
[{allow, all} | Rest] ->
|
||||
match_acls(
|
||||
lists:reverse(Rest) ++ ACLs ++
|
||||
[{allow, all}],
|
||||
match_acls(lists:reverse(Rest) ++
|
||||
ACLs ++ [{allow, all}],
|
||||
JID, Host);
|
||||
_ ->
|
||||
match_acls(GACLs ++ ACLs, JID, Host)
|
||||
_ -> match_acls(GACLs ++ ACLs, JID, Host)
|
||||
end
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
match_acls([], _, _Host) ->
|
||||
deny;
|
||||
match_acls([], _, _Host) -> deny;
|
||||
match_acls([{Access, ACL} | ACLs], JID, Host) ->
|
||||
case match_acl(ACL, JID, Host) of
|
||||
true ->
|
||||
Access;
|
||||
_ ->
|
||||
match_acls(ACLs, JID, Host)
|
||||
true -> Access;
|
||||
_ -> match_acls(ACLs, JID, Host)
|
||||
end.
|
||||
|
||||
-spec match_acl(atom(), jid() | ljid(), binary()) -> boolean().
|
||||
|
||||
match_acl(ACL, JID, Host) ->
|
||||
case ACL of
|
||||
all -> true;
|
||||
none -> false;
|
||||
_ ->
|
||||
{User, Server, Resource} = jlib:jid_tolower(JID),
|
||||
lists:any(fun(#acl{aclspec = Spec}) ->
|
||||
lists:any(fun (#acl{aclspec = Spec}) ->
|
||||
case Spec of
|
||||
all ->
|
||||
true;
|
||||
all -> true;
|
||||
{user, U} ->
|
||||
(U == User)
|
||||
andalso
|
||||
((Host == Server) orelse
|
||||
((Host == global) andalso
|
||||
lists:member(Server, ?MYHOSTS)));
|
||||
{user, U, S} ->
|
||||
(U == User) andalso (S == Server);
|
||||
{server, S} ->
|
||||
S == Server;
|
||||
{resource, R} ->
|
||||
R == Resource;
|
||||
U == User andalso
|
||||
(Host == Server orelse
|
||||
Host == global andalso
|
||||
lists:member(Server, ?MYHOSTS));
|
||||
{user, U, S} -> U == User andalso S == Server;
|
||||
{server, S} -> S == Server;
|
||||
{resource, R} -> R == Resource;
|
||||
{user_regexp, UR} ->
|
||||
((Host == Server) orelse
|
||||
((Host == global) andalso
|
||||
lists:member(Server, ?MYHOSTS)))
|
||||
(Host == Server orelse
|
||||
Host == global andalso
|
||||
lists:member(Server, ?MYHOSTS))
|
||||
andalso is_regexp_match(User, UR);
|
||||
{shared_group, G} ->
|
||||
Mod = loaded_shared_roster_module(Host),
|
||||
@ -187,8 +208,7 @@ match_acl(ACL, JID, Host) ->
|
||||
Mod = loaded_shared_roster_module(H),
|
||||
Mod:is_user_in_group({User, Server}, G, H);
|
||||
{user_regexp, UR, S} ->
|
||||
(S == Server) andalso
|
||||
is_regexp_match(User, UR);
|
||||
S == Server andalso is_regexp_match(User, UR);
|
||||
{server_regexp, SR} ->
|
||||
is_regexp_match(Server, SR);
|
||||
{resource_regexp, RR} ->
|
||||
@ -197,25 +217,22 @@ match_acl(ACL, JID, Host) ->
|
||||
is_regexp_match(Server, SR) andalso
|
||||
is_regexp_match(User, UR);
|
||||
{user_glob, UR} ->
|
||||
((Host == Server) orelse
|
||||
((Host == global) andalso
|
||||
lists:member(Server, ?MYHOSTS)))
|
||||
andalso
|
||||
is_glob_match(User, UR);
|
||||
(Host == Server orelse
|
||||
Host == global andalso
|
||||
lists:member(Server, ?MYHOSTS))
|
||||
andalso is_glob_match(User, UR);
|
||||
{user_glob, UR, S} ->
|
||||
(S == Server) andalso
|
||||
is_glob_match(User, UR);
|
||||
{server_glob, SR} ->
|
||||
is_glob_match(Server, SR);
|
||||
S == Server andalso is_glob_match(User, UR);
|
||||
{server_glob, SR} -> is_glob_match(Server, SR);
|
||||
{resource_glob, RR} ->
|
||||
is_glob_match(Resource, RR);
|
||||
{node_glob, UR, SR} ->
|
||||
is_glob_match(Server, SR) andalso
|
||||
is_glob_match(User, UR);
|
||||
WrongSpec ->
|
||||
?ERROR_MSG(
|
||||
"Wrong ACL expression: ~p~n"
|
||||
"Check your config file and reload it with the override_acls option enabled",
|
||||
?ERROR_MSG("Wrong ACL expression: ~p~nCheck your "
|
||||
"config file and reload it with the override_a"
|
||||
"cls option enabled",
|
||||
[WrongSpec]),
|
||||
false
|
||||
end
|
||||
@ -226,24 +243,46 @@ match_acl(ACL, JID, Host) ->
|
||||
|
||||
is_regexp_match(String, RegExp) ->
|
||||
case ejabberd_regexp:run(String, RegExp) of
|
||||
nomatch ->
|
||||
false;
|
||||
match ->
|
||||
true;
|
||||
nomatch -> false;
|
||||
match -> true;
|
||||
{error, ErrDesc} ->
|
||||
?ERROR_MSG(
|
||||
"Wrong regexp ~p in ACL: ~p",
|
||||
?ERROR_MSG("Wrong regexp ~p in ACL: ~p",
|
||||
[RegExp, ErrDesc]),
|
||||
false
|
||||
end.
|
||||
|
||||
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) ->
|
||||
case gen_mod:is_loaded(Host, mod_shared_roster_ldap) of
|
||||
true ->
|
||||
mod_shared_roster_ldap;
|
||||
false ->
|
||||
mod_shared_roster
|
||||
true -> mod_shared_roster_ldap;
|
||||
false -> mod_shared_roster
|
||||
end.
|
||||
|
||||
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.
|
||||
|
141
src/adhoc.erl
141
src/adhoc.erl
@ -25,11 +25,14 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(adhoc).
|
||||
|
||||
-author('henoch@dtek.chalmers.se').
|
||||
|
||||
-export([parse_request/1,
|
||||
-export([
|
||||
parse_request/1,
|
||||
produce_response/2,
|
||||
produce_response/1]).
|
||||
produce_response/1
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
@ -37,93 +40,121 @@
|
||||
|
||||
%% Parse an ad-hoc request. Return either an adhoc_request record or
|
||||
%% 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}) ->
|
||||
?DEBUG("entering parse_request...", []),
|
||||
Node = xml:get_tag_attr_s("node", SubEl),
|
||||
SessionID = xml:get_tag_attr_s("sessionid", SubEl),
|
||||
Action = xml:get_tag_attr_s("action", SubEl),
|
||||
Node = xml:get_tag_attr_s(<<"node">>, SubEl),
|
||||
SessionID = xml:get_tag_attr_s(<<"sessionid">>, SubEl),
|
||||
Action = xml:get_tag_attr_s(<<"action">>, SubEl),
|
||||
XData = find_xdata_el(SubEl),
|
||||
{xmlelement, _, _, AllEls} = SubEl,
|
||||
#xmlel{children = AllEls} = SubEl,
|
||||
Others = case XData of
|
||||
false ->
|
||||
AllEls;
|
||||
_ ->
|
||||
lists:delete(XData, AllEls)
|
||||
false -> AllEls;
|
||||
_ -> lists:delete(XData, AllEls)
|
||||
end,
|
||||
|
||||
#adhoc_request{lang = Lang,
|
||||
#adhoc_request{
|
||||
lang = Lang,
|
||||
node = Node,
|
||||
sessionid = SessionID,
|
||||
action = Action,
|
||||
xdata = XData,
|
||||
others = Others};
|
||||
parse_request(_) ->
|
||||
{error, ?ERR_BAD_REQUEST}.
|
||||
others = Others
|
||||
};
|
||||
parse_request(_) -> {error, ?ERR_BAD_REQUEST}.
|
||||
|
||||
%% 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([]) ->
|
||||
false;
|
||||
find_xdata_el1([{xmlelement, Name, Attrs, SubEls} | Els]) ->
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
?NS_XDATA ->
|
||||
{xmlelement, Name, Attrs, SubEls};
|
||||
_ ->
|
||||
find_xdata_el1(Els)
|
||||
find_xdata_el1([]) -> false;
|
||||
find_xdata_el1([El | Els]) when is_record(El, xmlel) ->
|
||||
case xml:get_tag_attr_s(<<"xmlns">>, El) of
|
||||
?NS_XDATA -> El;
|
||||
_ -> find_xdata_el1(Els)
|
||||
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
|
||||
%% record, filling in values for language, node and session id from
|
||||
%% the request.
|
||||
produce_response(#adhoc_request{lang = Lang,
|
||||
node = Node,
|
||||
sessionid = SessionID},
|
||||
Response) ->
|
||||
produce_response(Response#adhoc_response{lang = Lang,
|
||||
node = Node,
|
||||
sessionid = SessionID}).
|
||||
%%
|
||||
-spec(produce_response/2 ::
|
||||
(
|
||||
Adhoc_Request :: adhoc_request(),
|
||||
Adhoc_Response :: adhoc_response())
|
||||
-> Xmlel::xmlel()
|
||||
).
|
||||
|
||||
%% Produce a <command/> node to use as response from an adhoc_response
|
||||
%% 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,
|
||||
sessionid = ProvidedSessionID,
|
||||
status = Status,
|
||||
defaultaction = DefaultAction,
|
||||
actions = Actions,
|
||||
notes = Notes,
|
||||
elements = Elements}) ->
|
||||
SessionID = if is_list(ProvidedSessionID), ProvidedSessionID /= "" ->
|
||||
ProvidedSessionID;
|
||||
true ->
|
||||
jlib:now_to_utc_string(now())
|
||||
elements = Elements
|
||||
}) ->
|
||||
SessionID = if is_binary(ProvidedSessionID),
|
||||
ProvidedSessionID /= <<"">> -> ProvidedSessionID;
|
||||
true -> jlib:now_to_utc_string(now())
|
||||
end,
|
||||
case Actions of
|
||||
[] ->
|
||||
ActionsEls = [];
|
||||
_ ->
|
||||
case DefaultAction of
|
||||
"" ->
|
||||
ActionsElAttrs = [];
|
||||
_ ->
|
||||
ActionsElAttrs = [{"execute", DefaultAction}]
|
||||
<<"">> -> ActionsElAttrs = [];
|
||||
_ -> ActionsElAttrs = [{<<"execute">>, DefaultAction}]
|
||||
end,
|
||||
ActionsEls = [{xmlelement, "actions",
|
||||
ActionsElAttrs,
|
||||
[{xmlelement, Action, [], []} || Action <- Actions]}]
|
||||
ActionsEls = [
|
||||
#xmlel{
|
||||
name = <<"actions">>,
|
||||
attrs = ActionsElAttrs,
|
||||
children = [
|
||||
#xmlel{name = Action, attrs = [], children = []}
|
||||
|| Action <- Actions]
|
||||
}
|
||||
]
|
||||
end,
|
||||
NotesEls = lists:map(fun({Type, Text}) ->
|
||||
{xmlelement, "note",
|
||||
[{"type", Type}],
|
||||
[{xmlcdata, Text}]}
|
||||
#xmlel{
|
||||
name = <<"note">>,
|
||||
attrs = [{<<"type">>, Type}],
|
||||
children = [{xmlcdata, Text}]
|
||||
}
|
||||
end, Notes),
|
||||
{xmlelement, "command",
|
||||
[{"xmlns", ?NS_COMMANDS},
|
||||
{"sessionid", SessionID},
|
||||
{"node", Node},
|
||||
{"status", atom_to_list(Status)}],
|
||||
ActionsEls ++ NotesEls ++ Elements}.
|
||||
#xmlel{
|
||||
name = <<"command">>,
|
||||
attrs = [
|
||||
{<<"xmlns">>, ?NS_COMMANDS},
|
||||
{<<"sessionid">>, SessionID},
|
||||
{<<"node">>, Node},
|
||||
{<<"status">>, iolist_to_binary(atom_to_list(Status))}
|
||||
],
|
||||
children = ActionsEls ++ NotesEls ++ Elements
|
||||
}.
|
||||
|
@ -19,18 +19,27 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-record(adhoc_request, {lang,
|
||||
node,
|
||||
sessionid,
|
||||
action,
|
||||
xdata,
|
||||
others}).
|
||||
-record(adhoc_request,
|
||||
{
|
||||
lang = <<"">> :: binary(),
|
||||
node = <<"">> :: binary(),
|
||||
sessionid = <<"">> :: binary(),
|
||||
action = <<"">> :: binary(),
|
||||
xdata = false :: false | xmlel(),
|
||||
others = [] :: [xmlel()]
|
||||
}).
|
||||
|
||||
-record(adhoc_response, {lang,
|
||||
node,
|
||||
sessionid,
|
||||
status,
|
||||
defaultaction = "",
|
||||
actions = [],
|
||||
notes = [],
|
||||
elements = []}).
|
||||
-record(adhoc_response,
|
||||
{
|
||||
lang = <<"">> :: binary(),
|
||||
node = <<"">> :: binary(),
|
||||
sessionid = <<"">> :: binary(),
|
||||
status :: atom(),
|
||||
defaultaction = <<"">> :: binary(),
|
||||
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}.
|
||||
|
||||
get_proc_num() ->
|
||||
case erlang:system_info(logical_processors) of
|
||||
unknown ->
|
||||
1;
|
||||
Num ->
|
||||
Num
|
||||
case catch erlang:system_info(logical_processors) of
|
||||
Num when is_integer(Num) ->
|
||||
Num;
|
||||
_ ->
|
||||
1
|
||||
end.
|
||||
|
||||
get_proc_by_hash(Tab, Term) ->
|
||||
|
@ -62,7 +62,7 @@ start() ->
|
||||
RootDirS = "ERLANG_DIR = " ++ code:root_dir() ++ "\n",
|
||||
%% Load the ejabberd application description so that ?VERSION can read the vsn key
|
||||
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",
|
||||
OpenSSLDir = "OPENSSL_DIR = c:\\sdk\\OpenSSL\n",
|
||||
DBType = "DBTYPE = generic\n", %% 'generic' or 'mssql'
|
||||
|
152
src/cyrsasl.erl
152
src/cyrsasl.erl
@ -25,32 +25,57 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(cyrsasl).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/0,
|
||||
register_mechanism/3,
|
||||
listmech/1,
|
||||
server_new/7,
|
||||
server_start/3,
|
||||
server_step/2]).
|
||||
-export([start/0, register_mechanism/3, listmech/1,
|
||||
server_new/7, server_start/3, server_step/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-record(sasl_mechanism, {mechanism, module, password_type}).
|
||||
-record(sasl_state, {service, myname, realm,
|
||||
get_password, check_password, check_password_digest,
|
||||
mech_mod, mech_state}).
|
||||
%%
|
||||
-export_type([
|
||||
mechanism/0,
|
||||
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) ->
|
||||
[{mech_new, 4}, {mech_step, 2}];
|
||||
behaviour_info(_Other) ->
|
||||
undefined.
|
||||
-type(mechanism() :: binary()).
|
||||
-type(mechanisms() :: [mechanism(),...]).
|
||||
-type(password_type() :: plain | digest | scram).
|
||||
-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() ->
|
||||
ets:new(sasl_mechanism, [named_table,
|
||||
public,
|
||||
ets:new(sasl_mechanism,
|
||||
[named_table, public,
|
||||
{keypos, #sasl_mechanism.mechanism}]),
|
||||
cyrsasl_plain:start([]),
|
||||
cyrsasl_digest:start([]),
|
||||
@ -58,10 +83,18 @@ start() ->
|
||||
cyrsasl_anonymous:start([]),
|
||||
ok.
|
||||
|
||||
%%
|
||||
-spec(register_mechanism/3 ::
|
||||
(
|
||||
Mechanim :: mechanism(),
|
||||
Module :: module(),
|
||||
PasswordType :: password_type())
|
||||
-> any()
|
||||
).
|
||||
|
||||
register_mechanism(Mechanism, Module, PasswordType) ->
|
||||
ets:insert(sasl_mechanism,
|
||||
#sasl_mechanism{mechanism = Mechanism,
|
||||
module = Module,
|
||||
#sasl_mechanism{mechanism = Mechanism, module = Module,
|
||||
password_type = PasswordType}).
|
||||
|
||||
%%% TODO: use callbacks
|
||||
@ -89,62 +122,60 @@ register_mechanism(Mechanism, Module, PasswordType) ->
|
||||
%% end.
|
||||
|
||||
check_credentials(_State, Props) ->
|
||||
User = xml:get_attr_s(username, Props),
|
||||
User = proplists:get_value(username, Props, <<>>),
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
{error, "not-authorized"};
|
||||
"" ->
|
||||
{error, "not-authorized"};
|
||||
_LUser ->
|
||||
ok
|
||||
error -> {error, <<"not-authorized">>};
|
||||
<<"">> -> {error, <<"not-authorized">>};
|
||||
_LUser -> ok
|
||||
end.
|
||||
|
||||
-spec(listmech/1 ::
|
||||
(
|
||||
Host ::binary())
|
||||
-> Mechanisms::mechanisms()
|
||||
).
|
||||
|
||||
listmech(Host) ->
|
||||
Mechs = ets:select(sasl_mechanism,
|
||||
[{#sasl_mechanism{mechanism = '$1',
|
||||
password_type = '$2',
|
||||
_ = '_'},
|
||||
password_type = '$2', _ = '_'},
|
||||
case catch ejabberd_auth:store_type(Host) of
|
||||
external ->
|
||||
[{'==', '$2', plain}];
|
||||
scram ->
|
||||
[{'/=', '$2', digest}];
|
||||
{'EXIT',{undef,[{Module,store_type,[]} | _]}} ->
|
||||
?WARNING_MSG("~p doesn't implement the function store_type/0", [Module]),
|
||||
external -> [{'==', '$2', plain}];
|
||||
scram -> [{'/=', '$2', digest}];
|
||||
{'EXIT', {undef, [{Module, store_type, []} | _]}} ->
|
||||
?WARNING_MSG("~p doesn't implement the function store_type/0",
|
||||
[Module]),
|
||||
[];
|
||||
_Else ->
|
||||
[]
|
||||
_Else -> []
|
||||
end,
|
||||
['$1']}]),
|
||||
filter_anonymous(Host, Mechs).
|
||||
|
||||
server_new(Service, ServerFQDN, UserRealm, _SecFlags,
|
||||
GetPassword, CheckPassword, CheckPasswordDigest) ->
|
||||
#sasl_state{service = Service,
|
||||
myname = ServerFQDN,
|
||||
realm = UserRealm,
|
||||
get_password = GetPassword,
|
||||
#sasl_state{service = Service, myname = ServerFQDN,
|
||||
realm = UserRealm, get_password = GetPassword,
|
||||
check_password = CheckPassword,
|
||||
check_password_digest= CheckPasswordDigest}.
|
||||
check_password_digest = CheckPasswordDigest}.
|
||||
|
||||
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 ->
|
||||
case ets:lookup(sasl_mechanism, Mech) of
|
||||
[#sasl_mechanism{module = Module}] ->
|
||||
{ok, MechState} = Module:mech_new(
|
||||
State#sasl_state.myname,
|
||||
{ok, MechState} =
|
||||
Module:mech_new(State#sasl_state.myname,
|
||||
State#sasl_state.get_password,
|
||||
State#sasl_state.check_password,
|
||||
State#sasl_state.check_password_digest),
|
||||
server_step(State#sasl_state{mech_mod = Module,
|
||||
mech_state = MechState},
|
||||
ClientIn);
|
||||
_ ->
|
||||
{error, "no-mechanism"}
|
||||
_ -> {error, <<"no-mechanism">>}
|
||||
end;
|
||||
false ->
|
||||
{error, "no-mechanism"}
|
||||
false -> {error, <<"no-mechanism">>}
|
||||
end.
|
||||
|
||||
server_step(State, ClientIn) ->
|
||||
@ -153,21 +184,16 @@ server_step(State, ClientIn) ->
|
||||
case Module:mech_step(MechState, ClientIn) of
|
||||
{ok, Props} ->
|
||||
case check_credentials(State, Props) of
|
||||
ok ->
|
||||
{ok, Props};
|
||||
{error, Error} ->
|
||||
{error, Error}
|
||||
ok -> {ok, Props};
|
||||
{error, Error} -> {error, Error}
|
||||
end;
|
||||
{ok, Props, ServerOut} ->
|
||||
case check_credentials(State, Props) of
|
||||
ok ->
|
||||
{ok, Props, ServerOut};
|
||||
{error, Error} ->
|
||||
{error, Error}
|
||||
ok -> {ok, Props, ServerOut};
|
||||
{error, Error} -> {error, Error}
|
||||
end;
|
||||
{continue, ServerOut, NewMechState} ->
|
||||
{continue, ServerOut,
|
||||
State#sasl_state{mech_state = NewMechState}};
|
||||
{continue, ServerOut, State#sasl_state{mech_state = NewMechState}};
|
||||
{error, Error, Username} ->
|
||||
{error, Error, Username};
|
||||
{error, Error} ->
|
||||
@ -176,8 +202,16 @@ server_step(State, ClientIn) ->
|
||||
|
||||
%% Remove the anonymous mechanism from the list if not enabled for the given
|
||||
%% host
|
||||
%%
|
||||
-spec(filter_anonymous/2 ::
|
||||
(
|
||||
Host :: binary(),
|
||||
Mechs :: mechanisms())
|
||||
-> mechanisms()
|
||||
).
|
||||
|
||||
filter_anonymous(Host, Mechs) ->
|
||||
case ejabberd_auth_anonymous:is_sasl_anonymous_enabled(Host) of
|
||||
true -> Mechs;
|
||||
false -> Mechs -- ["ANONYMOUS"]
|
||||
false -> Mechs -- [<<"ANONYMOUS">>]
|
||||
end.
|
||||
|
@ -31,26 +31,20 @@
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-record(state, {server}).
|
||||
-record(state, {server = <<"">> :: binary()}).
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism("ANONYMOUS", ?MODULE, plain),
|
||||
cyrsasl:register_mechanism(<<"ANONYMOUS">>, ?MODULE, plain),
|
||||
ok.
|
||||
|
||||
stop() ->
|
||||
ok.
|
||||
stop() -> ok.
|
||||
|
||||
mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
|
||||
{ok, #state{server = Host}}.
|
||||
|
||||
mech_step(State, _ClientIn) ->
|
||||
%% We generate a random username:
|
||||
User = lists:concat([randoms:get_string() | tuple_to_list(now())]),
|
||||
Server = State#state.server,
|
||||
|
||||
%% Checks that the username is available
|
||||
mech_step(#state{server = Server}, _ClientIn) ->
|
||||
User = iolist_to_binary([randoms:get_string() | tuple_to_list(now())]),
|
||||
case ejabberd_auth:is_user_exists(User, Server) of
|
||||
true -> {error, "not-authorized"};
|
||||
false -> {ok, [{username, User},
|
||||
{auth_module, ejabberd_auth_anonymous}]}
|
||||
true -> {error, <<"not-authorized">>};
|
||||
false -> {ok, [{username, User}, {auth_module, ejabberd_auth_anonymous}]}
|
||||
end.
|
||||
|
@ -25,134 +25,145 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(cyrsasl_digest).
|
||||
|
||||
-author('alexey@sevcom.net').
|
||||
|
||||
-export([start/1,
|
||||
stop/0,
|
||||
mech_new/4,
|
||||
mech_step/2]).
|
||||
-export([start/1, stop/0, mech_new/4, mech_step/2, parse/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-record(state, {step, nonce, username, authzid, get_password, check_password, auth_module,
|
||||
host, hostfqdn}).
|
||||
-type get_password_fun() :: fun((binary()) -> {false, any()} |
|
||||
{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) ->
|
||||
Fqdn = get_local_fqdn(),
|
||||
?INFO_MSG("FQDN used to check DIGEST-MD5 SASL authentication: ~p", [Fqdn]),
|
||||
cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE, digest).
|
||||
?INFO_MSG("FQDN used to check DIGEST-MD5 SASL authentication: ~s",
|
||||
[Fqdn]),
|
||||
cyrsasl:register_mechanism(<<"DIGEST-MD5">>, ?MODULE,
|
||||
digest).
|
||||
|
||||
stop() ->
|
||||
ok.
|
||||
stop() -> ok.
|
||||
|
||||
mech_new(Host, GetPassword, _CheckPassword, CheckPasswordDigest) ->
|
||||
{ok, #state{step = 1,
|
||||
nonce = randoms:get_string(),
|
||||
host = Host,
|
||||
hostfqdn = get_local_fqdn(),
|
||||
mech_new(Host, GetPassword, _CheckPassword,
|
||||
CheckPasswordDigest) ->
|
||||
{ok,
|
||||
#state{step = 1, nonce = randoms:get_string(),
|
||||
host = Host, hostfqdn = get_local_fqdn(),
|
||||
get_password = GetPassword,
|
||||
check_password = CheckPasswordDigest}}.
|
||||
|
||||
mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
|
||||
{continue,
|
||||
"nonce=\"" ++ Nonce ++
|
||||
"\",qop=\"auth\",charset=utf-8,algorithm=md5-sess",
|
||||
<<"nonce=\"", Nonce/binary,
|
||||
"\",qop=\"auth\",charset=utf-8,algorithm=md5-sess">>,
|
||||
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
|
||||
bad ->
|
||||
{error, "bad-protocol"};
|
||||
bad -> {error, <<"bad-protocol">>};
|
||||
KeyVals ->
|
||||
DigestURI = xml:get_attr_s("digest-uri", KeyVals),
|
||||
UserName = xml:get_attr_s("username", KeyVals),
|
||||
case is_digesturi_valid(DigestURI, State#state.host, State#state.hostfqdn) of
|
||||
DigestURI = proplists:get_value(<<"digest-uri">>, KeyVals, <<>>),
|
||||
%DigestURI = xml:get_attr_s(<<"digest-uri">>, KeyVals),
|
||||
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 ->
|
||||
?DEBUG("User login not authorized because digest-uri "
|
||||
"seems invalid: ~p (checking for Host ~p, FQDN ~p)", [DigestURI,
|
||||
State#state.host, State#state.hostfqdn]),
|
||||
{error, "not-authorized", UserName};
|
||||
"seems invalid: ~p (checking for Host "
|
||||
"~p, FQDN ~p)",
|
||||
[DigestURI, State#state.host, State#state.hostfqdn]),
|
||||
{error, <<"not-authorized">>, UserName};
|
||||
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
|
||||
{false, _} ->
|
||||
{error, "not-authorized", UserName};
|
||||
{false, _} -> {error, <<"not-authorized">>, UserName};
|
||||
{Passwd, AuthModule} ->
|
||||
case (State#state.check_password)(UserName, "",
|
||||
xml:get_attr_s("response", KeyVals),
|
||||
fun(PW) -> response(KeyVals, UserName, PW, Nonce, AuthzId,
|
||||
"AUTHENTICATE") end) of
|
||||
case (State#state.check_password)(UserName, <<"">>,
|
||||
proplists:get_value(<<"response">>, KeyVals, <<>>),
|
||||
%xml:get_attr_s(<<"response">>, KeyVals),
|
||||
fun (PW) ->
|
||||
response(KeyVals,
|
||||
UserName,
|
||||
PW,
|
||||
Nonce,
|
||||
AuthzId,
|
||||
<<"AUTHENTICATE">>)
|
||||
end)
|
||||
of
|
||||
{true, _} ->
|
||||
RspAuth = response(KeyVals,
|
||||
UserName, Passwd,
|
||||
Nonce, AuthzId, ""),
|
||||
{continue,
|
||||
"rspauth=" ++ RspAuth,
|
||||
State#state{step = 5,
|
||||
auth_module = AuthModule,
|
||||
RspAuth = response(KeyVals, UserName, Passwd, Nonce,
|
||||
AuthzId, <<"">>),
|
||||
{continue, <<"rspauth=", RspAuth/binary>>,
|
||||
State#state{step = 5, auth_module = AuthModule,
|
||||
username = UserName,
|
||||
authzid = AuthzId}};
|
||||
false ->
|
||||
{error, "not-authorized", UserName};
|
||||
{false, _} ->
|
||||
{error, "not-authorized", UserName}
|
||||
false -> {error, <<"not-authorized">>, UserName};
|
||||
{false, _} -> {error, <<"not-authorized">>, UserName}
|
||||
end
|
||||
end
|
||||
end
|
||||
end;
|
||||
mech_step(#state{step = 5,
|
||||
auth_module = AuthModule,
|
||||
username = UserName,
|
||||
authzid = AuthzId}, "") ->
|
||||
{ok, [{username, UserName}, {authzid, AuthzId},
|
||||
mech_step(#state{step = 5, auth_module = AuthModule,
|
||||
username = UserName, authzid = AuthzId},
|
||||
<<"">>) ->
|
||||
{ok,
|
||||
[{username, UserName}, {authzid, AuthzId},
|
||||
{auth_module, AuthModule}]};
|
||||
mech_step(A, B) ->
|
||||
?DEBUG("SASL DIGEST: A ~p B ~p", [A,B]),
|
||||
{error, "bad-protocol"}.
|
||||
?DEBUG("SASL DIGEST: A ~p B ~p", [A, B]),
|
||||
{error, <<"bad-protocol">>}.
|
||||
|
||||
parse(S) ->
|
||||
parse1(S, "", []).
|
||||
parse(S) -> parse1(binary_to_list(S), "", []).
|
||||
|
||||
parse1([$= | Cs], S, Ts) ->
|
||||
parse2(Cs, lists:reverse(S), "", Ts);
|
||||
parse1([$, | Cs], [], Ts) ->
|
||||
parse1(Cs, [], Ts);
|
||||
parse1([$\s | Cs], [], Ts) ->
|
||||
parse1(Cs, [], Ts);
|
||||
parse1([C | Cs], S, Ts) ->
|
||||
parse1(Cs, [C | S], Ts);
|
||||
parse1([], [], T) ->
|
||||
lists:reverse(T);
|
||||
parse1([], _S, _T) ->
|
||||
bad.
|
||||
parse1([$, | Cs], [], Ts) -> parse1(Cs, [], Ts);
|
||||
parse1([$\s | Cs], [], Ts) -> parse1(Cs, [], Ts);
|
||||
parse1([C | Cs], S, Ts) -> 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);
|
||||
parse2([C | Cs], Key, Val, Ts) ->
|
||||
parse4(Cs, Key, [C | Val], Ts);
|
||||
parse2([], _, _, _) ->
|
||||
bad.
|
||||
parse2([], _, _, _) -> bad.
|
||||
|
||||
parse3([$\" | Cs], Key, Val, Ts) ->
|
||||
parse3([$" | Cs], Key, Val, Ts) ->
|
||||
parse4(Cs, Key, Val, Ts);
|
||||
parse3([$\\, C | Cs], Key, Val, Ts) ->
|
||||
parse3(Cs, Key, [C | Val], Ts);
|
||||
parse3([C | Cs], Key, Val, Ts) ->
|
||||
parse3(Cs, Key, [C | Val], Ts);
|
||||
parse3([], _, _, _) ->
|
||||
bad.
|
||||
parse3([], _, _, _) -> bad.
|
||||
|
||||
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(Cs, Key, Val, Ts);
|
||||
parse4([C | Cs], Key, Val, Ts) ->
|
||||
parse4(Cs, Key, [C | Val], Ts);
|
||||
parse4([], Key, Val, Ts) ->
|
||||
parse1([], "", [{Key, lists:reverse(Val)} | Ts]).
|
||||
|
||||
|
||||
%% @doc Check if the digest-uri is valid.
|
||||
%% RFC-2831 allows to provide the IP address in Host,
|
||||
%% 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/jabber.example.org
|
||||
%% 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),
|
||||
case catch string:tokens(DigestURI, "/") of
|
||||
["xmpp", Host] ->
|
||||
IsHostFqdn = is_host_fqdn(Host, JabberFQDN),
|
||||
case catch str:tokens(DigestURI, <<"/">>) of
|
||||
[<<"xmpp">>, Host] ->
|
||||
IsHostFqdn = is_host_fqdn(binary_to_list(Host), binary_to_list(JabberFQDN)),
|
||||
(Host == JabberDomain) or IsHostFqdn;
|
||||
["xmpp", Host, ServName] ->
|
||||
IsHostFqdn = is_host_fqdn(Host, JabberFQDN),
|
||||
[<<"xmpp">>, Host, ServName] ->
|
||||
IsHostFqdn = is_host_fqdn(binary_to_list(Host), binary_to_list(JabberFQDN)),
|
||||
(ServName == JabberDomain) and IsHostFqdn;
|
||||
_ ->
|
||||
false
|
||||
@ -185,62 +199,60 @@ is_host_fqdn(Host, [Fqdn | FqdnTail]) when Host /= Fqdn ->
|
||||
is_host_fqdn(Host, FqdnTail).
|
||||
|
||||
get_local_fqdn() ->
|
||||
case (catch get_local_fqdn2()) of
|
||||
Str when is_list(Str) -> Str;
|
||||
_ -> "unknown-fqdn, please configure fqdn option in ejabberd.cfg!"
|
||||
end.
|
||||
get_local_fqdn2() ->
|
||||
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
|
||||
case catch get_local_fqdn2() of
|
||||
Str when is_binary(Str) -> Str;
|
||||
_ ->
|
||||
<<"unknown-fqdn, please configure fqdn "
|
||||
"option in ejabberd.cfg!">>
|
||||
end.
|
||||
|
||||
digit_to_xchar(D) when (D >= 0) and (D < 10) ->
|
||||
D + 48;
|
||||
digit_to_xchar(D) ->
|
||||
D + 87.
|
||||
get_local_fqdn2() ->
|
||||
case ejabberd_config:get_local_option(
|
||||
fqdn, fun iolist_to_binary/1) of
|
||||
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, []).
|
||||
sha:to_hexlist(S).
|
||||
|
||||
hex([], Res) ->
|
||||
lists:reverse(Res);
|
||||
hex([N | Ns], Res) ->
|
||||
hex(Ns, [digit_to_xchar(N rem 16),
|
||||
digit_to_xchar(N div 16) | Res]).
|
||||
proplists_get_bin_value(Key, Pairs, Default) ->
|
||||
case proplists:get_value(Key, Pairs, Default) of
|
||||
L when is_list(L) ->
|
||||
list_to_binary(L);
|
||||
L2 ->
|
||||
L2
|
||||
end.
|
||||
|
||||
|
||||
response(KeyVals, User, Passwd, Nonce, AuthzId, A2Prefix) ->
|
||||
Realm = xml:get_attr_s("realm", KeyVals),
|
||||
CNonce = xml:get_attr_s("cnonce", KeyVals),
|
||||
DigestURI = xml:get_attr_s("digest-uri", KeyVals),
|
||||
NC = xml:get_attr_s("nc", KeyVals),
|
||||
QOP = xml:get_attr_s("qop", KeyVals),
|
||||
response(KeyVals, User, Passwd, Nonce, AuthzId,
|
||||
A2Prefix) ->
|
||||
Realm = proplists_get_bin_value(<<"realm">>, KeyVals, <<>>),
|
||||
CNonce = proplists_get_bin_value(<<"cnonce">>, KeyVals, <<>>),
|
||||
DigestURI = proplists_get_bin_value(<<"digest-uri">>, KeyVals, <<>>),
|
||||
NC = proplists_get_bin_value(<<"nc">>, KeyVals, <<>>),
|
||||
QOP = proplists_get_bin_value(<<"qop">>, KeyVals, <<>>),
|
||||
MD5Hash = crypto:md5(<<User/binary, ":", Realm/binary, ":",
|
||||
Passwd/binary>>),
|
||||
A1 = case AuthzId of
|
||||
"" ->
|
||||
binary_to_list(
|
||||
crypto:md5(User ++ ":" ++ Realm ++ ":" ++ Passwd)) ++
|
||||
":" ++ Nonce ++ ":" ++ CNonce;
|
||||
<<"">> ->
|
||||
<<MD5Hash/binary, ":", Nonce/binary, ":", CNonce/binary>>;
|
||||
_ ->
|
||||
binary_to_list(
|
||||
crypto:md5(User ++ ":" ++ Realm ++ ":" ++ Passwd)) ++
|
||||
":" ++ Nonce ++ ":" ++ CNonce ++ ":" ++ AuthzId
|
||||
<<MD5Hash/binary, ":", Nonce/binary, ":", CNonce/binary, ":",
|
||||
AuthzId/binary>>
|
||||
end,
|
||||
A2 = case QOP of
|
||||
"auth" ->
|
||||
A2Prefix ++ ":" ++ DigestURI;
|
||||
<<"auth">> ->
|
||||
<<A2Prefix/binary, ":", DigestURI/binary>>;
|
||||
_ ->
|
||||
A2Prefix ++ ":" ++ DigestURI ++
|
||||
":00000000000000000000000000000000"
|
||||
<<A2Prefix/binary, ":", DigestURI/binary,
|
||||
":00000000000000000000000000000000">>
|
||||
end,
|
||||
T = hex(binary_to_list(crypto:md5(A1))) ++ ":" ++ Nonce ++ ":" ++
|
||||
NC ++ ":" ++ CNonce ++ ":" ++ QOP ++ ":" ++
|
||||
hex(binary_to_list(crypto:md5(A2))),
|
||||
hex(binary_to_list(crypto:md5(T))).
|
||||
|
||||
|
||||
|
||||
T = <<(hex((crypto:md5(A1))))/binary, ":", Nonce/binary,
|
||||
":", NC/binary, ":", CNonce/binary, ":", QOP/binary,
|
||||
":", (hex((crypto:md5(A2))))/binary>>,
|
||||
hex((crypto:md5(T))).
|
||||
|
@ -25,6 +25,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(cyrsasl_plain).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/1, stop/0, mech_new/4, mech_step/2, parse/1]).
|
||||
@ -34,11 +35,10 @@
|
||||
-record(state, {check_password}).
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism("PLAIN", ?MODULE, plain),
|
||||
cyrsasl:register_mechanism(<<"PLAIN">>, ?MODULE, plain),
|
||||
ok.
|
||||
|
||||
stop() ->
|
||||
ok.
|
||||
stop() -> ok.
|
||||
|
||||
mech_new(_Host, _GetPassword, CheckPassword, _CheckPasswordDigest) ->
|
||||
{ok, #state{check_password = CheckPassword}}.
|
||||
@ -48,53 +48,43 @@ mech_step(State, ClientIn) ->
|
||||
[AuthzId, User, Password] ->
|
||||
case (State#state.check_password)(User, Password) of
|
||||
{true, AuthModule} ->
|
||||
{ok, [{username, User}, {authzid, AuthzId},
|
||||
{ok,
|
||||
[{username, User}, {authzid, AuthzId},
|
||||
{auth_module, AuthModule}]};
|
||||
_ ->
|
||||
{error, "not-authorized", User}
|
||||
_ -> {error, <<"not-authorized">>, User}
|
||||
end;
|
||||
_ ->
|
||||
{error, "bad-protocol"}
|
||||
_ -> {error, <<"bad-protocol">>}
|
||||
end.
|
||||
|
||||
prepare(ClientIn) ->
|
||||
case parse(ClientIn) of
|
||||
[[], UserMaybeDomain, Password] ->
|
||||
[<<"">>, UserMaybeDomain, Password] ->
|
||||
case parse_domain(UserMaybeDomain) of
|
||||
%% <NUL>login@domain<NUL>pwd
|
||||
[User, _Domain] ->
|
||||
[UserMaybeDomain, User, Password];
|
||||
[User, _Domain] -> [UserMaybeDomain, User, Password];
|
||||
%% <NUL>login<NUL>pwd
|
||||
[User] ->
|
||||
["", User, Password]
|
||||
[User] -> [<<"">>, User, Password]
|
||||
end;
|
||||
%% login@domain<NUL>login<NUL>pwd
|
||||
[AuthzId, User, Password] ->
|
||||
[AuthzId, User, Password];
|
||||
_ ->
|
||||
error
|
||||
[AuthzId, User, Password] -> [AuthzId, User, Password];
|
||||
_ -> error
|
||||
end.
|
||||
|
||||
|
||||
parse(S) ->
|
||||
parse1(S, "", []).
|
||||
parse(S) -> parse1(binary_to_list(S), "", []).
|
||||
|
||||
parse1([0 | Cs], S, T) ->
|
||||
parse1(Cs, "", [lists:reverse(S) | T]);
|
||||
parse1([C | Cs], S, T) ->
|
||||
parse1(Cs, [C | S], T);
|
||||
parse1(Cs, "", [list_to_binary(lists:reverse(S)) | T]);
|
||||
parse1([C | Cs], S, T) -> parse1(Cs, [C | S], T);
|
||||
%parse1([], [], T) ->
|
||||
% lists:reverse(T);
|
||||
parse1([], S, T) ->
|
||||
lists:reverse([lists:reverse(S) | T]).
|
||||
lists:reverse([list_to_binary(lists:reverse(S)) | T]).
|
||||
|
||||
|
||||
parse_domain(S) ->
|
||||
parse_domain1(S, "", []).
|
||||
parse_domain(S) -> parse_domain1(binary_to_list(S), "", []).
|
||||
|
||||
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(Cs, [C | 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).
|
||||
|
||||
-author('stephen.roettger@googlemail.com').
|
||||
|
||||
-export([start/1,
|
||||
stop/0,
|
||||
mech_new/4,
|
||||
mech_step/2]).
|
||||
-export([start/1, stop/0, mech_new/4, mech_step/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-record(state, {step, stored_key, server_key, username, get_password, check_password,
|
||||
auth_message, client_nonce, server_nonce}).
|
||||
-record(state,
|
||||
{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(NONCE_LENGTH, 16).
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism("SCRAM-SHA-1", ?MODULE, scram).
|
||||
cyrsasl:register_mechanism(<<"SCRAM-SHA-1">>, ?MODULE,
|
||||
scram).
|
||||
|
||||
stop() ->
|
||||
ok.
|
||||
stop() -> ok.
|
||||
|
||||
mech_new(_Host, GetPassword, _CheckPassword, _CheckPasswordDigest) ->
|
||||
mech_new(_Host, GetPassword, _CheckPassword,
|
||||
_CheckPasswordDigest) ->
|
||||
{ok, #state{step = 2, get_password = GetPassword}}.
|
||||
|
||||
mech_step(#state{step = 2} = State, ClientIn) ->
|
||||
case string:tokens(ClientIn, ",") of
|
||||
[CBind, UserNameAttribute, ClientNonceAttribute] when (CBind == "y") or (CBind == "n") ->
|
||||
case str:tokens(ClientIn, <<",">>) of
|
||||
[CBind, UserNameAttribute, ClientNonceAttribute]
|
||||
when (CBind == <<"y">>) or (CBind == <<"n">>) ->
|
||||
case parse_attribute(UserNameAttribute) of
|
||||
{error, Reason} ->
|
||||
{error, Reason};
|
||||
{error, Reason} -> {error, Reason};
|
||||
{_, EscapedUserName} ->
|
||||
case unescape_username(EscapedUserName) of
|
||||
error ->
|
||||
{error, "protocol-error-bad-username"};
|
||||
error -> {error, <<"protocol-error-bad-username">>};
|
||||
UserName ->
|
||||
case parse_attribute(ClientNonceAttribute) of
|
||||
{$r, ClientNonce} ->
|
||||
case (State#state.get_password)(UserName) of
|
||||
{false, _} ->
|
||||
{error, "not-authorized", UserName};
|
||||
{false, _} -> {error, <<"not-authorized">>, UserName};
|
||||
{Ret, _AuthModule} ->
|
||||
{StoredKey, ServerKey, Salt, IterationCount} = if
|
||||
is_tuple(Ret) ->
|
||||
Ret;
|
||||
{StoredKey, ServerKey, Salt, IterationCount} =
|
||||
if is_tuple(Ret) -> Ret;
|
||||
true ->
|
||||
TempSalt = crypto:rand_bytes(?SALT_LENGTH),
|
||||
SaltedPassword = scram:salted_password(Ret, TempSalt, ?SCRAM_DEFAULT_ITERATION_COUNT),
|
||||
TempSalt =
|
||||
crypto:rand_bytes(?SALT_LENGTH),
|
||||
SaltedPassword =
|
||||
scram:salted_password(Ret,
|
||||
TempSalt,
|
||||
?SCRAM_DEFAULT_ITERATION_COUNT),
|
||||
{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,
|
||||
ClientFirstMessageBare = string:substr(ClientIn, string:str(ClientIn, "n=")),
|
||||
ServerNonce = base64:encode_to_string(crypto:rand_bytes(?NONCE_LENGTH)),
|
||||
ServerFirstMessage = "r=" ++ ClientNonce ++ ServerNonce ++ "," ++
|
||||
"s=" ++ base64:encode_to_string(Salt) ++ "," ++
|
||||
"i=" ++ integer_to_list(IterationCount),
|
||||
{continue,
|
||||
ServerFirstMessage,
|
||||
State#state{step = 4, stored_key = StoredKey, server_key = ServerKey,
|
||||
auth_message = ClientFirstMessageBare ++ "," ++ ServerFirstMessage,
|
||||
client_nonce = ClientNonce, server_nonce = ServerNonce, username = UserName}}
|
||||
ClientFirstMessageBare =
|
||||
str:substr(ClientIn,
|
||||
str:str(ClientIn, <<"n=">>)),
|
||||
ServerNonce =
|
||||
jlib:encode_base64(crypto:rand_bytes(?NONCE_LENGTH)),
|
||||
ServerFirstMessage =
|
||||
iolist_to_binary(
|
||||
["r=",
|
||||
ClientNonce,
|
||||
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;
|
||||
_Else ->
|
||||
{error, "not-supported"}
|
||||
_Else -> {error, <<"not-supported">>}
|
||||
end
|
||||
end
|
||||
end;
|
||||
_Else ->
|
||||
{error, "bad-protocol"}
|
||||
_Else -> {error, <<"bad-protocol">>}
|
||||
end;
|
||||
mech_step(#state{step = 4} = State, ClientIn) ->
|
||||
case string:tokens(ClientIn, ",") of
|
||||
[GS2ChannelBindingAttribute, NonceAttribute, ClientProofAttribute] ->
|
||||
case str:tokens(ClientIn, <<",">>) of
|
||||
[GS2ChannelBindingAttribute, NonceAttribute,
|
||||
ClientProofAttribute] ->
|
||||
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
|
||||
%% 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
|
||||
{$r, CompareNonce} when CompareNonce == Nonce ->
|
||||
case parse_attribute(ClientProofAttribute) of
|
||||
{$p, ClientProofB64} ->
|
||||
ClientProof = base64:decode(ClientProofB64),
|
||||
AuthMessage = State#state.auth_message ++ "," ++ string:substr(ClientIn, 1, string:str(ClientIn, ",p=")-1),
|
||||
ClientSignature = scram:client_signature(State#state.stored_key, AuthMessage),
|
||||
ClientKey = scram:client_key(ClientProof, ClientSignature),
|
||||
ClientProof = jlib:decode_base64(ClientProofB64),
|
||||
AuthMessage =
|
||||
iolist_to_binary(
|
||||
[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),
|
||||
if CompareStoredKey == State#state.stored_key ->
|
||||
ServerSignature = scram:server_signature(State#state.server_key, AuthMessage),
|
||||
{ok, [{username, State#state.username}], "v=" ++ base64:encode_to_string(ServerSignature)};
|
||||
true ->
|
||||
{error, "bad-auth"}
|
||||
ServerSignature =
|
||||
scram:server_signature(State#state.server_key,
|
||||
AuthMessage),
|
||||
{ok, [{username, State#state.username}],
|
||||
<<"v=",
|
||||
(jlib:encode_base64(ServerSignature))/binary>>};
|
||||
true -> {error, <<"bad-auth">>}
|
||||
end;
|
||||
_Else ->
|
||||
{error, "bad-protocol"}
|
||||
_Else -> {error, <<"bad-protocol">>}
|
||||
end;
|
||||
{$r, _} ->
|
||||
{error, "bad-nonce"};
|
||||
_Else ->
|
||||
{error, "bad-protocol"}
|
||||
{$r, _} -> {error, <<"bad-nonce">>};
|
||||
_Else -> {error, <<"bad-protocol">>}
|
||||
end;
|
||||
_Else ->
|
||||
{error, "bad-protocol"}
|
||||
_Else -> {error, <<"bad-protocol">>}
|
||||
end;
|
||||
_Else ->
|
||||
{error, "bad-protocol"}
|
||||
_Else -> {error, <<"bad-protocol">>}
|
||||
end.
|
||||
|
||||
parse_attribute(Attribute) ->
|
||||
AttributeLen = string:len(Attribute),
|
||||
if
|
||||
AttributeLen >= 3 ->
|
||||
SecondChar = lists:nth(2, Attribute),
|
||||
case is_alpha(lists:nth(1, Attribute)) of
|
||||
AttributeLen = byte_size(Attribute),
|
||||
if AttributeLen >= 3 ->
|
||||
AttributeS = binary_to_list(Attribute),
|
||||
SecondChar = lists:nth(2, AttributeS),
|
||||
case is_alpha(lists:nth(1, AttributeS)) of
|
||||
true ->
|
||||
if
|
||||
SecondChar == $= ->
|
||||
String = string:substr(Attribute, 3),
|
||||
{lists:nth(1, Attribute), String};
|
||||
true ->
|
||||
{error, "bad-format second char not equal sign"}
|
||||
if SecondChar == $= ->
|
||||
String = str:substr(Attribute, 3),
|
||||
{lists:nth(1, AttributeS), String};
|
||||
true -> {error, <<"bad-format second char not equal sign">>}
|
||||
end;
|
||||
_Else ->
|
||||
{error, "bad-format first char not a letter"}
|
||||
_Else -> {error, <<"bad-format first char not a letter">>}
|
||||
end;
|
||||
true ->
|
||||
{error, "bad-format attribute too short"}
|
||||
true -> {error, <<"bad-format attribute too short">>}
|
||||
end.
|
||||
|
||||
unescape_username("") ->
|
||||
"";
|
||||
unescape_username(<<"">>) -> <<"">>;
|
||||
unescape_username(EscapedUsername) ->
|
||||
Pos = string:str(EscapedUsername, "="),
|
||||
if
|
||||
Pos == 0 ->
|
||||
EscapedUsername;
|
||||
Pos = str:str(EscapedUsername, <<"=">>),
|
||||
if Pos == 0 -> EscapedUsername;
|
||||
true ->
|
||||
Start = string:substr(EscapedUsername, 1, Pos-1),
|
||||
End = string:substr(EscapedUsername, Pos),
|
||||
EndLen = string:len(End),
|
||||
if
|
||||
EndLen < 3 ->
|
||||
error;
|
||||
Start = str:substr(EscapedUsername, 1, Pos - 1),
|
||||
End = str:substr(EscapedUsername, Pos),
|
||||
EndLen = byte_size(End),
|
||||
if EndLen < 3 -> error;
|
||||
true ->
|
||||
case string:substr(End, 1, 3) of
|
||||
"=2C" ->
|
||||
Start ++ "," ++ unescape_username(string:substr(End, 4));
|
||||
"=3D" ->
|
||||
Start ++ "=" ++ unescape_username(string:substr(End, 4));
|
||||
_Else ->
|
||||
error
|
||||
case str:substr(End, 1, 3) of
|
||||
<<"=2C">> ->
|
||||
<<Start/binary, ",",
|
||||
(unescape_username(str:substr(End, 4)))/binary>>;
|
||||
<<"=3D">> ->
|
||||
<<Start/binary, "=",
|
||||
(unescape_username(str:substr(End, 4)))/binary>>;
|
||||
_Else -> error
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
is_alpha(Char) when Char >= $a, Char =< $z ->
|
||||
true;
|
||||
is_alpha(Char) when Char >= $A, Char =< $Z ->
|
||||
true;
|
||||
is_alpha(_) ->
|
||||
false.
|
||||
|
||||
is_alpha(Char) when Char >= $a, Char =< $z -> true;
|
||||
is_alpha(Char) when Char >= $A, Char =< $Z -> true;
|
||||
is_alpha(_) -> false.
|
||||
|
@ -21,44 +21,57 @@
|
||||
|
||||
%% 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
|
||||
-define(VERSION, element(2, application:get_key(ejabberd,vsn))).
|
||||
-define(VERSION, ejabberd_config:get_version()).
|
||||
|
||||
-define(MYHOSTS, ejabberd_config:get_global_option(hosts)).
|
||||
-define(MYNAME, hd(ejabberd_config:get_global_option(hosts))).
|
||||
-define(MYLANG, ejabberd_config:get_global_option(language)).
|
||||
-define(MYHOSTS, ejabberd_config:get_myhosts()).
|
||||
|
||||
-define(MSGS_DIR, "msgs").
|
||||
-define(CONFIG_PATH, "ejabberd.cfg").
|
||||
-define(LOG_PATH, "ejabberd.log").
|
||||
-define(MYNAME, hd(ejabberd_config:get_myhosts())).
|
||||
|
||||
-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(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).
|
||||
|
||||
%% ---------------------------------
|
||||
%% Logging mechanism
|
||||
|
||||
%% Print in standard output
|
||||
-define(PRINT(Format, Args),
|
||||
io:format(Format, Args)).
|
||||
-define(PRINT(Format, Args), io:format(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),
|
||||
ejabberd_logger:info_msg(?MODULE,?LINE,Format, Args)).
|
||||
ejabberd_logger:info_msg(?MODULE, ?LINE, 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),
|
||||
ejabberd_logger:error_msg(?MODULE,?LINE,Format, Args)).
|
||||
ejabberd_logger:error_msg(?MODULE, ?LINE, 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],
|
||||
desc = "Register a user",
|
||||
module = ?MODULE, function = register,
|
||||
args = [{user, string}, {host, string}, {password, string}],
|
||||
args = [{user, binary}, {host, binary}, {password, binary}],
|
||||
result = {res, restuple}},
|
||||
#ejabberd_commands{name = unregister, tags = [accounts],
|
||||
desc = "Unregister a user",
|
||||
module = ?MODULE, function = unregister,
|
||||
args = [{user, string}, {host, string}],
|
||||
args = [{user, binary}, {host, binary}],
|
||||
result = {res, restuple}},
|
||||
#ejabberd_commands{name = registered_users, tags = [accounts],
|
||||
desc = "List all registered users in HOST",
|
||||
module = ?MODULE, function = registered_users,
|
||||
args = [{host, string}],
|
||||
args = [{host, binary}],
|
||||
result = {users, {list, {username, string}}}},
|
||||
#ejabberd_commands{name = registered_vhosts, tags = [server],
|
||||
desc = "List all registered vhosts in SERVER",
|
||||
@ -158,6 +158,11 @@ commands() ->
|
||||
module = ejabberd_piefxis, function = export_host,
|
||||
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],
|
||||
desc = "Delete expired offline messages from database",
|
||||
module = ?MODULE, function = delete_expired_messages,
|
||||
@ -296,11 +301,12 @@ stop_kindly(DelaySeconds, AnnouncementText) ->
|
||||
ok.
|
||||
|
||||
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(
|
||||
fun(ServerHost) ->
|
||||
MUCHost = gen_mod:get_module_opt_host(
|
||||
ServerHost, mod_muc, "conference.@HOST@"),
|
||||
ServerHost, mod_muc, <<"conference.@HOST@">>),
|
||||
mod_muc:broadcast_service_message(MUCHost, Message)
|
||||
end,
|
||||
?MYHOSTS).
|
||||
@ -320,6 +326,8 @@ update("all") ->
|
||||
update(ModStr) ->
|
||||
update_module(ModStr).
|
||||
|
||||
update_module(ModuleNameBin) when is_binary(ModuleNameBin) ->
|
||||
update_module(binary_to_list(ModuleNameBin));
|
||||
update_module(ModuleNameString) ->
|
||||
ModuleName = list_to_atom(ModuleNameString),
|
||||
case ejabberd_update:update([ModuleName]) of
|
||||
|
@ -57,8 +57,6 @@ start(normal, _Args) ->
|
||||
ejabberd_config:start(),
|
||||
ejabberd_check:config(),
|
||||
connect_nodes(),
|
||||
%% Loading ASN.1 driver explicitly to avoid races in LDAP
|
||||
catch asn1rt:load_driver(),
|
||||
Sup = ejabberd_sup:start_link(),
|
||||
ejabberd_rdbms:start(),
|
||||
ejabberd_auth:start(),
|
||||
@ -135,41 +133,48 @@ db_init() ->
|
||||
start_modules() ->
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
case ejabberd_config:get_local_option({modules, Host}) of
|
||||
undefined ->
|
||||
ok;
|
||||
Modules ->
|
||||
Modules = ejabberd_config:get_local_option(
|
||||
{modules, Host},
|
||||
fun(Mods) ->
|
||||
lists:map(
|
||||
fun({M, A}) when is_atom(M), is_list(A) ->
|
||||
{M, A}
|
||||
end, Mods)
|
||||
end, []),
|
||||
lists:foreach(
|
||||
fun({Module, Args}) ->
|
||||
gen_mod:start_module(Host, Module, Args)
|
||||
end, Modules)
|
||||
end
|
||||
end, ?MYHOSTS).
|
||||
|
||||
%% Stop all the modules in all the hosts
|
||||
stop_modules() ->
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
case ejabberd_config:get_local_option({modules, Host}) of
|
||||
undefined ->
|
||||
ok;
|
||||
Modules ->
|
||||
Modules = ejabberd_config:get_local_option(
|
||||
{modules, Host},
|
||||
fun(Mods) ->
|
||||
lists:map(
|
||||
fun({M, A}) when is_atom(M), is_list(A) ->
|
||||
{M, A}
|
||||
end, Mods)
|
||||
end, []),
|
||||
lists:foreach(
|
||||
fun({Module, _Args}) ->
|
||||
gen_mod:stop_module_keep_config(Host, Module)
|
||||
end, Modules)
|
||||
end
|
||||
end, ?MYHOSTS).
|
||||
|
||||
connect_nodes() ->
|
||||
case ejabberd_config:get_local_option(cluster_nodes) of
|
||||
undefined ->
|
||||
ok;
|
||||
Nodes when is_list(Nodes) ->
|
||||
Nodes = ejabberd_config:get_local_option(
|
||||
cluster_nodes,
|
||||
fun(Ns) ->
|
||||
true = lists:all(fun is_atom/1, Ns),
|
||||
Ns
|
||||
end, []),
|
||||
lists:foreach(fun(Node) ->
|
||||
net_kernel:connect_node(Node)
|
||||
end, Nodes)
|
||||
end.
|
||||
end, Nodes).
|
||||
|
||||
%% @spec () -> string()
|
||||
%% @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.
|
||||
|
||||
-module(ejabberd_auth).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
%% External exports
|
||||
-export([start/0,
|
||||
set_password/3,
|
||||
check_password/3,
|
||||
check_password/5,
|
||||
check_password_with_authmodule/3,
|
||||
check_password_with_authmodule/5,
|
||||
try_register/3,
|
||||
dirty_get_registered_users/0,
|
||||
get_vh_registered_users/1,
|
||||
get_vh_registered_users/2,
|
||||
-export([start/0, set_password/3, check_password/3,
|
||||
check_password/5, check_password_with_authmodule/3,
|
||||
check_password_with_authmodule/5, try_register/3,
|
||||
dirty_get_registered_users/0, get_vh_registered_users/1,
|
||||
get_vh_registered_users/2, export/1,
|
||||
get_vh_registered_users_number/1,
|
||||
get_vh_registered_users_number/2,
|
||||
get_password/2,
|
||||
get_password_s/2,
|
||||
get_password_with_authmodule/2,
|
||||
is_user_exists/2,
|
||||
is_user_exists_in_other_modules/3,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
plain_password_required/1,
|
||||
store_type/1,
|
||||
entropy/1
|
||||
]).
|
||||
get_vh_registered_users_number/2, get_password/2,
|
||||
get_password_s/2, get_password_with_authmodule/2,
|
||||
is_user_exists/2, 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]).
|
||||
|
||||
@ -61,42 +50,62 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start() ->
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
lists:foreach(
|
||||
fun(M) ->
|
||||
M:start(Host)
|
||||
end, auth_modules(Host))
|
||||
end, ?MYHOSTS).
|
||||
-type opts() :: [{prefix, binary()} | {from, integer()} |
|
||||
{to, integer()} | {limit, integer()} |
|
||||
{offset, integer()}].
|
||||
|
||||
-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
|
||||
lists:foreach(fun (Host) ->
|
||||
lists:foreach(fun (M) -> M:start(Host) end,
|
||||
auth_modules(Host))
|
||||
end,
|
||||
?MYHOSTS).
|
||||
|
||||
plain_password_required(Server) ->
|
||||
lists:any(
|
||||
fun(M) ->
|
||||
M:plain_password_required()
|
||||
end, auth_modules(Server)).
|
||||
lists:any(fun (M) -> M:plain_password_required() end,
|
||||
auth_modules(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.
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
%% 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) ->
|
||||
case check_password_with_authmodule(User, Server, Password) of
|
||||
case check_password_with_authmodule(User, Server,
|
||||
Password)
|
||||
of
|
||||
{true, _AuthModule} -> true;
|
||||
false -> false
|
||||
end.
|
||||
@ -105,9 +114,14 @@ check_password(User, Server, Password) ->
|
||||
%% @spec (User::string(), Server::string(), Password::string(),
|
||||
%% Digest::string(), DigestGen::function()) ->
|
||||
%% true | false
|
||||
check_password(User, Server, Password, Digest, DigestGen) ->
|
||||
case check_password_with_authmodule(User, Server, Password,
|
||||
Digest, DigestGen) of
|
||||
-spec check_password(binary(), binary(), binary(), binary(),
|
||||
fun((binary()) -> binary())) -> boolean().
|
||||
|
||||
check_password(User, Server, Password, Digest,
|
||||
DigestGen) ->
|
||||
case check_password_with_authmodule(User, Server,
|
||||
Password, Digest, DigestGen)
|
||||
of
|
||||
{true, _AuthModule} -> true;
|
||||
false -> false
|
||||
end.
|
||||
@ -122,55 +136,62 @@ check_password(User, Server, Password, Digest, DigestGen) ->
|
||||
%% AuthModule = ejabberd_auth_anonymous | ejabberd_auth_external
|
||||
%% | ejabberd_auth_internal | ejabberd_auth_ldap
|
||||
%% | ejabberd_auth_odbc | ejabberd_auth_pam
|
||||
check_password_with_authmodule(User, Server, Password) ->
|
||||
check_password_loop(auth_modules(Server), [User, Server, Password]).
|
||||
-spec check_password_with_authmodule(binary(), 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_with_authmodule(User, Server,
|
||||
Password) ->
|
||||
check_password_loop(auth_modules(Server),
|
||||
[User, Server, Password]).
|
||||
|
||||
check_password_loop([], _Args) ->
|
||||
false;
|
||||
-spec check_password_with_authmodule(binary(), binary(), binary(), binary(),
|
||||
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) ->
|
||||
case apply(AuthModule, check_password, Args) of
|
||||
true ->
|
||||
{true, AuthModule};
|
||||
false ->
|
||||
check_password_loop(AuthModules, Args)
|
||||
true -> {true, AuthModule};
|
||||
false -> check_password_loop(AuthModules, Args)
|
||||
end.
|
||||
|
||||
-spec set_password(binary(), binary(), binary()) -> ok |
|
||||
{error, atom()}.
|
||||
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
%% ok | {error, ErrorType}
|
||||
%% where ErrorType = empty_password | not_allowed | invalid_jid
|
||||
set_password(_User, _Server, "") ->
|
||||
%% We do not allow empty password
|
||||
set_password(_User, _Server, <<"">>) ->
|
||||
{error, empty_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}
|
||||
try_register(_User, _Server, "") ->
|
||||
%% We do not allow empty password
|
||||
lists:foldl(fun (M, {error, _}) ->
|
||||
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};
|
||||
try_register(User, Server, Password) ->
|
||||
case is_user_exists(User,Server) of
|
||||
true ->
|
||||
{atomic, exists};
|
||||
case is_user_exists(User, Server) of
|
||||
true -> {atomic, exists};
|
||||
false ->
|
||||
case lists:member(jlib:nameprep(Server), ?MYHOSTS) of
|
||||
true ->
|
||||
Res = lists:foldl(
|
||||
fun(_M, {atomic, ok} = Res) ->
|
||||
Res;
|
||||
Res = lists:foldl(fun (_M, {atomic, ok} = Res) -> Res;
|
||||
(M, _) ->
|
||||
M:try_register(User, Server, Password)
|
||||
end, {error, not_allowed}, auth_modules(Server)),
|
||||
end,
|
||||
{error, not_allowed}, auth_modules(Server)),
|
||||
case Res of
|
||||
{atomic, ok} ->
|
||||
ejabberd_hooks:run(register_user, Server,
|
||||
@ -178,143 +199,161 @@ try_register(User, Server, Password) ->
|
||||
{atomic, ok};
|
||||
_ -> Res
|
||||
end;
|
||||
false ->
|
||||
{error, not_allowed}
|
||||
false -> {error, not_allowed}
|
||||
end
|
||||
end.
|
||||
|
||||
%% Registered users list do not include anonymous users logged
|
||||
-spec dirty_get_registered_users() -> [{binary(), binary()}].
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
lists:flatmap(
|
||||
fun(M) ->
|
||||
M:dirty_get_registered_users()
|
||||
end, auth_modules()).
|
||||
lists:flatmap(fun (M) -> M:dirty_get_registered_users()
|
||||
end,
|
||||
auth_modules()).
|
||||
|
||||
-spec get_vh_registered_users(binary()) -> [{binary(), binary()}].
|
||||
|
||||
%% Registered users list do not include anonymous users logged
|
||||
get_vh_registered_users(Server) ->
|
||||
lists:flatmap(
|
||||
fun(M) ->
|
||||
lists:flatmap(fun (M) ->
|
||||
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) ->
|
||||
lists:flatmap(
|
||||
fun(M) ->
|
||||
case erlang:function_exported(
|
||||
M, get_vh_registered_users, 2) of
|
||||
true ->
|
||||
M:get_vh_registered_users(Server, Opts);
|
||||
false ->
|
||||
M:get_vh_registered_users(Server)
|
||||
lists:flatmap(fun (M) ->
|
||||
case erlang:function_exported(M,
|
||||
get_vh_registered_users,
|
||||
2)
|
||||
of
|
||||
true -> M:get_vh_registered_users(Server, Opts);
|
||||
false -> M:get_vh_registered_users(Server)
|
||||
end
|
||||
end, auth_modules(Server)).
|
||||
end,
|
||||
auth_modules(Server)).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
lists:sum(
|
||||
lists:map(
|
||||
fun(M) ->
|
||||
case erlang:function_exported(
|
||||
M, get_vh_registered_users_number, 1) of
|
||||
lists:sum(lists:map(fun (M) ->
|
||||
case erlang:function_exported(M,
|
||||
get_vh_registered_users_number,
|
||||
1)
|
||||
of
|
||||
true ->
|
||||
M:get_vh_registered_users_number(Server);
|
||||
false ->
|
||||
length(M:get_vh_registered_users(Server))
|
||||
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) ->
|
||||
lists:sum(
|
||||
lists:map(
|
||||
fun(M) ->
|
||||
case erlang:function_exported(
|
||||
M, get_vh_registered_users_number, 2) of
|
||||
%% @doc Get the password of the user.
|
||||
%% @spec (User::string(), Server::string()) -> Password::string()
|
||||
lists:sum(lists:map(fun (M) ->
|
||||
case erlang:function_exported(M,
|
||||
get_vh_registered_users_number,
|
||||
2)
|
||||
of
|
||||
true ->
|
||||
M:get_vh_registered_users_number(Server, Opts);
|
||||
M:get_vh_registered_users_number(Server,
|
||||
Opts);
|
||||
false ->
|
||||
length(M:get_vh_registered_users(Server))
|
||||
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) ->
|
||||
lists:foldl(
|
||||
fun(M, false) ->
|
||||
lists:foldl(fun (M, false) ->
|
||||
M:get_password(User, Server);
|
||||
(_M, Password) ->
|
||||
Password
|
||||
end, false, auth_modules(Server)).
|
||||
(_M, Password) -> Password
|
||||
end,
|
||||
false, auth_modules(Server)).
|
||||
|
||||
-spec get_password_s(binary(), binary()) -> binary().
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
case get_password(User, Server) of
|
||||
false ->
|
||||
"";
|
||||
Password when is_list(Password) ->
|
||||
Password;
|
||||
_ ->
|
||||
""
|
||||
false -> <<"">>;
|
||||
Password -> Password
|
||||
end.
|
||||
|
||||
%% @doc Get the password of the user and the auth module.
|
||||
%% @spec (User::string(), Server::string()) ->
|
||||
%% {Password::string(), AuthModule::atom()} | {false, none}
|
||||
get_password_with_authmodule(User, Server) ->
|
||||
lists:foldl(
|
||||
fun(M, {false, _}) ->
|
||||
{M:get_password(User, Server), M};
|
||||
(_M, {Password, AuthModule}) ->
|
||||
{Password, AuthModule}
|
||||
end, {false, none}, auth_modules(Server)).
|
||||
-spec get_password_with_authmodule(binary(), binary()) -> {false | binary(), atom()}.
|
||||
|
||||
get_password_with_authmodule(User, Server) ->
|
||||
%% Returns true if the user exists in the DB or if an anonymous user is logged
|
||||
%% under the given name
|
||||
is_user_exists(User, Server) ->
|
||||
lists:any(
|
||||
fun(M) ->
|
||||
case M:is_user_exists(User, Server) of
|
||||
{error, Error} ->
|
||||
?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)).
|
||||
lists:foldl(fun (M, {false, _}) ->
|
||||
{M:get_password(User, Server), M};
|
||||
(_M, {Password, AuthModule}) -> {Password, AuthModule}
|
||||
end,
|
||||
{false, none}, 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
|
||||
%% passed as parameter
|
||||
%% @spec (Module::atom(), User, Server) -> true | false | 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);
|
||||
lists:any(fun (M) ->
|
||||
case M:is_user_exists(User, Server) of
|
||||
{error, Error} ->
|
||||
?DEBUG("The authentication module ~p returned an error~nwhen "
|
||||
"checking user ~p in server ~p~nError message: ~p",
|
||||
?ERROR_MSG("The authentication module ~p returned "
|
||||
"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]),
|
||||
maybe
|
||||
end.
|
||||
|
||||
-spec remove_user(binary(), binary()) -> ok.
|
||||
|
||||
%% @spec (User, Server) -> ok
|
||||
%% @doc Remove user.
|
||||
%% Note: it may return ok even if there was some problem removing the user.
|
||||
remove_user(User, Server) ->
|
||||
lists:foreach(
|
||||
fun(M) ->
|
||||
M:remove_user(User, Server)
|
||||
end, auth_modules(Server)),
|
||||
ejabberd_hooks:run(remove_user, jlib:nameprep(Server), [User, Server]),
|
||||
lists:foreach(fun (M) -> M:remove_user(User, Server)
|
||||
end,
|
||||
auth_modules(Server)),
|
||||
ejabberd_hooks:run(remove_user, jlib:nameprep(Server),
|
||||
[User, Server]),
|
||||
ok.
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request | error
|
||||
@ -322,41 +361,49 @@ remove_user(User, Server) ->
|
||||
%% The removal is attempted in each auth method provided:
|
||||
%% when one returns 'ok' the loop stops;
|
||||
%% 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) ->
|
||||
R = lists:foldl(
|
||||
fun(_M, ok = Res) ->
|
||||
Res;
|
||||
(M, _) ->
|
||||
M:remove_user(User, Server, Password)
|
||||
end, error, auth_modules(Server)),
|
||||
R = lists:foldl(fun (_M, ok = Res) -> Res;
|
||||
(M, _) -> M:remove_user(User, Server, Password)
|
||||
end,
|
||||
error, auth_modules(Server)),
|
||||
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
|
||||
end,
|
||||
R.
|
||||
|
||||
%% @spec (IOList) -> non_negative_float()
|
||||
%% @doc Calculate informational entropy.
|
||||
entropy(IOList) ->
|
||||
case binary_to_list(iolist_to_binary(IOList)) of
|
||||
"" ->
|
||||
0.0;
|
||||
entropy(B) ->
|
||||
case binary_to_list(B) of
|
||||
"" -> 0.0;
|
||||
S ->
|
||||
Set = lists:foldl(
|
||||
fun(C, [Digit, Printable, LowLetter, HiLetter, Other]) ->
|
||||
Set = lists:foldl(fun (C,
|
||||
[Digit, Printable, LowLetter, HiLetter,
|
||||
Other]) ->
|
||||
if C >= $a, C =< $z ->
|
||||
[Digit, Printable, 26, HiLetter, Other];
|
||||
[Digit, Printable, 26, HiLetter,
|
||||
Other];
|
||||
C >= $0, C =< $9 ->
|
||||
[9, Printable, LowLetter, HiLetter, Other];
|
||||
[9, Printable, LowLetter, HiLetter,
|
||||
Other];
|
||||
C >= $A, C =< $Z ->
|
||||
[Digit, Printable, LowLetter, 26, Other];
|
||||
C >= 16#21, C =< 16#7e ->
|
||||
[Digit, 33, LowLetter, HiLetter, Other];
|
||||
[Digit, Printable, LowLetter, 26,
|
||||
Other];
|
||||
C >= 33, C =< 126 ->
|
||||
[Digit, 33, LowLetter, HiLetter,
|
||||
Other];
|
||||
true ->
|
||||
[Digit, Printable, LowLetter, HiLetter, 128]
|
||||
[Digit, Printable, LowLetter,
|
||||
HiLetter, 128]
|
||||
end
|
||||
end, [0, 0, 0, 0, 0], S),
|
||||
length(S) * math:log(lists:sum(Set))/math:log(2)
|
||||
end,
|
||||
[0, 0, 0, 0, 0], S),
|
||||
length(S) * math:log(lists:sum(Set)) / math:log(2)
|
||||
end.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
@ -365,19 +412,27 @@ entropy(IOList) ->
|
||||
%% Return the lists of all the auth modules actually used in the
|
||||
%% configuration
|
||||
auth_modules() ->
|
||||
lists:usort(
|
||||
lists:flatmap(
|
||||
fun(Server) ->
|
||||
lists:usort(lists:flatmap(fun (Server) ->
|
||||
auth_modules(Server)
|
||||
end, ?MYHOSTS)).
|
||||
end,
|
||||
?MYHOSTS)).
|
||||
|
||||
-spec auth_modules(binary()) -> [atom()].
|
||||
|
||||
%% Return the list of authenticated modules for a given host
|
||||
auth_modules(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
Method = ejabberd_config:get_local_option({auth_method, LServer}),
|
||||
Methods = if
|
||||
Method == undefined -> [];
|
||||
is_list(Method) -> Method;
|
||||
is_atom(Method) -> [Method]
|
||||
end,
|
||||
[list_to_atom("ejabberd_auth_" ++ atom_to_list(M)) || M <- Methods].
|
||||
Methods = ejabberd_config:get_local_option(
|
||||
{auth_method, LServer},
|
||||
fun(V) when is_list(V) ->
|
||||
true = lists:all(fun is_atom/1, V),
|
||||
V;
|
||||
(V) when is_atom(V) ->
|
||||
[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:
|
||||
-export([login/2,
|
||||
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/3,
|
||||
is_user_exists/2,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
store_type/0,
|
||||
-export([login/2, 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_s/2,
|
||||
get_password/2, get_password/3, is_user_exists/2,
|
||||
remove_user/2, remove_user/3, store_type/0,
|
||||
plain_password_required/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-record(anonymous, {us, sid}).
|
||||
|
||||
%% Create the anonymous table if at least one virtual host has anonymous features enabled
|
||||
%% Register to login / logout events
|
||||
-record(anonymous, {us = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||
sid = {now(), self()} :: ejabberd_sm:sid()}).
|
||||
|
||||
start(Host) ->
|
||||
%% TODO: Check cluster mode
|
||||
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
|
||||
%% defaults to login_anon
|
||||
anonymous_protocol(Host) ->
|
||||
case ejabberd_config:get_local_option({anonymous_protocol, Host}) of
|
||||
sasl_anon -> sasl_anon;
|
||||
login_anon -> login_anon;
|
||||
both -> both;
|
||||
_Other -> sasl_anon
|
||||
end.
|
||||
ejabberd_config:get_local_option(
|
||||
{anonymous_protocol, Host},
|
||||
fun(sasl_anon) -> sasl_anon;
|
||||
(login_anon) -> login_anon;
|
||||
(both) -> both
|
||||
end,
|
||||
sasl_anon).
|
||||
|
||||
%% Return true if multiple connections have been allowed in the config file
|
||||
%% defaults to false
|
||||
allow_multiple_connections(Host) ->
|
||||
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
|
||||
anonymous_user_exist(User, Server) ->
|
||||
@ -134,36 +134,39 @@ anonymous_user_exist(User, Server) ->
|
||||
%% Remove connection from Mnesia tables
|
||||
remove_connection(SID, LUser, LServer) ->
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
mnesia:delete_object({anonymous, US, SID})
|
||||
F = fun () -> mnesia:delete_object({anonymous, US, SID})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
%% Register connection
|
||||
register_connection(SID, #jid{luser = LUser, lserver = LServer}, Info) ->
|
||||
AuthModule = xml:get_attr_s(auth_module, Info),
|
||||
case AuthModule == ?MODULE of
|
||||
register_connection(SID,
|
||||
#jid{luser = LUser, lserver = LServer}, Info) ->
|
||||
AuthModule = list_to_atom(binary_to_list(xml:get_attr_s(<<"auth_module">>, Info))),
|
||||
case AuthModule == (?MODULE) of
|
||||
true ->
|
||||
ejabberd_hooks:run(register_user, LServer, [LUser, LServer]),
|
||||
ejabberd_hooks:run(register_user, LServer,
|
||||
[LUser, LServer]),
|
||||
US = {LUser, LServer},
|
||||
mnesia:sync_dirty(
|
||||
fun() -> mnesia:write(#anonymous{us = US, sid=SID})
|
||||
mnesia:sync_dirty(fun () ->
|
||||
mnesia:write(#anonymous{us = US,
|
||||
sid = SID})
|
||||
end);
|
||||
false ->
|
||||
ok
|
||||
false -> ok
|
||||
end.
|
||||
|
||||
%% Remove an anonymous user from the anonymous users table
|
||||
unregister_connection(SID, #jid{luser = LUser, lserver = LServer}, _) ->
|
||||
purge_hook(anonymous_user_exist(LUser, LServer),
|
||||
LUser, LServer),
|
||||
unregister_connection(SID,
|
||||
#jid{luser = LUser, lserver = LServer}, _) ->
|
||||
purge_hook(anonymous_user_exist(LUser, LServer), LUser,
|
||||
LServer),
|
||||
remove_connection(SID, LUser, LServer).
|
||||
|
||||
%% Launch the hook to purge user data only for anonymous users
|
||||
purge_hook(false, _LUser, _LServer) ->
|
||||
ok;
|
||||
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
|
||||
@ -172,12 +175,15 @@ purge_hook(true, LUser, LServer) ->
|
||||
%% When anonymous login is enabled, check the password for permenant users
|
||||
%% before allowing access
|
||||
check_password(User, Server, Password) ->
|
||||
check_password(User, Server, Password, undefined, undefined).
|
||||
check_password(User, Server, _Password, _Digest, _DigestGen) ->
|
||||
%% We refuse login for registered accounts (They cannot logged but
|
||||
%% they however are "reserved")
|
||||
case ejabberd_auth:is_user_exists_in_other_modules(?MODULE,
|
||||
User, Server) of
|
||||
check_password(User, Server, Password, undefined,
|
||||
undefined).
|
||||
|
||||
check_password(User, Server, _Password, _Digest,
|
||||
_DigestGen) ->
|
||||
case
|
||||
ejabberd_auth:is_user_exists_in_other_modules(?MODULE,
|
||||
User, Server)
|
||||
of
|
||||
%% If user exists in other module, reject anonnymous authentication
|
||||
true -> false;
|
||||
%% 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
|
||||
set_password(User, Server, _Password) ->
|
||||
case anonymous_user_exist(User, Server) of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
{error, not_allowed}
|
||||
true -> ok;
|
||||
false -> {error, not_allowed}
|
||||
end.
|
||||
|
||||
%% 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) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
[].
|
||||
dirty_get_registered_users() -> [].
|
||||
|
||||
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
|
||||
get_password(User, Server) ->
|
||||
get_password(User, Server, "").
|
||||
get_password(User, Server, <<"">>).
|
||||
|
||||
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
|
||||
true ->
|
||||
DefaultValue;
|
||||
true -> DefaultValue;
|
||||
%% We return the permanent user password otherwise
|
||||
false -> false
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
case get_password(User, Server) of
|
||||
false ->
|
||||
false
|
||||
<<"">>;
|
||||
Password ->
|
||||
Password
|
||||
end.
|
||||
|
||||
%% 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) ->
|
||||
anonymous_user_exist(User, Server).
|
||||
|
||||
remove_user(_User, _Server) ->
|
||||
{error, not_allowed}.
|
||||
remove_user(_User, _Server) -> {error, not_allowed}.
|
||||
|
||||
remove_user(_User, _Server, _Password) ->
|
||||
not_allowed.
|
||||
remove_user(_User, _Server, _Password) -> not_allowed.
|
||||
|
||||
plain_password_required() ->
|
||||
false.
|
||||
plain_password_required() -> false.
|
||||
|
||||
store_type() ->
|
||||
plain.
|
||||
|
@ -25,27 +25,21 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_auth_external).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(ejabberd_auth).
|
||||
|
||||
%% External exports
|
||||
-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,
|
||||
-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
|
||||
]).
|
||||
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]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
@ -53,55 +47,59 @@
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host) ->
|
||||
extauth:start(
|
||||
Host, ejabberd_config:get_local_option({extauth_program, Host})),
|
||||
Cmd = ejabberd_config:get_local_option(
|
||||
{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
|
||||
cache ->
|
||||
ok = ejabberd_auth_internal:start(Host);
|
||||
no_cache ->
|
||||
ok
|
||||
cache -> ok = ejabberd_auth_internal:start(Host);
|
||||
no_cache -> ok
|
||||
end.
|
||||
|
||||
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
|
||||
false -> no_cache;
|
||||
{true, _CacheTime} ->
|
||||
case get_mod_last_configured(Server) of
|
||||
no_mod_last ->
|
||||
?ERROR_MSG("In host ~p extauth is used, extauth_cache is enabled but "
|
||||
"mod_last is not enabled.", [Server]),
|
||||
?ERROR_MSG("In host ~p extauth is used, extauth_cache "
|
||||
"is enabled but mod_last is not enabled.",
|
||||
[Server]),
|
||||
no_cache;
|
||||
_ -> cache
|
||||
end
|
||||
end.
|
||||
|
||||
plain_password_required() ->
|
||||
true.
|
||||
plain_password_required() -> true.
|
||||
|
||||
store_type() ->
|
||||
external.
|
||||
store_type() -> external.
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
case get_cache_option(Server) of
|
||||
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.
|
||||
|
||||
check_password(User, Server, Password, _Digest, _DigestGen) ->
|
||||
check_password(User, Server, Password, _Digest,
|
||||
_DigestGen) ->
|
||||
check_password(User, Server, Password).
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
case extauth:set_password(User, Server, Password) of
|
||||
true -> set_password_internal(User, Server, Password),
|
||||
ok;
|
||||
true ->
|
||||
set_password_internal(User, Server, Password), ok;
|
||||
_ -> {error, unknown_problem}
|
||||
end.
|
||||
|
||||
try_register(User, Server, Password) ->
|
||||
case get_cache_option(Server) of
|
||||
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.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
@ -111,24 +109,27 @@ get_vh_registered_users(Server) ->
|
||||
ejabberd_auth_internal:get_vh_registered_users(Server).
|
||||
|
||||
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) ->
|
||||
ejabberd_auth_internal:get_vh_registered_users_number(Server).
|
||||
|
||||
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.
|
||||
get_password(User, Server) ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, CacheTime} -> get_password_cache(User, Server, CacheTime)
|
||||
{true, CacheTime} ->
|
||||
get_password_cache(User, Server, CacheTime)
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
case get_password(User, Server) of
|
||||
false -> [];
|
||||
false -> <<"">>;
|
||||
Other -> Other
|
||||
end.
|
||||
|
||||
@ -158,7 +159,8 @@ remove_user(User, Server, Password) ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, _CacheTime} ->
|
||||
ejabberd_auth_internal:remove_user(User, Server, Password)
|
||||
ejabberd_auth_internal:remove_user(User, Server,
|
||||
Password)
|
||||
end
|
||||
end.
|
||||
|
||||
@ -168,37 +170,42 @@ remove_user(User, Server, Password) ->
|
||||
|
||||
%% @spec (Host::string()) -> false | {true, CacheTime::integer()}
|
||||
get_cache_option(Host) ->
|
||||
case ejabberd_config:get_local_option({extauth_cache, Host}) of
|
||||
CacheTime when is_integer(CacheTime) -> {true, CacheTime};
|
||||
_ -> false
|
||||
case ejabberd_config:get_local_option(
|
||||
{extauth_cache, Host},
|
||||
fun(I) when is_integer(I), I > 0 -> I end) of
|
||||
undefined -> false;
|
||||
CacheTime -> {true, CacheTime}
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> true | false
|
||||
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
|
||||
try_register_extauth(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
|
||||
online ->
|
||||
check_password_internal(User, Server, Password);
|
||||
never ->
|
||||
check_password_external_cache(User, Server, Password);
|
||||
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);
|
||||
TimeStamp ->
|
||||
%% If last access exists, compare last access with cache refresh time
|
||||
case is_fresh_enough(TimeStamp, CacheTime) of
|
||||
%% If no need to refresh, check password against Mnesia
|
||||
true ->
|
||||
case check_password_internal(User, Server, Password) of
|
||||
%% If password valid in Mnesia, accept it
|
||||
true ->
|
||||
true;
|
||||
true -> true;
|
||||
%% Else (password nonvalid in Mnesia), check in extauth and cache result
|
||||
false ->
|
||||
check_password_external_cache(User, Server, Password)
|
||||
@ -215,60 +222,54 @@ get_password_internal(User, Server) ->
|
||||
%% @spec (User, Server, CacheTime) -> false | Password::string()
|
||||
get_password_cache(User, Server, CacheTime) ->
|
||||
case get_last_access(User, Server) of
|
||||
online ->
|
||||
get_password_internal(User, Server);
|
||||
never ->
|
||||
false;
|
||||
online -> get_password_internal(User, Server);
|
||||
never -> false;
|
||||
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;
|
||||
TimeStamp ->
|
||||
case is_fresh_enough(TimeStamp, CacheTime) of
|
||||
true ->
|
||||
get_password_internal(User, Server);
|
||||
false ->
|
||||
false
|
||||
true -> get_password_internal(User, Server);
|
||||
false -> false
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
%% Check the password using extauth; if success then cache it
|
||||
check_password_external_cache(User, Server, Password) ->
|
||||
case check_password_extauth(User, Server, Password) of
|
||||
true ->
|
||||
set_password_internal(User, Server, Password), true;
|
||||
false ->
|
||||
false
|
||||
false -> false
|
||||
end.
|
||||
|
||||
%% Try to register using extauth; if success then cache it
|
||||
try_register_external_cache(User, Server, Password) ->
|
||||
case try_register_extauth(User, Server, Password) of
|
||||
{atomic, ok} = R ->
|
||||
set_password_internal(User, Server, Password),
|
||||
R;
|
||||
set_password_internal(User, Server, Password), R;
|
||||
_ -> {error, not_allowed}
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> true | false
|
||||
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}
|
||||
set_password_internal(User, Server, Password) ->
|
||||
ejabberd_auth_internal:set_password(User, Server, Password).
|
||||
|
||||
%% @spec (TimeLast, CacheTime) -> true | false
|
||||
%% TimeLast = online | never | integer()
|
||||
%% CacheTime = integer() | false
|
||||
is_fresh_enough(online, _CacheTime) ->
|
||||
true;
|
||||
is_fresh_enough(never, _CacheTime) ->
|
||||
false;
|
||||
ejabberd_auth_internal:set_password(User, Server,
|
||||
Password).
|
||||
|
||||
is_fresh_enough(TimeStampLast, CacheTime) ->
|
||||
{MegaSecs, Secs, _MicroSecs} = now(),
|
||||
Now = MegaSecs * 1000000 + Secs,
|
||||
(TimeStampLast + CacheTime > Now).
|
||||
TimeStampLast + CacheTime > Now.
|
||||
|
||||
%% @spec (User, Server) -> online | never | mod_last_required | TimeStamp::integer()
|
||||
%% Code copied from mod_configure.erl
|
||||
@ -279,17 +280,14 @@ get_last_access(User, Server) ->
|
||||
[] ->
|
||||
_US = {User, Server},
|
||||
case get_last_info(User, Server) of
|
||||
mod_last_required ->
|
||||
mod_last_required;
|
||||
not_found ->
|
||||
never;
|
||||
{ok, Timestamp, _Status} ->
|
||||
Timestamp
|
||||
mod_last_required -> mod_last_required;
|
||||
not_found -> never;
|
||||
{ok, Timestamp, _Status} -> Timestamp
|
||||
end;
|
||||
_ ->
|
||||
online
|
||||
_ -> online
|
||||
end.
|
||||
%% @spec (User, Server) -> {ok, Timestamp, Status} | not_found | mod_last_required
|
||||
|
||||
get_last_info(User, Server) ->
|
||||
case get_mod_last_enabled(Server) of
|
||||
mod_last -> mod_last:get_last_info(User, Server);
|
||||
@ -310,4 +308,4 @@ get_mod_last_configured(Server) ->
|
||||
end.
|
||||
|
||||
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).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(ejabberd_auth).
|
||||
|
||||
%% External exports
|
||||
-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,
|
||||
-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
|
||||
]).
|
||||
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, export/1,
|
||||
plain_password_required/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-record(passwd, {us, password}).
|
||||
-record(reg_users_counter, {vhost, count}).
|
||||
-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
|
||||
password = <<"">> :: binary() | scram() | '_'}).
|
||||
|
||||
-record(reg_users_counter, {vhost = <<"">> :: binary(),
|
||||
count = 0 :: integer() | '$1'}).
|
||||
|
||||
-define(SALT_LENGTH, 16).
|
||||
|
||||
@ -58,7 +55,8 @@
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host) ->
|
||||
mnesia:create_table(passwd, [{disc_copies, [node()]},
|
||||
mnesia:create_table(passwd,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, passwd)}]),
|
||||
mnesia:create_table(reg_users_counter,
|
||||
[{ram_copies, [node()]},
|
||||
@ -72,7 +70,7 @@ update_reg_users_counter_table(Server) ->
|
||||
Set = get_vh_registered_users(Server),
|
||||
Size = length(Set),
|
||||
LServer = jlib:nameprep(Server),
|
||||
F = fun() ->
|
||||
F = fun () ->
|
||||
mnesia:write(#reg_users_counter{vhost = LServer,
|
||||
count = Size})
|
||||
end,
|
||||
@ -95,46 +93,40 @@ check_password(User, Server, Password) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[#passwd{password = Password}] when is_list(Password) ->
|
||||
Password /= "";
|
||||
[#passwd{password = Scram}] when is_record(Scram, scram) ->
|
||||
[#passwd{password = Password}]
|
||||
when is_binary(Password) ->
|
||||
Password /= <<"">>;
|
||||
[#passwd{password = Scram}]
|
||||
when is_record(Scram, scram) ->
|
||||
is_password_scram_valid(Password, Scram);
|
||||
_ ->
|
||||
false
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
check_password(User, Server, Password, Digest, DigestGen) ->
|
||||
check_password(User, Server, Password, Digest,
|
||||
DigestGen) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[#passwd{password = Passwd}] when is_list(Passwd) ->
|
||||
DigRes = if
|
||||
Digest /= "" ->
|
||||
[#passwd{password = Passwd}] when is_binary(Passwd) ->
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true ->
|
||||
false
|
||||
true -> false
|
||||
end,
|
||||
if DigRes ->
|
||||
true;
|
||||
true ->
|
||||
(Passwd == Password) and (Password /= "")
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
[#passwd{password = Scram}] when is_record(Scram, scram) ->
|
||||
Passwd = base64:decode(Scram#scram.storedkey),
|
||||
DigRes = if
|
||||
Digest /= "" ->
|
||||
[#passwd{password = Scram}]
|
||||
when is_record(Scram, scram) ->
|
||||
Passwd = jlib:decode_base64(Scram#scram.storedkey),
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true ->
|
||||
false
|
||||
true -> false
|
||||
end,
|
||||
if DigRes ->
|
||||
true;
|
||||
true ->
|
||||
(Passwd == Password) and (Password /= "")
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
@ -143,46 +135,45 @@ set_password(User, Server, Password) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
if
|
||||
(LUser == error) or (LServer == error) ->
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
true ->
|
||||
F = fun() ->
|
||||
Password2 = case is_scrammed() and is_list(Password) of
|
||||
F = fun () ->
|
||||
Password2 = case is_scrammed() and is_binary(Password)
|
||||
of
|
||||
true -> password_to_scram(Password);
|
||||
false -> Password
|
||||
end,
|
||||
mnesia:write(#passwd{us = US,
|
||||
password = Password2})
|
||||
mnesia:write(#passwd{us = US, password = Password2})
|
||||
end,
|
||||
{atomic, ok} = mnesia:transaction(F),
|
||||
ok
|
||||
end.
|
||||
|
||||
%% @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),
|
||||
LServer = jlib:nameprep(Server),
|
||||
Password = iolist_to_binary(PasswordList),
|
||||
US = {LUser, LServer},
|
||||
if
|
||||
(LUser == error) or (LServer == error) ->
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
true ->
|
||||
F = fun() ->
|
||||
F = fun () ->
|
||||
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);
|
||||
false -> Password
|
||||
end,
|
||||
mnesia:write(#passwd{us = US,
|
||||
password = Password2}),
|
||||
mnesia:dirty_update_counter(
|
||||
reg_users_counter,
|
||||
mnesia:dirty_update_counter(reg_users_counter,
|
||||
LServer, 1),
|
||||
ok;
|
||||
[_E] ->
|
||||
exists
|
||||
[_E] -> exists
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F)
|
||||
@ -194,21 +185,20 @@ dirty_get_registered_users() ->
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
mnesia:dirty_select(
|
||||
passwd,
|
||||
mnesia:dirty_select(passwd,
|
||||
[{#passwd{us = '$1', _ = '_'},
|
||||
[{'==', {element, 2, '$1'}, LServer}],
|
||||
['$1']}]).
|
||||
[{'==', {element, 2, '$1'}, LServer}], ['$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) ->
|
||||
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, End - Start + 1}, {offset, Start}]);
|
||||
get_vh_registered_users(Server,
|
||||
[{limit, Limit}, {offset, Offset}])
|
||||
when is_integer(Limit) and is_integer(Offset) ->
|
||||
case get_vh_registered_users(Server) of
|
||||
[] ->
|
||||
[];
|
||||
[] -> [];
|
||||
Users ->
|
||||
Set = lists:keysort(1, Users),
|
||||
L = length(Set),
|
||||
@ -218,21 +208,28 @@ get_vh_registered_users(Server, [{limit, Limit}, {offset, Offset}])
|
||||
end,
|
||||
lists:sublist(Set, Start, Limit)
|
||||
end;
|
||||
|
||||
get_vh_registered_users(Server, [{prefix, Prefix}])
|
||||
when is_list(Prefix) ->
|
||||
Set = [{U,S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)],
|
||||
when is_binary(Prefix) ->
|
||||
Set = [{U, S}
|
||||
|| {U, S} <- get_vh_registered_users(Server),
|
||||
str:prefix(Prefix, U)],
|
||||
lists:keysort(1, Set);
|
||||
|
||||
get_vh_registered_users(Server, [{prefix, Prefix}, {from, Start}, {to, End}])
|
||||
when is_list(Prefix) and is_integer(Start) and is_integer(End) ->
|
||||
get_vh_registered_users(Server, [{prefix, Prefix}, {limit, End-Start+1}, {offset, Start}]);
|
||||
|
||||
get_vh_registered_users(Server, [{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
|
||||
when is_list(Prefix) and is_integer(Limit) and is_integer(Offset) ->
|
||||
case [{U,S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)] of
|
||||
[] ->
|
||||
[];
|
||||
get_vh_registered_users(Server,
|
||||
[{prefix, Prefix}, {from, Start}, {to, End}])
|
||||
when is_binary(Prefix) and is_integer(Start) and
|
||||
is_integer(End) ->
|
||||
get_vh_registered_users(Server,
|
||||
[{prefix, Prefix}, {limit, End - Start + 1},
|
||||
{offset, Start}]);
|
||||
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 ->
|
||||
Set = lists:keysort(1, Users),
|
||||
L = length(Set),
|
||||
@ -242,27 +239,27 @@ get_vh_registered_users(Server, [{prefix, Prefix}, {limit, Limit}, {offset, Offs
|
||||
end,
|
||||
lists:sublist(Set, Start, Limit)
|
||||
end;
|
||||
|
||||
get_vh_registered_users(Server, _) ->
|
||||
get_vh_registered_users(Server).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
Query = mnesia:dirty_select(
|
||||
reg_users_counter,
|
||||
[{#reg_users_counter{vhost = LServer, count = '$1'},
|
||||
[],
|
||||
['$1']}]),
|
||||
Query = mnesia:dirty_select(reg_users_counter,
|
||||
[{#reg_users_counter{vhost = LServer,
|
||||
count = '$1'},
|
||||
[], ['$1']}]),
|
||||
case Query of
|
||||
[Count] ->
|
||||
Count;
|
||||
[Count] -> Count;
|
||||
_ -> 0
|
||||
end.
|
||||
|
||||
get_vh_registered_users_number(Server, [{prefix, Prefix}]) when is_list(Prefix) ->
|
||||
Set = [{U, S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)],
|
||||
get_vh_registered_users_number(Server,
|
||||
[{prefix, Prefix}])
|
||||
when is_binary(Prefix) ->
|
||||
Set = [{U, S}
|
||||
|| {U, S} <- get_vh_registered_users(Server),
|
||||
str:prefix(Prefix, U)],
|
||||
length(Set);
|
||||
|
||||
get_vh_registered_users_number(Server, _) ->
|
||||
get_vh_registered_users_number(Server).
|
||||
|
||||
@ -271,15 +268,16 @@ get_password(User, Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read(passwd, US) of
|
||||
[#passwd{password = Password}] when is_list(Password) ->
|
||||
[#passwd{password = Password}]
|
||||
when is_binary(Password) ->
|
||||
Password;
|
||||
[#passwd{password = Scram}] when is_record(Scram, scram) ->
|
||||
{base64:decode(Scram#scram.storedkey),
|
||||
base64:decode(Scram#scram.serverkey),
|
||||
base64:decode(Scram#scram.salt),
|
||||
[#passwd{password = Scram}]
|
||||
when is_record(Scram, scram) ->
|
||||
{jlib:decode_base64(Scram#scram.storedkey),
|
||||
jlib:decode_base64(Scram#scram.serverkey),
|
||||
jlib:decode_base64(Scram#scram.salt),
|
||||
Scram#scram.iterationcount};
|
||||
_ ->
|
||||
false
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
@ -287,12 +285,13 @@ get_password_s(User, Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read(passwd, US) of
|
||||
[#passwd{password = Password}] when is_list(Password) ->
|
||||
[#passwd{password = Password}]
|
||||
when is_binary(Password) ->
|
||||
Password;
|
||||
[#passwd{password = Scram}] when is_record(Scram, scram) ->
|
||||
[];
|
||||
_ ->
|
||||
[]
|
||||
[#passwd{password = Scram}]
|
||||
when is_record(Scram, scram) ->
|
||||
<<"">>;
|
||||
_ -> <<"">>
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
@ -301,12 +300,9 @@ is_user_exists(User, Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[] ->
|
||||
false;
|
||||
[_] ->
|
||||
true;
|
||||
Other ->
|
||||
{error, Other}
|
||||
[] -> false;
|
||||
[_] -> true;
|
||||
Other -> {error, Other}
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> ok
|
||||
@ -316,10 +312,10 @@ remove_user(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
F = fun () ->
|
||||
mnesia:delete({passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter,
|
||||
LServer, -1)
|
||||
mnesia:dirty_update_counter(reg_users_counter, LServer,
|
||||
-1)
|
||||
end,
|
||||
mnesia:transaction(F),
|
||||
ok.
|
||||
@ -330,79 +326,65 @@ remove_user(User, Server, Password) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
F = fun () ->
|
||||
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:dirty_update_counter(reg_users_counter,
|
||||
LServer, -1),
|
||||
mnesia:dirty_update_counter(reg_users_counter, LServer,
|
||||
-1),
|
||||
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
|
||||
true ->
|
||||
mnesia:delete({passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter,
|
||||
LServer, -1),
|
||||
ok;
|
||||
false ->
|
||||
not_allowed
|
||||
false -> not_allowed
|
||||
end;
|
||||
_ ->
|
||||
not_exists
|
||||
_ -> not_exists
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{atomic, Res} ->
|
||||
Res;
|
||||
_ ->
|
||||
bad_request
|
||||
{atomic, ok} -> ok;
|
||||
{atomic, Res} -> Res;
|
||||
_ -> bad_request
|
||||
end.
|
||||
|
||||
update_table() ->
|
||||
Fields = record_info(fields, passwd),
|
||||
case mnesia:table_info(passwd, attributes) of
|
||||
Fields ->
|
||||
convert_to_binary(Fields),
|
||||
maybe_scram_passwords(),
|
||||
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", []),
|
||||
mnesia:transform_table(passwd, ignore, Fields)
|
||||
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
|
||||
%%%
|
||||
@ -411,27 +393,30 @@ update_table() ->
|
||||
%% or if at least the first password is scrammed.
|
||||
is_scrammed() ->
|
||||
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
|
||||
{true, _} ->
|
||||
{true, _} -> true;
|
||||
{false, [#passwd{password = Scram}]}
|
||||
when is_record(Scram, scram) ->
|
||||
true;
|
||||
{false, [#passwd{password = Scram}]} when is_record(Scram, scram) ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
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() ->
|
||||
case is_scrammed() andalso not is_option_scram() of
|
||||
true ->
|
||||
?ERROR_MSG("Some passwords were stored in the database as SCRAM, "
|
||||
"but 'auth_password_format' is not configured 'scram'. "
|
||||
"The option will now be considered to be 'scram'.", []);
|
||||
false ->
|
||||
ok
|
||||
?ERROR_MSG("Some passwords were stored in the database "
|
||||
"as SCRAM, but 'auth_password_format' "
|
||||
"is not configured 'scram'. The option "
|
||||
"will now be considered to be 'scram'.",
|
||||
[]);
|
||||
false -> ok
|
||||
end.
|
||||
|
||||
maybe_scram_passwords() ->
|
||||
@ -441,8 +426,10 @@ maybe_scram_passwords() ->
|
||||
end.
|
||||
|
||||
scram_passwords() ->
|
||||
?INFO_MSG("Converting the stored passwords into SCRAM bits", []),
|
||||
Fun = fun(#passwd{password = Password} = P) ->
|
||||
?INFO_MSG("Converting the stored passwords into "
|
||||
"SCRAM bits",
|
||||
[]),
|
||||
Fun = fun (#passwd{password = Password} = P) ->
|
||||
Scram = password_to_scram(Password),
|
||||
P#passwd{password = Scram}
|
||||
end,
|
||||
@ -450,21 +437,39 @@ scram_passwords() ->
|
||||
mnesia:transform_table(passwd, Fun, Fields).
|
||||
|
||||
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) ->
|
||||
Salt = crypto:rand_bytes(?SALT_LENGTH),
|
||||
SaltedPassword = scram:salted_password(Password, Salt, IterationCount),
|
||||
StoredKey = scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
SaltedPassword = scram:salted_password(Password, Salt,
|
||||
IterationCount),
|
||||
StoredKey =
|
||||
scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
ServerKey = scram:server_key(SaltedPassword),
|
||||
#scram{storedkey = base64:encode(StoredKey),
|
||||
serverkey = base64:encode(ServerKey),
|
||||
salt = base64:encode(Salt),
|
||||
#scram{storedkey = jlib:encode_base64(StoredKey),
|
||||
serverkey = jlib:encode_base64(ServerKey),
|
||||
salt = jlib:encode_base64(Salt),
|
||||
iterationcount = IterationCount}.
|
||||
|
||||
is_password_scram_valid(Password, Scram) ->
|
||||
IterationCount = Scram#scram.iterationcount,
|
||||
Salt = base64:decode(Scram#scram.salt),
|
||||
SaltedPassword = scram:salted_password(Password, Salt, IterationCount),
|
||||
StoredKey = scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
(base64:decode(Scram#scram.storedkey) == StoredKey).
|
||||
Salt = jlib:decode_base64(Scram#scram.salt),
|
||||
SaltedPassword = scram:salted_password(Password, Salt,
|
||||
IterationCount),
|
||||
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).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_server).
|
||||
-behaviour(ejabberd_auth).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1,
|
||||
handle_info/2,
|
||||
handle_call/3,
|
||||
handle_cast/2,
|
||||
terminate/2,
|
||||
code_change/3
|
||||
]).
|
||||
-export([init/1, handle_info/2, handle_call/3,
|
||||
handle_cast/2, terminate/2, code_change/3]).
|
||||
|
||||
%% External exports
|
||||
-export([start/1,
|
||||
stop/1,
|
||||
start_link/1,
|
||||
set_password/3,
|
||||
check_password/3,
|
||||
check_password/5,
|
||||
try_register/3,
|
||||
dirty_get_registered_users/0,
|
||||
get_vh_registered_users/1,
|
||||
-export([start/1, stop/1, start_link/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_password/2,
|
||||
get_password_s/2,
|
||||
is_user_exists/2,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
store_type/0,
|
||||
plain_password_required/0
|
||||
]).
|
||||
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]).
|
||||
|
||||
-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.
|
||||
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
|
||||
@ -99,10 +85,8 @@ handle_info(_Info, State) ->
|
||||
|
||||
start(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
ChildSpec = {
|
||||
Proc, {?MODULE, start_link, [Host]},
|
||||
transient, 1000, worker, [?MODULE]
|
||||
},
|
||||
ChildSpec = {Proc, {?MODULE, start_link, [Host]},
|
||||
transient, 1000, worker, [?MODULE]},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||
|
||||
stop(Host) ->
|
||||
@ -115,56 +99,45 @@ start_link(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
gen_server:start_link({local, Proc}, ?MODULE, Host, []).
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
terminate(_Reason, _State) -> ok.
|
||||
|
||||
init(Host) ->
|
||||
State = parse_options(Host),
|
||||
eldap_pool:start_link(State#state.eldap_id,
|
||||
State#state.servers,
|
||||
State#state.backups,
|
||||
State#state.port,
|
||||
State#state.dn,
|
||||
State#state.password,
|
||||
State#state.tls_options),
|
||||
State#state.servers, State#state.backups,
|
||||
State#state.port, State#state.dn,
|
||||
State#state.password, State#state.tls_options),
|
||||
eldap_pool:start_link(State#state.bind_eldap_id,
|
||||
State#state.servers,
|
||||
State#state.backups,
|
||||
State#state.port,
|
||||
State#state.dn,
|
||||
State#state.password,
|
||||
State#state.tls_options),
|
||||
State#state.servers, State#state.backups,
|
||||
State#state.port, State#state.dn,
|
||||
State#state.password, State#state.tls_options),
|
||||
{ok, State}.
|
||||
|
||||
plain_password_required() ->
|
||||
true.
|
||||
plain_password_required() -> true.
|
||||
|
||||
store_type() ->
|
||||
external.
|
||||
store_type() -> external.
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
%% In LDAP spec: empty password means anonymous authentication.
|
||||
%% As ejabberd is providing other anonymous authentication mechanisms
|
||||
%% we simply prevent the use of LDAP anonymous authentication.
|
||||
if Password == "" ->
|
||||
false;
|
||||
if Password == <<"">> -> false;
|
||||
true ->
|
||||
case catch check_password_ldap(User, Server, Password) of
|
||||
case catch check_password_ldap(User, Server, Password)
|
||||
of
|
||||
{'EXIT', _} -> false;
|
||||
Result -> Result
|
||||
end
|
||||
end.
|
||||
|
||||
check_password(User, Server, Password, _Digest, _DigestGen) ->
|
||||
check_password(User, Server, Password, _Digest,
|
||||
_DigestGen) ->
|
||||
check_password(User, Server, Password).
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
|
||||
case find_user_dn(User, State) of
|
||||
false ->
|
||||
{error, user_not_found};
|
||||
false -> {error, user_not_found};
|
||||
DN ->
|
||||
eldap_pool:modify_passwd(State#state.eldap_id, DN, Password)
|
||||
eldap_pool:modify_passwd(State#state.eldap_id, DN,
|
||||
Password)
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> {error, not_allowed}
|
||||
@ -173,10 +146,10 @@ try_register(_User, _Server, _Password) ->
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
Servers = ejabberd_config:get_vh_by_auth_method(ldap),
|
||||
lists:flatmap(
|
||||
fun(Server) ->
|
||||
lists:flatmap(fun (Server) ->
|
||||
get_vh_registered_users(Server)
|
||||
end, Servers).
|
||||
end,
|
||||
Servers).
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
case catch get_vh_registered_users_ldap(Server) of
|
||||
@ -184,29 +157,29 @@ get_vh_registered_users(Server) ->
|
||||
Result -> Result
|
||||
end.
|
||||
|
||||
get_vh_registered_users(Server, _) ->
|
||||
get_vh_registered_users(Server).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
length(get_vh_registered_users(Server)).
|
||||
|
||||
get_password(_User, _Server) ->
|
||||
false.
|
||||
get_vh_registered_users_number(Server, _) ->
|
||||
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}
|
||||
is_user_exists(User, Server) ->
|
||||
case catch is_user_exists_ldap(User, Server) of
|
||||
{'EXIT', Error} ->
|
||||
{error, Error};
|
||||
Result ->
|
||||
Result
|
||||
{'EXIT', Error} -> {error, Error};
|
||||
Result -> Result
|
||||
end.
|
||||
|
||||
remove_user(_User, _Server) ->
|
||||
{error, not_allowed}.
|
||||
remove_user(_User, _Server) -> {error, not_allowed}.
|
||||
|
||||
remove_user(_User, _Server, _Password) ->
|
||||
not_allowed.
|
||||
remove_user(_User, _Server, _Password) -> not_allowed.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
@ -214,10 +187,11 @@ remove_user(_User, _Server, _Password) ->
|
||||
check_password_ldap(User, Server, Password) ->
|
||||
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
|
||||
case find_user_dn(User, State) of
|
||||
false ->
|
||||
false;
|
||||
false -> false;
|
||||
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;
|
||||
_ -> false
|
||||
end
|
||||
@ -236,33 +210,40 @@ get_vh_registered_users_ldap(Server) ->
|
||||
{filter, EldapFilter},
|
||||
{timeout, ?LDAP_SEARCH_TIMEOUT},
|
||||
{deref_aliases, State#state.deref_aliases},
|
||||
{attributes, ResAttrs}]) of
|
||||
{attributes, ResAttrs}])
|
||||
of
|
||||
#eldap_search_result{entries = Entries} ->
|
||||
lists:flatmap(
|
||||
fun(#eldap_entry{attributes = Attrs,
|
||||
lists:flatmap(fun (#eldap_entry{attributes = Attrs,
|
||||
object_name = DN}) ->
|
||||
case is_valid_dn(DN, Attrs, State) of
|
||||
false -> [];
|
||||
_ ->
|
||||
case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
|
||||
"" -> [];
|
||||
case
|
||||
eldap_utils:find_ldap_attrs(UIDs,
|
||||
Attrs)
|
||||
of
|
||||
<<"">> -> [];
|
||||
{User, UIDFormat} ->
|
||||
case eldap_utils:get_user_part(User, UIDFormat) of
|
||||
case
|
||||
eldap_utils:get_user_part(User,
|
||||
UIDFormat)
|
||||
of
|
||||
{ok, U} ->
|
||||
case jlib:nodeprep(U) of
|
||||
error -> [];
|
||||
LU -> [{LU, jlib:nameprep(Server)}]
|
||||
LU ->
|
||||
[{LU,
|
||||
jlib:nameprep(Server)}]
|
||||
end;
|
||||
_ -> []
|
||||
end
|
||||
end
|
||||
end
|
||||
end, Entries);
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
Entries);
|
||||
_ -> []
|
||||
end;
|
||||
_ ->
|
||||
[]
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
is_user_exists_ldap(User, Server) ->
|
||||
@ -274,70 +255,72 @@ is_user_exists_ldap(User, Server) ->
|
||||
|
||||
handle_call(get_state, _From, State) ->
|
||||
{reply, {ok, State}, State};
|
||||
|
||||
handle_call(stop, _From, State) ->
|
||||
{stop, normal, ok, State};
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, bad_request, State}.
|
||||
|
||||
find_user_dn(User, 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} ->
|
||||
case eldap_pool:search(State#state.eldap_id,
|
||||
[{base, State#state.base},
|
||||
{filter, Filter},
|
||||
[{base, State#state.base}, {filter, Filter},
|
||||
{deref_aliases, State#state.deref_aliases},
|
||||
{attributes, ResAttrs}]) of
|
||||
#eldap_search_result{entries = [#eldap_entry{attributes = Attrs,
|
||||
object_name = DN} | _]} ->
|
||||
{attributes, ResAttrs}])
|
||||
of
|
||||
#eldap_search_result{entries =
|
||||
[#eldap_entry{attributes = Attrs,
|
||||
object_name = DN}
|
||||
| _]} ->
|
||||
dn_filter(DN, Attrs, State);
|
||||
_ ->
|
||||
false
|
||||
_ -> false
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
%% apply the dn filter and the local filter:
|
||||
dn_filter(DN, Attrs, State) ->
|
||||
%% Check if user is denied access by attribute value (local check)
|
||||
case check_local_filter(Attrs, State) of
|
||||
false -> false;
|
||||
true -> is_valid_dn(DN, Attrs, State)
|
||||
end.
|
||||
|
||||
%% Check that the DN is valid, based on the dn filter
|
||||
is_valid_dn(DN, _, #state{dn_filter = undefined}) ->
|
||||
DN;
|
||||
|
||||
is_valid_dn(DN, _, #state{dn_filter = undefined}) -> DN;
|
||||
is_valid_dn(DN, Attrs, State) ->
|
||||
DNAttrs = State#state.dn_filter_attrs,
|
||||
UIDs = State#state.uids,
|
||||
Values = [{"%s", eldap_utils:get_ldap_attr(Attr, Attrs), 1} || Attr <- DNAttrs],
|
||||
SubstValues = case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
|
||||
"" -> Values;
|
||||
Values = [{<<"%s">>,
|
||||
eldap_utils:get_ldap_attr(Attr, Attrs), 1}
|
||||
|| Attr <- DNAttrs],
|
||||
SubstValues = case eldap_utils:find_ldap_attrs(UIDs,
|
||||
Attrs)
|
||||
of
|
||||
<<"">> -> Values;
|
||||
{S, UAF} ->
|
||||
case eldap_utils:get_user_part(S, UAF) of
|
||||
{ok, U} -> [{"%u", U} | Values];
|
||||
{ok, U} -> [{<<"%u">>, U} | Values];
|
||||
_ -> Values
|
||||
end
|
||||
end ++ [{"%d", State#state.host}, {"%D", DN}],
|
||||
case eldap_filter:parse(State#state.dn_filter, SubstValues) of
|
||||
end
|
||||
++ [{<<"%d">>, State#state.host}, {<<"%D">>, DN}],
|
||||
case eldap_filter:parse(State#state.dn_filter,
|
||||
SubstValues)
|
||||
of
|
||||
{ok, EldapFilter} ->
|
||||
case eldap_pool:search(State#state.eldap_id,
|
||||
[{base, State#state.base},
|
||||
{filter, EldapFilter},
|
||||
{deref_aliases, State#state.deref_aliases},
|
||||
{attributes, ["dn"]}]) of
|
||||
#eldap_search_result{entries = [_|_]} ->
|
||||
DN;
|
||||
_ ->
|
||||
false
|
||||
{attributes, [<<"dn">>]}])
|
||||
of
|
||||
#eldap_search_result{entries = [_ | _]} -> DN;
|
||||
_ -> false
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
%% The local filter is used to check an attribute in ejabberd
|
||||
@ -346,9 +329,11 @@ is_valid_dn(DN, Attrs, State) ->
|
||||
%% {equal, {"accountStatus",["active"]}}
|
||||
%% {notequal, {"accountStatus",["disabled"]}}
|
||||
%% {ldap_local_filter, {notequal, {"accountStatus",["disabled"]}}}
|
||||
check_local_filter(_Attrs, #state{lfilter = undefined}) ->
|
||||
check_local_filter(_Attrs,
|
||||
#state{lfilter = undefined}) ->
|
||||
true;
|
||||
check_local_filter(Attrs, #state{lfilter = LocalFilter}) ->
|
||||
check_local_filter(Attrs,
|
||||
#state{lfilter = LocalFilter}) ->
|
||||
{Operation, FilterMatch} = LocalFilter,
|
||||
local_filter(Operation, Attrs, FilterMatch).
|
||||
|
||||
@ -356,99 +341,80 @@ local_filter(equal, Attrs, FilterMatch) ->
|
||||
{Attr, Value} = FilterMatch,
|
||||
case lists:keysearch(Attr, 1, Attrs) of
|
||||
false -> false;
|
||||
{value,{Attr,Value}} -> true;
|
||||
{value, {Attr, Value}} -> true;
|
||||
_ -> false
|
||||
end;
|
||||
local_filter(notequal, Attrs, FilterMatch) ->
|
||||
not local_filter(equal, Attrs, FilterMatch).
|
||||
|
||||
result_attrs(#state{uids = UIDs, dn_filter_attrs = DNFilterAttrs}) ->
|
||||
lists:foldl(
|
||||
fun({UID}, Acc) ->
|
||||
[UID | Acc];
|
||||
({UID, _}, Acc) ->
|
||||
[UID | Acc]
|
||||
end, DNFilterAttrs, UIDs).
|
||||
result_attrs(#state{uids = UIDs,
|
||||
dn_filter_attrs = DNFilterAttrs}) ->
|
||||
lists:foldl(fun ({UID}, Acc) -> [UID | Acc];
|
||||
({UID, _}, Acc) -> [UID | Acc]
|
||||
end,
|
||||
DNFilterAttrs, UIDs).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Auxiliary functions
|
||||
%%%----------------------------------------------------------------------
|
||||
parse_options(Host) ->
|
||||
Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, ?MODULE)),
|
||||
Bind_Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, bind_ejabberd_auth_ldap)),
|
||||
LDAPServers = ejabberd_config:get_local_option({ldap_servers, Host}),
|
||||
LDAPBackups = case ejabberd_config:get_local_option({ldap_backups, Host}) of
|
||||
undefined -> [];
|
||||
Backups -> Backups
|
||||
end,
|
||||
LDAPEncrypt = ejabberd_config:get_local_option({ldap_encrypt, Host}),
|
||||
LDAPTLSVerify = ejabberd_config:get_local_option({ldap_tls_verify, Host}),
|
||||
LDAPTLSCAFile = ejabberd_config:get_local_option({ldap_tls_cacertfile, Host}),
|
||||
LDAPTLSDepth = ejabberd_config:get_local_option({ldap_tls_depth, Host}),
|
||||
LDAPPort = case ejabberd_config:get_local_option({ldap_port, Host}) of
|
||||
undefined -> case LDAPEncrypt of
|
||||
tls -> ?LDAPS_PORT;
|
||||
starttls -> ?LDAP_PORT;
|
||||
_ -> ?LDAP_PORT
|
||||
end;
|
||||
P -> P
|
||||
end,
|
||||
RootDN = case ejabberd_config:get_local_option({ldap_rootdn, Host}) of
|
||||
undefined -> "";
|
||||
RDN -> RDN
|
||||
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;
|
||||
Cfg = eldap_utils:get_config(Host, []),
|
||||
Eldap_ID = jlib:atom_to_binary(gen_mod:get_module_proc(Host, ?MODULE)),
|
||||
Bind_Eldap_ID = jlib:atom_to_binary(
|
||||
gen_mod:get_module_proc(Host, bind_ejabberd_auth_ldap)),
|
||||
UIDsTemp = eldap_utils:get_opt(
|
||||
{ldap_uids, Host}, [],
|
||||
fun(Us) ->
|
||||
lists:map(
|
||||
fun({U, P}) ->
|
||||
{iolist_to_binary(U),
|
||||
iolist_to_binary(P)};
|
||||
({U}) ->
|
||||
{iolist_to_binary(U)}
|
||||
end, Us)
|
||||
end, [{<<"uid">>, <<"%u">>}]),
|
||||
UIDs = eldap_utils:uids_domain_subst(Host, UIDsTemp),
|
||||
SubFilter = eldap_utils:generate_subfilter(UIDs),
|
||||
UserFilter = case eldap_utils:get_opt(
|
||||
{ldap_filter, Host}, [],
|
||||
fun check_filter/1, <<"">>) of
|
||||
<<"">> ->
|
||||
SubFilter;
|
||||
F ->
|
||||
eldap_utils:check_filter(F),
|
||||
"(&" ++ SubFilter ++ F ++ ")"
|
||||
<<"(&", SubFilter/binary, F/binary, ")">>
|
||||
end,
|
||||
SearchFilter = eldap_filter:do_sub(UserFilter, [{"%u", "*"}]),
|
||||
LDAPBase = ejabberd_config:get_local_option({ldap_base, Host}),
|
||||
SearchFilter = eldap_filter:do_sub(UserFilter,
|
||||
[{<<"%u">>, <<"*">>}]),
|
||||
{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, []};
|
||||
{DNF, undefined} ->
|
||||
{DNF, []};
|
||||
{DNF, DNFA} ->
|
||||
{DNF, DNFA}
|
||||
[];
|
||||
_ ->
|
||||
[iolist_to_binary(A)
|
||||
|| A <- DNFA]
|
||||
end,
|
||||
eldap_utils:check_filter(DNFilter),
|
||||
LocalFilter = ejabberd_config:get_local_option({ldap_local_filter, Host}),
|
||||
DerefAliases = case ejabberd_config:get_local_option(
|
||||
{ldap_deref_aliases, Host}) of
|
||||
undefined -> never;
|
||||
Val -> Val
|
||||
end,
|
||||
#state{host = Host,
|
||||
eldap_id = Eldap_ID,
|
||||
NewDNF = check_filter(DNF),
|
||||
{NewDNF, NewDNFA}
|
||||
end, {undefined, []}),
|
||||
LocalFilter = eldap_utils:get_opt(
|
||||
{ldap_local_filter, Host}, [], fun(V) -> V end),
|
||||
#state{host = Host, eldap_id = Eldap_ID,
|
||||
bind_eldap_id = Bind_Eldap_ID,
|
||||
servers = LDAPServers,
|
||||
backups = LDAPBackups,
|
||||
port = LDAPPort,
|
||||
tls_options = [{encrypt, LDAPEncrypt},
|
||||
{tls_verify, LDAPTLSVerify},
|
||||
{tls_cacertfile, LDAPTLSCAFile},
|
||||
{tls_depth, LDAPTLSDepth}],
|
||||
dn = RootDN,
|
||||
password = Password,
|
||||
base = LDAPBase,
|
||||
uids = UIDs,
|
||||
ufilter = UserFilter,
|
||||
sfilter = SearchFilter,
|
||||
lfilter = LocalFilter,
|
||||
deref_aliases = DerefAliases,
|
||||
dn_filter = DNFilter,
|
||||
dn_filter_attrs = DNFilterAttrs
|
||||
}.
|
||||
servers = Cfg#eldap_config.servers,
|
||||
backups = Cfg#eldap_config.backups,
|
||||
port = Cfg#eldap_config.port,
|
||||
tls_options = Cfg#eldap_config.tls_options,
|
||||
dn = Cfg#eldap_config.dn,
|
||||
password = Cfg#eldap_config.password,
|
||||
base = Cfg#eldap_config.base,
|
||||
deref_aliases = Cfg#eldap_config.deref_aliases,
|
||||
uids = UIDs, ufilter = UserFilter,
|
||||
sfilter = SearchFilter, lfilter = LocalFilter,
|
||||
dn_filter = DNFilter, dn_filter_attrs = DNFilterAttrs}.
|
||||
|
||||
check_filter(F) ->
|
||||
NewF = iolist_to_binary(F),
|
||||
{ok, _} = eldap_filter:parse(NewF),
|
||||
NewF.
|
||||
|
@ -25,56 +25,46 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_auth_odbc).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(ejabberd_auth).
|
||||
|
||||
%% External exports
|
||||
-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,
|
||||
-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
|
||||
]).
|
||||
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]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(_Host) ->
|
||||
ok.
|
||||
start(_Host) -> ok.
|
||||
|
||||
plain_password_required() ->
|
||||
false.
|
||||
plain_password_required() -> false.
|
||||
|
||||
store_type() ->
|
||||
plain.
|
||||
store_type() -> plain.
|
||||
|
||||
%% @spec (User, Server, Password) -> true | false | {error, Error}
|
||||
check_password(User, Server, Password) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
false;
|
||||
error -> false;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
try odbc_queries:get_password(LServer, Username) of
|
||||
{selected, ["password"], [{Password}]} ->
|
||||
Password /= ""; %% Password is correct, and not empty
|
||||
{selected, ["password"], [{_Password2}]} ->
|
||||
{selected, [<<"password">>], [[Password]]} ->
|
||||
Password /= <<"">>;
|
||||
{selected, [<<"password">>], [[_Password2]]} ->
|
||||
false; %% Password is not correct
|
||||
{selected, ["password"], []} ->
|
||||
{selected, [<<"password">>], []} ->
|
||||
false; %% Account does not exist
|
||||
{error, _Error} ->
|
||||
false %% Typical error is that table doesn't exist
|
||||
@ -85,28 +75,24 @@ check_password(User, Server, Password) ->
|
||||
end.
|
||||
|
||||
%% @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
|
||||
error ->
|
||||
false;
|
||||
error -> false;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
try odbc_queries:get_password(LServer, Username) of
|
||||
%% Account exists, check if password is valid
|
||||
{selected, ["password"], [{Passwd}]} ->
|
||||
DigRes = if
|
||||
Digest /= "" ->
|
||||
{selected, [<<"password">>], [[Passwd]]} ->
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true ->
|
||||
false
|
||||
true -> false
|
||||
end,
|
||||
if DigRes ->
|
||||
true;
|
||||
true ->
|
||||
(Passwd == Password) and (Password /= "")
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
{selected, ["password"], []} ->
|
||||
{selected, [<<"password">>], []} ->
|
||||
false; %% Account does not exist
|
||||
{error, _Error} ->
|
||||
false %% Typical error is that table doesn't exist
|
||||
@ -120,127 +106,115 @@ check_password(User, Server, Password, Digest, DigestGen) ->
|
||||
%% ok | {error, invalid_jid}
|
||||
set_password(User, Server, Password) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
{error, invalid_jid};
|
||||
error -> {error, invalid_jid};
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
Pass = ejabberd_odbc:escape(Password),
|
||||
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;
|
||||
Other -> {error, Other}
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid}
|
||||
try_register(User, Server, Password) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
{error, invalid_jid};
|
||||
error -> {error, invalid_jid};
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
Pass = ejabberd_odbc:escape(Password),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:add_user(LServer, Username, Pass) of
|
||||
{updated, 1} ->
|
||||
{atomic, ok};
|
||||
_ ->
|
||||
{atomic, exists}
|
||||
case catch odbc_queries:add_user(LServer, Username,
|
||||
Pass)
|
||||
of
|
||||
{updated, 1} -> {atomic, ok};
|
||||
_ -> {atomic, exists}
|
||||
end
|
||||
end.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
Servers = ejabberd_config:get_vh_by_auth_method(odbc),
|
||||
lists:flatmap(
|
||||
fun(Server) ->
|
||||
lists:flatmap(fun (Server) ->
|
||||
get_vh_registered_users(Server)
|
||||
end, Servers).
|
||||
end,
|
||||
Servers).
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:list_users(LServer) of
|
||||
{selected, ["username"], Res} ->
|
||||
[{U, LServer} || {U} <- Res];
|
||||
_ ->
|
||||
[]
|
||||
{selected, [<<"username">>], Res} ->
|
||||
[{U, LServer} || [U] <- Res];
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
get_vh_registered_users(Server, Opts) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:list_users(LServer, Opts) of
|
||||
{selected, ["username"], Res} ->
|
||||
[{U, LServer} || {U} <- Res];
|
||||
_ ->
|
||||
[]
|
||||
{selected, [<<"username">>], Res} ->
|
||||
[{U, LServer} || [U] <- Res];
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:users_number(LServer) of
|
||||
{selected, [_], [{Res}]} ->
|
||||
list_to_integer(Res);
|
||||
_ ->
|
||||
0
|
||||
{selected, [_], [[Res]]} ->
|
||||
jlib:binary_to_integer(Res);
|
||||
_ -> 0
|
||||
end.
|
||||
|
||||
get_vh_registered_users_number(Server, Opts) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:users_number(LServer, Opts) of
|
||||
{selected, [_], [{Res}]} ->
|
||||
list_to_integer(Res);
|
||||
_Other ->
|
||||
0
|
||||
{selected, [_], [[Res]]} ->
|
||||
jlib:binary_to_integer(Res);
|
||||
_Other -> 0
|
||||
end.
|
||||
|
||||
get_password(User, Server) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
false;
|
||||
error -> false;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:get_password(LServer, Username) of
|
||||
{selected, ["password"], [{Password}]} ->
|
||||
Password;
|
||||
_ ->
|
||||
false
|
||||
case catch odbc_queries:get_password(LServer, Username)
|
||||
of
|
||||
{selected, [<<"password">>], [[Password]]} -> Password;
|
||||
_ -> false
|
||||
end
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
"";
|
||||
error -> <<"">>;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:get_password(LServer, Username) of
|
||||
{selected, ["password"], [{Password}]} ->
|
||||
Password;
|
||||
_ ->
|
||||
""
|
||||
case catch odbc_queries:get_password(LServer, Username)
|
||||
of
|
||||
{selected, [<<"password">>], [[Password]]} -> Password;
|
||||
_ -> <<"">>
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
is_user_exists(User, Server) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
false;
|
||||
error -> false;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
try odbc_queries:get_password(LServer, Username) of
|
||||
{selected, ["password"], [{_Password}]} ->
|
||||
{selected, [<<"password">>], [[_Password]]} ->
|
||||
true; %% Account exists
|
||||
{selected, ["password"], []} ->
|
||||
{selected, [<<"password">>], []} ->
|
||||
false; %% Account does not exist
|
||||
{error, Error} ->
|
||||
{error, Error} %% Typical error is that table doesn't exist
|
||||
{error, Error} -> {error, Error}
|
||||
catch
|
||||
_:B ->
|
||||
{error, B} %% Typical error is database not accessible
|
||||
_:B -> {error, B}
|
||||
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.
|
||||
remove_user(User, Server) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
error;
|
||||
error -> error;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
@ -262,24 +235,22 @@ remove_user(User, Server) ->
|
||||
%% @doc Remove user if the provided password is correct.
|
||||
remove_user(User, Server, Password) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
error;
|
||||
error -> error;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
Pass = ejabberd_odbc:escape(Password),
|
||||
LServer = jlib:nameprep(Server),
|
||||
F = fun() ->
|
||||
Result = odbc_queries:del_user_return_password(
|
||||
LServer, Username, Pass),
|
||||
F = fun () ->
|
||||
Result = odbc_queries:del_user_return_password(LServer,
|
||||
Username,
|
||||
Pass),
|
||||
case Result of
|
||||
{selected, ["password"], [{Password}]} ->
|
||||
ok;
|
||||
{selected, ["password"], []} ->
|
||||
not_exists;
|
||||
_ ->
|
||||
not_allowed
|
||||
{selected, [<<"password">>], [[Password]]} -> ok;
|
||||
{selected, [<<"password">>], []} -> not_exists;
|
||||
_ -> not_allowed
|
||||
end
|
||||
end,
|
||||
{atomic, Result} = odbc_queries:sql_transaction(LServer, F),
|
||||
{atomic, Result} = odbc_queries:sql_transaction(LServer,
|
||||
F),
|
||||
Result
|
||||
end.
|
||||
|
@ -24,48 +24,47 @@
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ejabberd_auth_pam).
|
||||
|
||||
-author('xram@jabber.ru').
|
||||
|
||||
%% External exports
|
||||
-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
|
||||
]).
|
||||
-behaviour(ejabberd_auth).
|
||||
|
||||
%% External exports
|
||||
%%====================================================================
|
||||
%% 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) ->
|
||||
case epam:start() of
|
||||
{ok, _} -> ok;
|
||||
{error,{already_started, _}} -> ok;
|
||||
{error, {already_started, _}} -> ok;
|
||||
Err -> Err
|
||||
end.
|
||||
|
||||
set_password(_User, _Server, _Password) ->
|
||||
{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, Host, Password) ->
|
||||
Service = get_pam_service(Host),
|
||||
UserInfo = case get_pam_userinfotype(Host) of
|
||||
username -> User;
|
||||
jid -> User++"@"++Host
|
||||
jid -> <<User/binary, "@", Host/binary>>
|
||||
end,
|
||||
case catch epam:authenticate(Service, UserInfo, Password) of
|
||||
case catch epam:authenticate(Service, UserInfo,
|
||||
Password)
|
||||
of
|
||||
true -> true;
|
||||
_ -> false
|
||||
end.
|
||||
@ -73,17 +72,19 @@ check_password(User, Host, Password) ->
|
||||
try_register(_User, _Server, _Password) ->
|
||||
{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) ->
|
||||
false.
|
||||
get_vh_registered_users(_Host, _) -> [].
|
||||
|
||||
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}
|
||||
%% 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),
|
||||
UserInfo = case get_pam_userinfotype(Host) of
|
||||
username -> User;
|
||||
jid -> User++"@"++Host
|
||||
jid -> <<User/binary, "@", Host/binary>>
|
||||
end,
|
||||
case catch epam:acct_mgmt(Service, UserInfo) of
|
||||
true -> true;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
remove_user(_User, _Server) ->
|
||||
{error, not_allowed}.
|
||||
remove_user(_User, _Server) -> {error, not_allowed}.
|
||||
|
||||
remove_user(_User, _Server, _Password) ->
|
||||
not_allowed.
|
||||
remove_user(_User, _Server, _Password) -> not_allowed.
|
||||
|
||||
plain_password_required() ->
|
||||
true.
|
||||
plain_password_required() -> true.
|
||||
|
||||
store_type() ->
|
||||
external.
|
||||
store_type() -> external.
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
get_pam_service(Host) ->
|
||||
case ejabberd_config:get_local_option({pam_service, Host}) of
|
||||
undefined -> "ejabberd";
|
||||
Service -> Service
|
||||
end.
|
||||
ejabberd_config:get_local_option(
|
||||
{pam_service, Host},
|
||||
fun iolist_to_binary/1,
|
||||
<<"ejabberd">>).
|
||||
|
||||
get_pam_userinfotype(Host) ->
|
||||
case ejabberd_config:get_local_option({pam_userinfotype, Host}) of
|
||||
undefined -> username;
|
||||
Type -> Type
|
||||
end.
|
||||
ejabberd_config:get_local_option(
|
||||
{pam_userinfotype, Host},
|
||||
fun(username) -> username;
|
||||
(jid) -> jid
|
||||
end,
|
||||
username).
|
||||
|
2270
src/ejabberd_c2s.erl
2270
src/ejabberd_c2s.erl
File diff suppressed because it is too large
Load Diff
@ -26,6 +26,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_c2s_config).
|
||||
|
||||
-author('mremond@process-one.net').
|
||||
|
||||
-export([get_c2s_limits/0]).
|
||||
@ -33,28 +34,33 @@
|
||||
%% Get first c2s configuration limitations to apply it to other c2s
|
||||
%% connectors.
|
||||
get_c2s_limits() ->
|
||||
case ejabberd_config:get_local_option(listen) of
|
||||
undefined ->
|
||||
[];
|
||||
case ejabberd_config:get_local_option(listen, fun(V) -> V end) of
|
||||
undefined -> [];
|
||||
C2SFirstListen ->
|
||||
case lists:keysearch(ejabberd_c2s, 2, C2SFirstListen) of
|
||||
false ->
|
||||
[];
|
||||
false -> [];
|
||||
{value, {_Port, ejabberd_c2s, Opts}} ->
|
||||
select_opts_values(Opts)
|
||||
end
|
||||
end.
|
||||
%% Only get access, shaper and max_stanza_size values
|
||||
|
||||
select_opts_values(Opts) ->
|
||||
select_opts_values(Opts, []).
|
||||
|
||||
select_opts_values([], SelectedValues) ->
|
||||
SelectedValues;
|
||||
select_opts_values([{access,Value}|Opts], SelectedValues) ->
|
||||
select_opts_values(Opts, [{access, Value}|SelectedValues]);
|
||||
select_opts_values([{shaper,Value}|Opts], 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([{access, Value} | Opts],
|
||||
SelectedValues) ->
|
||||
select_opts_values(Opts,
|
||||
[{access, Value} | SelectedValues]);
|
||||
select_opts_values([{shaper, Value} | Opts],
|
||||
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(Opts, SelectedValues).
|
||||
|
||||
|
@ -32,36 +32,44 @@
|
||||
-export([start_link/0]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
|
||||
-export([create_captcha/6, build_captcha_html/2, check_captcha/2,
|
||||
process_reply/1, process/2, is_feature_available/0,
|
||||
create_captcha_x/5, create_captcha_x/6]).
|
||||
-export([create_captcha/6, build_captcha_html/2,
|
||||
check_captcha/2, process_reply/1, process/2,
|
||||
is_feature_available/0, create_captcha_x/5,
|
||||
create_captcha_x/6]).
|
||||
|
||||
-include("jlib.hrl").
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-include("web/ejabberd_http.hrl").
|
||||
|
||||
-define(VFIELD(Type, Var, Value),
|
||||
{xmlelement, "field", [{"type", Type}, {"var", Var}],
|
||||
[{xmlelement, "value", [], [Value]}]}).
|
||||
#xmlel{name = <<"field">>,
|
||||
attrs = [{<<"type">>, Type}, {<<"var">>, Var}],
|
||||
children =
|
||||
[#xmlel{name = <<"value">>, attrs = [],
|
||||
children = [Value]}]}).
|
||||
|
||||
-define(CAPTCHA_TEXT(Lang), translate:translate(Lang, "Enter the text you see")).
|
||||
-define(CAPTCHA_LIFETIME, 120000). % two minutes
|
||||
-define(LIMIT_PERIOD, 60*1000*1000). % one minute
|
||||
-define(CAPTCHA_TEXT(Lang),
|
||||
translate:translate(Lang,
|
||||
<<"Enter the text you see">>)).
|
||||
|
||||
-record(state, {limits = treap:empty()}).
|
||||
-record(captcha, {id, pid, key, tref, args}).
|
||||
-define(CAPTCHA_LIFETIME, 120000).
|
||||
|
||||
-define(T(S),
|
||||
case catch mnesia:transaction(fun() -> S end) of
|
||||
{atomic, Res} ->
|
||||
Res;
|
||||
{_, Reason} ->
|
||||
?ERROR_MSG("mnesia transaction failed: ~p", [Reason]),
|
||||
{error, Reason}
|
||||
end).
|
||||
-define(LIMIT_PERIOD, 60*1000*1000).
|
||||
|
||||
-type error() :: efbig | enodata | limit | malformed_image | timeout.
|
||||
|
||||
-record(state, {limits = treap:empty() :: treap:treap()}).
|
||||
|
||||
-record(captcha, {id :: binary(),
|
||||
pid :: pid(),
|
||||
key :: binary(),
|
||||
tref :: reference(),
|
||||
args :: any()}).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
@ -71,98 +79,197 @@
|
||||
%% Description: Starts the server
|
||||
%%--------------------------------------------------------------------
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [],
|
||||
[]).
|
||||
|
||||
create_captcha(SID, From, To, Lang, Limiter, Args)
|
||||
when is_list(Lang), is_list(SID),
|
||||
is_record(From, jid), is_record(To, jid) ->
|
||||
-spec create_captcha(binary(), jid(), jid(),
|
||||
binary(), any(), any()) -> {error, error()} |
|
||||
{ok, binary(), [xmlel()]}.
|
||||
|
||||
create_captcha(SID, From, To, Lang, Limiter, Args) ->
|
||||
case create_image(Limiter) of
|
||||
{ok, Type, Key, Image} ->
|
||||
Id = randoms:get_string(),
|
||||
B64Image = jlib:encode_base64(binary_to_list(Image)),
|
||||
Id = <<(randoms:get_string())/binary>>,
|
||||
B64Image = jlib:encode_base64((Image)),
|
||||
JID = jlib:jid_to_string(From),
|
||||
CID = "sha1+" ++ sha:sha(Image) ++ "@bob.xmpp.org",
|
||||
Data = {xmlelement, "data",
|
||||
[{"xmlns", ?NS_BOB}, {"cid", CID},
|
||||
{"max-age", "0"}, {"type", Type}],
|
||||
[{xmlcdata, B64Image}]},
|
||||
Captcha =
|
||||
{xmlelement, "captcha", [{"xmlns", ?NS_CAPTCHA}],
|
||||
[{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
|
||||
[?VFIELD("hidden", "FORM_TYPE", {xmlcdata, ?NS_CAPTCHA}),
|
||||
?VFIELD("hidden", "from", {xmlcdata, jlib:jid_to_string(To)}),
|
||||
?VFIELD("hidden", "challenge", {xmlcdata, Id}),
|
||||
?VFIELD("hidden", "sid", {xmlcdata, SID}),
|
||||
{xmlelement, "field", [{"var", "ocr"}, {"label", ?CAPTCHA_TEXT(Lang)}],
|
||||
[{xmlelement, "required", [], []},
|
||||
{xmlelement, "media", [{"xmlns", ?NS_MEDIA}],
|
||||
[{xmlelement, "uri", [{"type", Type}],
|
||||
[{xmlcdata, "cid:" ++ CID}]}]}]}]}]},
|
||||
BodyString1 = translate:translate(Lang, "Your messages to ~s are being blocked. To unblock them, visit ~s"),
|
||||
BodyString = io_lib:format(BodyString1, [JID, get_url(Id)]),
|
||||
Body = {xmlelement, "body", [],
|
||||
[{xmlcdata, BodyString}]},
|
||||
OOB = {xmlelement, "x", [{"xmlns", ?NS_OOB}],
|
||||
[{xmlelement, "url", [], [{xmlcdata, get_url(Id)}]}]},
|
||||
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE, {remove_id, Id}),
|
||||
case ?T(mnesia:write(#captcha{id=Id, pid=self(), key=Key,
|
||||
tref=Tref, args=Args})) of
|
||||
ok ->
|
||||
CID = <<"sha1+", (sha:sha(Image))/binary,
|
||||
"@bob.xmpp.org">>,
|
||||
Data = #xmlel{name = <<"data">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_BOB}, {<<"cid">>, CID},
|
||||
{<<"max-age">>, <<"0">>}, {<<"type">>, Type}],
|
||||
children = [{xmlcdata, B64Image}]},
|
||||
Captcha = #xmlel{name = <<"captcha">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_CAPTCHA}],
|
||||
children =
|
||||
[#xmlel{name = <<"x">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_XDATA},
|
||||
{<<"type">>, <<"form">>}],
|
||||
children =
|
||||
[?VFIELD(<<"hidden">>,
|
||||
<<"FORM_TYPE">>,
|
||||
{xmlcdata, ?NS_CAPTCHA}),
|
||||
?VFIELD(<<"hidden">>, <<"from">>,
|
||||
{xmlcdata,
|
||||
jlib:jid_to_string(To)}),
|
||||
?VFIELD(<<"hidden">>,
|
||||
<<"challenge">>,
|
||||
{xmlcdata, Id}),
|
||||
?VFIELD(<<"hidden">>, <<"sid">>,
|
||||
{xmlcdata, SID}),
|
||||
#xmlel{name = <<"field">>,
|
||||
attrs =
|
||||
[{<<"var">>, <<"ocr">>},
|
||||
{<<"label">>,
|
||||
?CAPTCHA_TEXT(Lang)}],
|
||||
children =
|
||||
[#xmlel{name =
|
||||
<<"required">>,
|
||||
attrs = [],
|
||||
children = []},
|
||||
#xmlel{name =
|
||||
<<"media">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>,
|
||||
?NS_MEDIA}],
|
||||
children =
|
||||
[#xmlel{name
|
||||
=
|
||||
<<"uri">>,
|
||||
attrs
|
||||
=
|
||||
[{<<"type">>,
|
||||
Type}],
|
||||
children
|
||||
=
|
||||
[{xmlcdata,
|
||||
<<"cid:",
|
||||
CID/binary>>}]}]}]}]}]},
|
||||
BodyString1 = translate:translate(Lang,
|
||||
<<"Your messages to ~s are being blocked. "
|
||||
"To unblock them, visit ~s">>),
|
||||
BodyString = iolist_to_binary(io_lib:format(BodyString1,
|
||||
[JID, get_url(Id)])),
|
||||
Body = #xmlel{name = <<"body">>, attrs = [],
|
||||
children = [{xmlcdata, BodyString}]},
|
||||
OOB = #xmlel{name = <<"x">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_OOB}],
|
||||
children =
|
||||
[#xmlel{name = <<"url">>, attrs = [],
|
||||
children = [{xmlcdata, get_url(Id)}]}]},
|
||||
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE,
|
||||
{remove_id, Id}),
|
||||
ets:insert(captcha,
|
||||
#captcha{id = Id, pid = self(), key = Key, tref = Tref,
|
||||
args = Args}),
|
||||
{ok, Id, [Body, OOB, Captcha, Data]};
|
||||
Err ->
|
||||
{error, Err}
|
||||
end;
|
||||
Err ->
|
||||
Err
|
||||
Err -> Err
|
||||
end.
|
||||
|
||||
-spec create_captcha_x(binary(), jid(), binary(),
|
||||
any(), [xmlel()]) -> {ok, [xmlel()]} |
|
||||
{error, error()}.
|
||||
|
||||
create_captcha_x(SID, To, Lang, Limiter, HeadEls) ->
|
||||
create_captcha_x(SID, To, Lang, Limiter, HeadEls, []).
|
||||
|
||||
create_captcha_x(SID, To, Lang, Limiter, HeadEls, TailEls) ->
|
||||
-spec create_captcha_x(binary(), jid(), binary(),
|
||||
any(), [xmlel()], [xmlel()]) -> {ok, [xmlel()]} |
|
||||
{error, error()}.
|
||||
|
||||
create_captcha_x(SID, To, Lang, Limiter, HeadEls,
|
||||
TailEls) ->
|
||||
case create_image(Limiter) of
|
||||
{ok, Type, Key, Image} ->
|
||||
Id = randoms:get_string(),
|
||||
B64Image = jlib:encode_base64(binary_to_list(Image)),
|
||||
CID = "sha1+" ++ sha:sha(Image) ++ "@bob.xmpp.org",
|
||||
Data = {xmlelement, "data",
|
||||
[{"xmlns", ?NS_BOB}, {"cid", CID},
|
||||
{"max-age", "0"}, {"type", Type}],
|
||||
[{xmlcdata, B64Image}]},
|
||||
HelpTxt = translate:translate(
|
||||
Lang,
|
||||
"If you don't see the CAPTCHA image here, "
|
||||
"visit the web page."),
|
||||
Imageurl = get_url(Id ++ "/image"),
|
||||
Captcha =
|
||||
{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
|
||||
[?VFIELD("hidden", "FORM_TYPE", {xmlcdata, ?NS_CAPTCHA}) | HeadEls] ++
|
||||
[{xmlelement, "field", [{"type", "fixed"}],
|
||||
[{xmlelement, "value", [], [{xmlcdata, HelpTxt}]}]},
|
||||
{xmlelement, "field", [{"type", "hidden"}, {"var", "captchahidden"}],
|
||||
[{xmlelement, "value", [], [{xmlcdata, "workaround-for-psi"}]}]},
|
||||
{xmlelement, "field",
|
||||
[{"type", "text-single"},
|
||||
{"label", translate:translate(Lang, "CAPTCHA web page")},
|
||||
{"var", "url"}],
|
||||
[{xmlelement, "value", [], [{xmlcdata, Imageurl}]}]},
|
||||
?VFIELD("hidden", "from", {xmlcdata, jlib:jid_to_string(To)}),
|
||||
?VFIELD("hidden", "challenge", {xmlcdata, Id}),
|
||||
?VFIELD("hidden", "sid", {xmlcdata, SID}),
|
||||
{xmlelement, "field", [{"var", "ocr"}, {"label", ?CAPTCHA_TEXT(Lang)}],
|
||||
[{xmlelement, "required", [], []},
|
||||
{xmlelement, "media", [{"xmlns", ?NS_MEDIA}],
|
||||
[{xmlelement, "uri", [{"type", Type}],
|
||||
[{xmlcdata, "cid:" ++ CID}]}]}]}] ++ TailEls},
|
||||
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE, {remove_id, Id}),
|
||||
case ?T(mnesia:write(#captcha{id=Id, key=Key, tref=Tref})) of
|
||||
ok ->
|
||||
Id = <<(randoms:get_string())/binary>>,
|
||||
B64Image = jlib:encode_base64((Image)),
|
||||
CID = <<"sha1+", (sha:sha(Image))/binary,
|
||||
"@bob.xmpp.org">>,
|
||||
Data = #xmlel{name = <<"data">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_BOB}, {<<"cid">>, CID},
|
||||
{<<"max-age">>, <<"0">>}, {<<"type">>, Type}],
|
||||
children = [{xmlcdata, B64Image}]},
|
||||
HelpTxt = translate:translate(Lang,
|
||||
<<"If you don't see the CAPTCHA image here, "
|
||||
"visit the web page.">>),
|
||||
Imageurl = get_url(<<Id/binary, "/image">>),
|
||||
Captcha = #xmlel{name = <<"x">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_XDATA},
|
||||
{<<"type">>, <<"form">>}],
|
||||
children =
|
||||
[?VFIELD(<<"hidden">>, <<"FORM_TYPE">>,
|
||||
{xmlcdata, ?NS_CAPTCHA})
|
||||
| HeadEls]
|
||||
++
|
||||
[#xmlel{name = <<"field">>,
|
||||
attrs = [{<<"type">>, <<"fixed">>}],
|
||||
children =
|
||||
[#xmlel{name = <<"value">>,
|
||||
attrs = [],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
HelpTxt}]}]},
|
||||
#xmlel{name = <<"field">>,
|
||||
attrs =
|
||||
[{<<"type">>, <<"hidden">>},
|
||||
{<<"var">>, <<"captchahidden">>}],
|
||||
children =
|
||||
[#xmlel{name = <<"value">>,
|
||||
attrs = [],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
<<"workaround-for-psi">>}]}]},
|
||||
#xmlel{name = <<"field">>,
|
||||
attrs =
|
||||
[{<<"type">>, <<"text-single">>},
|
||||
{<<"label">>,
|
||||
translate:translate(Lang,
|
||||
<<"CAPTCHA web page">>)},
|
||||
{<<"var">>, <<"url">>}],
|
||||
children =
|
||||
[#xmlel{name = <<"value">>,
|
||||
attrs = [],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
Imageurl}]}]},
|
||||
?VFIELD(<<"hidden">>, <<"from">>,
|
||||
{xmlcdata, jlib:jid_to_string(To)}),
|
||||
?VFIELD(<<"hidden">>, <<"challenge">>,
|
||||
{xmlcdata, Id}),
|
||||
?VFIELD(<<"hidden">>, <<"sid">>,
|
||||
{xmlcdata, SID}),
|
||||
#xmlel{name = <<"field">>,
|
||||
attrs =
|
||||
[{<<"var">>, <<"ocr">>},
|
||||
{<<"label">>,
|
||||
?CAPTCHA_TEXT(Lang)}],
|
||||
children =
|
||||
[#xmlel{name = <<"required">>,
|
||||
attrs = [], children = []},
|
||||
#xmlel{name = <<"media">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>,
|
||||
?NS_MEDIA}],
|
||||
children =
|
||||
[#xmlel{name =
|
||||
<<"uri">>,
|
||||
attrs =
|
||||
[{<<"type">>,
|
||||
Type}],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
<<"cid:",
|
||||
CID/binary>>}]}]}]}]
|
||||
++ TailEls},
|
||||
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE,
|
||||
{remove_id, Id}),
|
||||
ets:insert(captcha,
|
||||
#captcha{id = Id, key = Key, tref = Tref}),
|
||||
{ok, [Captcha, Data]};
|
||||
Err ->
|
||||
{error, Err}
|
||||
end;
|
||||
Err ->
|
||||
Err
|
||||
Err -> Err
|
||||
end.
|
||||
|
||||
%% @spec (Id::string(), Lang::string()) -> {FormEl, {ImgEl, TextEl, IdEl, KeyEl}} | captcha_not_found
|
||||
@ -171,206 +278,177 @@ create_captcha_x(SID, To, Lang, Limiter, HeadEls, TailEls) ->
|
||||
%% TextEl = xmlelement()
|
||||
%% IdEl = xmlelement()
|
||||
%% KeyEl = xmlelement()
|
||||
-spec build_captcha_html(binary(), binary()) -> captcha_not_found |
|
||||
{xmlel(),
|
||||
{xmlel(), xmlel(),
|
||||
xmlel(), xmlel()}}.
|
||||
|
||||
build_captcha_html(Id, Lang) ->
|
||||
case mnesia:dirty_read(captcha, Id) of
|
||||
[#captcha{}] ->
|
||||
ImgEl = {xmlelement, "img", [{"src", get_url(Id ++ "/image")}], []},
|
||||
case lookup_captcha(Id) of
|
||||
{ok, _} ->
|
||||
ImgEl = #xmlel{name = <<"img">>,
|
||||
attrs =
|
||||
[{<<"src">>, get_url(<<Id/binary, "/image">>)}],
|
||||
children = []},
|
||||
TextEl = {xmlcdata, ?CAPTCHA_TEXT(Lang)},
|
||||
IdEl = {xmlelement, "input", [{"type", "hidden"},
|
||||
{"name", "id"},
|
||||
{"value", Id}], []},
|
||||
KeyEl = {xmlelement, "input", [{"type", "text"},
|
||||
{"name", "key"},
|
||||
{"size", "10"}], []},
|
||||
FormEl = {xmlelement, "form", [{"action", get_url(Id)},
|
||||
{"name", "captcha"},
|
||||
{"method", "POST"}],
|
||||
IdEl = #xmlel{name = <<"input">>,
|
||||
attrs =
|
||||
[{<<"type">>, <<"hidden">>}, {<<"name">>, <<"id">>},
|
||||
{<<"value">>, Id}],
|
||||
children = []},
|
||||
KeyEl = #xmlel{name = <<"input">>,
|
||||
attrs =
|
||||
[{<<"type">>, <<"text">>}, {<<"name">>, <<"key">>},
|
||||
{<<"size">>, <<"10">>}],
|
||||
children = []},
|
||||
FormEl = #xmlel{name = <<"form">>,
|
||||
attrs =
|
||||
[{<<"action">>, get_url(Id)},
|
||||
{<<"name">>, <<"captcha">>},
|
||||
{<<"method">>, <<"POST">>}],
|
||||
children =
|
||||
[ImgEl,
|
||||
{xmlelement, "br", [], []},
|
||||
#xmlel{name = <<"br">>, attrs = [],
|
||||
children = []},
|
||||
TextEl,
|
||||
{xmlelement, "br", [], []},
|
||||
IdEl,
|
||||
KeyEl,
|
||||
{xmlelement, "br", [], []},
|
||||
{xmlelement, "input", [{"type", "submit"},
|
||||
{"name", "enter"},
|
||||
{"value", "OK"}], []}
|
||||
]},
|
||||
#xmlel{name = <<"br">>, attrs = [],
|
||||
children = []},
|
||||
IdEl, KeyEl,
|
||||
#xmlel{name = <<"br">>, attrs = [],
|
||||
children = []},
|
||||
#xmlel{name = <<"input">>,
|
||||
attrs =
|
||||
[{<<"type">>, <<"submit">>},
|
||||
{<<"name">>, <<"enter">>},
|
||||
{<<"value">>, <<"OK">>}],
|
||||
children = []}]},
|
||||
{FormEl, {ImgEl, TextEl, IdEl, KeyEl}};
|
||||
_ ->
|
||||
captcha_not_found
|
||||
_ -> captcha_not_found
|
||||
end.
|
||||
|
||||
%% @spec (Id::string(), ProvidedKey::string()) -> captcha_valid | captcha_non_valid | captcha_not_found
|
||||
check_captcha(Id, ProvidedKey) ->
|
||||
?T(case mnesia:read(captcha, Id, write) of
|
||||
[#captcha{pid=Pid, args=Args, key=StoredKey, tref=Tref}] ->
|
||||
mnesia:delete({captcha, Id}),
|
||||
erlang:cancel_timer(Tref),
|
||||
if StoredKey == ProvidedKey ->
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_succeed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
captcha_valid;
|
||||
true ->
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_failed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
captcha_non_valid
|
||||
end;
|
||||
_ ->
|
||||
captcha_not_found
|
||||
end).
|
||||
-spec check_captcha(binary(), binary()) -> captcha_not_found |
|
||||
captcha_valid |
|
||||
captcha_non_valid.
|
||||
|
||||
|
||||
process_reply({xmlelement, _, _, _} = El) ->
|
||||
case xml:get_subtag(El, "x") of
|
||||
false ->
|
||||
{error, malformed};
|
||||
-spec process_reply(xmlel()) -> ok | {error, bad_match | not_found | malformed}.
|
||||
|
||||
process_reply(#xmlel{} = El) ->
|
||||
case xml:get_subtag(El, <<"x">>) of
|
||||
false -> {error, malformed};
|
||||
Xdata ->
|
||||
Fields = jlib:parse_xdata_submit(Xdata),
|
||||
case catch {proplists:get_value("challenge", Fields),
|
||||
proplists:get_value("ocr", Fields)} of
|
||||
{[Id|_], [OCR|_]} ->
|
||||
?T(case mnesia:read(captcha, Id, write) of
|
||||
[#captcha{pid=Pid, args=Args, key=Key, tref=Tref}] ->
|
||||
mnesia:delete({captcha, Id}),
|
||||
erlang:cancel_timer(Tref),
|
||||
if OCR == Key ->
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_succeed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
ok;
|
||||
true ->
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_failed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{error, bad_match}
|
||||
case catch {proplists:get_value(<<"challenge">>,
|
||||
Fields),
|
||||
proplists:get_value(<<"ocr">>, Fields)}
|
||||
of
|
||||
{[Id | _], [OCR | _]} ->
|
||||
case check_captcha(Id, OCR) of
|
||||
captcha_valid -> ok;
|
||||
captcha_non_valid -> {error, bad_match};
|
||||
captcha_not_found -> {error, not_found}
|
||||
end;
|
||||
_ ->
|
||||
{error, not_found}
|
||||
end);
|
||||
_ ->
|
||||
{error, malformed}
|
||||
_ -> {error, malformed}
|
||||
end
|
||||
end;
|
||||
process_reply(_) ->
|
||||
{error, malformed}.
|
||||
process_reply(_) -> {error, malformed}.
|
||||
|
||||
|
||||
process(_Handlers, #request{method='GET', lang=Lang, path=[_, Id]}) ->
|
||||
process(_Handlers,
|
||||
#request{method = 'GET', lang = Lang,
|
||||
path = [_, Id]}) ->
|
||||
case build_captcha_html(Id, Lang) of
|
||||
{FormEl, _} when is_tuple(FormEl) ->
|
||||
Form =
|
||||
{xmlelement, "div", [{"align", "center"}],
|
||||
[FormEl]},
|
||||
Form = #xmlel{name = <<"div">>,
|
||||
attrs = [{<<"align">>, <<"center">>}],
|
||||
children = [FormEl]},
|
||||
ejabberd_web:make_xhtml([Form]);
|
||||
captcha_not_found ->
|
||||
ejabberd_web:error(not_found)
|
||||
captcha_not_found -> ejabberd_web:error(not_found)
|
||||
end;
|
||||
|
||||
process(_Handlers, #request{method='GET', path=[_, Id, "image"], ip = IP}) ->
|
||||
process(_Handlers,
|
||||
#request{method = 'GET', path = [_, Id, <<"image">>],
|
||||
ip = IP}) ->
|
||||
{Addr, _Port} = IP,
|
||||
case mnesia:dirty_read(captcha, Id) of
|
||||
[#captcha{key=Key}] ->
|
||||
case lookup_captcha(Id) of
|
||||
{ok, #captcha{key = Key}} ->
|
||||
case create_image(Addr, Key) of
|
||||
{ok, Type, _, Img} ->
|
||||
{200,
|
||||
[{"Content-Type", Type},
|
||||
{"Cache-Control", "no-cache"},
|
||||
{"Last-Modified", httpd_util:rfc1123_date()}],
|
||||
[{<<"Content-Type">>, Type},
|
||||
{<<"Cache-Control">>, <<"no-cache">>},
|
||||
{<<"Last-Modified">>, list_to_binary(httpd_util:rfc1123_date())}],
|
||||
Img};
|
||||
{error, limit} ->
|
||||
ejabberd_web:error(not_allowed);
|
||||
_ ->
|
||||
ejabberd_web:error(not_found)
|
||||
{error, limit} -> ejabberd_web:error(not_allowed);
|
||||
_ -> ejabberd_web:error(not_found)
|
||||
end;
|
||||
_ ->
|
||||
ejabberd_web:error(not_found)
|
||||
_ -> ejabberd_web:error(not_found)
|
||||
end;
|
||||
|
||||
process(_Handlers, #request{method='POST', q=Q, lang=Lang, path=[_, Id]}) ->
|
||||
ProvidedKey = proplists:get_value("key", Q, none),
|
||||
process(_Handlers,
|
||||
#request{method = 'POST', q = Q, lang = Lang,
|
||||
path = [_, Id]}) ->
|
||||
ProvidedKey = proplists:get_value(<<"key">>, Q, none),
|
||||
case check_captcha(Id, ProvidedKey) of
|
||||
captcha_valid ->
|
||||
Form =
|
||||
{xmlelement, "p", [],
|
||||
Form = #xmlel{name = <<"p">>, attrs = [],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
translate:translate(Lang, "The CAPTCHA is valid.")
|
||||
}]},
|
||||
translate:translate(Lang,
|
||||
<<"The CAPTCHA is valid.">>)}]},
|
||||
ejabberd_web:make_xhtml([Form]);
|
||||
captcha_non_valid ->
|
||||
ejabberd_web:error(not_allowed);
|
||||
captcha_not_found ->
|
||||
ejabberd_web:error(not_found)
|
||||
captcha_non_valid -> ejabberd_web:error(not_allowed);
|
||||
captcha_not_found -> ejabberd_web:error(not_found)
|
||||
end;
|
||||
|
||||
process(_Handlers, _Request) ->
|
||||
ejabberd_web:error(not_found).
|
||||
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
init([]) ->
|
||||
mnesia:create_table(captcha,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, captcha)}]),
|
||||
mnesia:add_table_copy(captcha, node(), ram_copies),
|
||||
mnesia:delete_table(captcha),
|
||||
ets:new(captcha,
|
||||
[named_table, public, {keypos, #captcha.id}]),
|
||||
check_captcha_setup(),
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call({is_limited, Limiter, RateLimit}, _From, State) ->
|
||||
handle_call({is_limited, Limiter, RateLimit}, _From,
|
||||
State) ->
|
||||
NowPriority = now_priority(),
|
||||
CleanPriority = NowPriority + ?LIMIT_PERIOD,
|
||||
CleanPriority = NowPriority + (?LIMIT_PERIOD),
|
||||
Limits = clean_treap(State#state.limits, CleanPriority),
|
||||
case treap:lookup(Limiter, Limits) of
|
||||
{ok, _, Rate} when Rate >= RateLimit ->
|
||||
{reply, true, State#state{limits = Limits}};
|
||||
{ok, Priority, Rate} ->
|
||||
NewLimits = treap:insert(Limiter, Priority, Rate+1, Limits),
|
||||
NewLimits = treap:insert(Limiter, Priority, Rate + 1,
|
||||
Limits),
|
||||
{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}}
|
||||
end;
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, bad_request, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
handle_cast(_Msg, State) -> {noreply, State}.
|
||||
|
||||
handle_info({remove_id, Id}, State) ->
|
||||
?DEBUG("captcha ~p timed out", [Id]),
|
||||
_ = ?T(case mnesia:read(captcha, Id, write) of
|
||||
[#captcha{args=Args, pid=Pid}] ->
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_failed, Args};
|
||||
true ->
|
||||
ok
|
||||
case ets:lookup(captcha, Id) of
|
||||
[#captcha{args = Args, pid = Pid}] ->
|
||||
if is_pid(Pid) -> Pid ! {captcha_failed, Args};
|
||||
true -> ok
|
||||
end,
|
||||
ets:delete(captcha, Id);
|
||||
_ -> ok
|
||||
end,
|
||||
mnesia:delete({captcha, Id});
|
||||
_ ->
|
||||
ok
|
||||
end),
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) -> {noreply, State}.
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
terminate(_Reason, _State) -> ok.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
@ -382,35 +460,35 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
%% Image = binary()
|
||||
%% Reason = atom()
|
||||
%%--------------------------------------------------------------------
|
||||
create_image() ->
|
||||
create_image(undefined).
|
||||
create_image() -> create_image(undefined).
|
||||
|
||||
create_image(Limiter) ->
|
||||
%% Six numbers from 1 to 9.
|
||||
Key = string:substr(randoms:get_string(), 1, 6),
|
||||
Key = str:substr(randoms:get_string(), 1, 6),
|
||||
create_image(Limiter, Key).
|
||||
|
||||
create_image(Limiter, Key) ->
|
||||
case is_limited(Limiter) of
|
||||
true ->
|
||||
{error, limit};
|
||||
false ->
|
||||
do_create_image(Key)
|
||||
true -> {error, limit};
|
||||
false -> do_create_image(Key)
|
||||
end.
|
||||
|
||||
do_create_image(Key) ->
|
||||
FileName = get_prog_name(),
|
||||
Cmd = lists:flatten(io_lib:format("~s ~s", [FileName, Key])),
|
||||
case cmd(Cmd) of
|
||||
{ok, <<16#89, $P, $N, $G, $\r, $\n, 16#1a, $\n, _/binary>> = Img} ->
|
||||
{ok, "image/png", Key, Img};
|
||||
{ok, <<16#ff, 16#d8, _/binary>> = Img} ->
|
||||
{ok, "image/jpeg", Key, Img};
|
||||
{ok, <<$G, $I, $F, $8, X, $a, _/binary>> = Img} when X==$7; X==$9 ->
|
||||
{ok, "image/gif", Key, Img};
|
||||
{ok,
|
||||
<<137, $P, $N, $G, $\r, $\n, 26, $\n, _/binary>> =
|
||||
Img} ->
|
||||
{ok, <<"image/png">>, Key, Img};
|
||||
{ok, <<255, 216, _/binary>> = Img} ->
|
||||
{ok, <<"image/jpeg">>, Key, Img};
|
||||
{ok, <<$G, $I, $F, $8, X, $a, _/binary>> = Img}
|
||||
when X == $7; X == $9 ->
|
||||
{ok, <<"image/gif">>, Key, Img};
|
||||
{error, enodata = Reason} ->
|
||||
?ERROR_MSG("Failed to process output from \"~s\". "
|
||||
"Maybe ImageMagick's Convert program is not installed.",
|
||||
"Maybe ImageMagick's Convert program "
|
||||
"is not installed.",
|
||||
[Cmd]),
|
||||
{error, Reason};
|
||||
{error, Reason} ->
|
||||
@ -425,83 +503,93 @@ do_create_image(Key) ->
|
||||
end.
|
||||
|
||||
get_prog_name() ->
|
||||
case ejabberd_config:get_local_option(captcha_cmd) of
|
||||
FileName when is_list(FileName) ->
|
||||
FileName;
|
||||
Value when (Value == undefined) or (Value == "") ->
|
||||
?DEBUG("The option captcha_cmd is not configured, but some "
|
||||
"module wants to use the CAPTCHA feature.", []),
|
||||
false
|
||||
case ejabberd_config:get_local_option(
|
||||
captcha_cmd,
|
||||
fun(FileName) ->
|
||||
F = iolist_to_binary(FileName),
|
||||
if F /= <<"">> -> F end
|
||||
end) of
|
||||
undefined ->
|
||||
?DEBUG("The option captcha_cmd is not configured, "
|
||||
"but some module wants to use the CAPTCHA "
|
||||
"feature.",
|
||||
[]),
|
||||
false;
|
||||
FileName ->
|
||||
FileName
|
||||
end.
|
||||
|
||||
get_url(Str) ->
|
||||
CaptchaHost = ejabberd_config:get_local_option(captcha_host),
|
||||
case string:tokens(CaptchaHost, ":") of
|
||||
CaptchaHost = ejabberd_config:get_local_option(
|
||||
captcha_host,
|
||||
fun iolist_to_binary/1,
|
||||
<<"">>),
|
||||
case str:tokens(CaptchaHost, <<":">>) of
|
||||
[Host] ->
|
||||
"http://" ++ Host ++ "/captcha/" ++ Str;
|
||||
["http"++_ = TransferProt, Host] ->
|
||||
TransferProt ++ ":" ++ Host ++ "/captcha/" ++ Str;
|
||||
<<"http://", Host/binary, "/captcha/", Str/binary>>;
|
||||
[<<"http", _/binary>> = TransferProt, Host] ->
|
||||
<<TransferProt/binary, ":", Host/binary, "/captcha/",
|
||||
Str/binary>>;
|
||||
[Host, PortString] ->
|
||||
TransferProt = atom_to_list(get_transfer_protocol(PortString)),
|
||||
TransferProt ++ "://" ++ Host ++ ":" ++ PortString ++ "/captcha/" ++ Str;
|
||||
TransferProt =
|
||||
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 ++ "/captcha/" ++ Str;
|
||||
<<TransferProt/binary, ":", Host/binary, ":",
|
||||
PortString/binary, "/captcha/", Str/binary>>;
|
||||
_ ->
|
||||
"http://" ++ ?MYNAME ++ "/captcha/" ++ Str
|
||||
<<"http://", (?MYNAME)/binary, "/captcha/", Str/binary>>
|
||||
end.
|
||||
|
||||
get_transfer_protocol(PortString) ->
|
||||
PortNumber = list_to_integer(PortString),
|
||||
PortNumber = jlib:binary_to_integer(PortString),
|
||||
PortListeners = get_port_listeners(PortNumber),
|
||||
get_captcha_transfer_protocol(PortListeners).
|
||||
|
||||
get_port_listeners(PortNumber) ->
|
||||
AllListeners = ejabberd_config:get_local_option(listen),
|
||||
lists:filter(
|
||||
fun({{Port, _Ip, _Netp}, _Module1, _Opts1}) when Port == PortNumber ->
|
||||
AllListeners = ejabberd_config:get_local_option(listen, fun(V) -> V end),
|
||||
lists:filter(fun ({{Port, _Ip, _Netp}, _Module1,
|
||||
_Opts1})
|
||||
when Port == PortNumber ->
|
||||
true;
|
||||
(_) ->
|
||||
false
|
||||
(_) -> false
|
||||
end,
|
||||
AllListeners).
|
||||
|
||||
get_captcha_transfer_protocol([]) ->
|
||||
throw("The port number mentioned in captcha_host is not "
|
||||
"a ejabberd_http listener with 'captcha' option. "
|
||||
"Change the port number or specify http:// in that option.");
|
||||
get_captcha_transfer_protocol([{{_Port, _Ip, tcp}, ejabberd_http, Opts}
|
||||
throw(<<"The port number mentioned in captcha_host "
|
||||
"is not a ejabberd_http listener with "
|
||||
"'captcha' option. Change the port number "
|
||||
"or specify http:// in that option.">>);
|
||||
get_captcha_transfer_protocol([{{_Port, _Ip, tcp},
|
||||
ejabberd_http, Opts}
|
||||
| Listeners]) ->
|
||||
case lists:member(captcha, Opts) of
|
||||
true ->
|
||||
case lists:member(tls, Opts) of
|
||||
true ->
|
||||
https;
|
||||
false ->
|
||||
http
|
||||
true -> https;
|
||||
false -> http
|
||||
end;
|
||||
false ->
|
||||
get_captcha_transfer_protocol(Listeners)
|
||||
false -> get_captcha_transfer_protocol(Listeners)
|
||||
end;
|
||||
get_captcha_transfer_protocol([_ | Listeners]) ->
|
||||
get_captcha_transfer_protocol(Listeners).
|
||||
|
||||
is_limited(undefined) ->
|
||||
false;
|
||||
is_limited(undefined) -> false;
|
||||
is_limited(Limiter) ->
|
||||
case ejabberd_config:get_local_option(captcha_limit) of
|
||||
Int when is_integer(Int), Int > 0 ->
|
||||
case catch gen_server:call(?MODULE, {is_limited, Limiter, Int},
|
||||
5000) of
|
||||
true ->
|
||||
true;
|
||||
false ->
|
||||
false;
|
||||
Err ->
|
||||
?ERROR_MSG("Call failed: ~p", [Err]),
|
||||
false
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
case ejabberd_config:get_local_option(
|
||||
captcha_limit,
|
||||
fun(I) when is_integer(I), I > 0 -> I end) of
|
||||
undefined -> false;
|
||||
Int ->
|
||||
case catch gen_server:call(?MODULE,
|
||||
{is_limited, Limiter, Int}, 5000)
|
||||
of
|
||||
true -> true;
|
||||
false -> false;
|
||||
Err -> ?ERROR_MSG("Call failed: ~p", [Err]), false
|
||||
end
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@ -511,28 +599,27 @@ is_limited(Limiter) ->
|
||||
%% Description: os:cmd/1 replacement
|
||||
%%--------------------------------------------------------------------
|
||||
-define(CMD_TIMEOUT, 5000).
|
||||
-define(MAX_FILE_SIZE, 64*1024).
|
||||
|
||||
-define(MAX_FILE_SIZE, 64 * 1024).
|
||||
|
||||
cmd(Cmd) ->
|
||||
Port = open_port({spawn, Cmd}, [stream, eof, binary]),
|
||||
TRef = erlang:start_timer(?CMD_TIMEOUT, self(), timeout),
|
||||
TRef = erlang:start_timer(?CMD_TIMEOUT, self(),
|
||||
timeout),
|
||||
recv_data(Port, TRef, <<>>).
|
||||
|
||||
recv_data(Port, TRef, Buf) ->
|
||||
receive
|
||||
{Port, {data, Bytes}} ->
|
||||
NewBuf = <<Buf/binary, Bytes/binary>>,
|
||||
if size(NewBuf) > ?MAX_FILE_SIZE ->
|
||||
if byte_size(NewBuf) > (?MAX_FILE_SIZE) ->
|
||||
return(Port, TRef, {error, efbig});
|
||||
true ->
|
||||
recv_data(Port, TRef, NewBuf)
|
||||
true -> recv_data(Port, TRef, NewBuf)
|
||||
end;
|
||||
{Port, {data, _}} ->
|
||||
return(Port, TRef, {error, efbig});
|
||||
{Port, {data, _}} -> return(Port, TRef, {error, efbig});
|
||||
{Port, eof} when Buf /= <<>> ->
|
||||
return(Port, TRef, {ok, Buf});
|
||||
{Port, eof} ->
|
||||
return(Port, TRef, {error, enodata});
|
||||
{Port, eof} -> return(Port, TRef, {error, enodata});
|
||||
{timeout, TRef, _} ->
|
||||
return(Port, TRef, {error, timeout})
|
||||
end.
|
||||
@ -540,21 +627,15 @@ recv_data(Port, TRef, Buf) ->
|
||||
return(Port, TRef, Result) ->
|
||||
case erlang:cancel_timer(TRef) of
|
||||
false ->
|
||||
receive
|
||||
{timeout, TRef, _} ->
|
||||
ok
|
||||
after 0 ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
receive {timeout, TRef, _} -> ok after 0 -> ok end;
|
||||
_ -> ok
|
||||
end,
|
||||
catch port_close(Port),
|
||||
Result.
|
||||
|
||||
is_feature_available() ->
|
||||
case get_prog_name() of
|
||||
Prog when is_list(Prog) -> true;
|
||||
Prog when is_binary(Prog) -> true;
|
||||
false -> false
|
||||
end.
|
||||
|
||||
@ -562,31 +643,53 @@ check_captcha_setup() ->
|
||||
case is_feature_available() of
|
||||
true ->
|
||||
case create_image() of
|
||||
{ok, _, _, _} ->
|
||||
ok;
|
||||
{ok, _, _, _} -> ok;
|
||||
_Err ->
|
||||
?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})
|
||||
end;
|
||||
false ->
|
||||
ok
|
||||
false -> ok
|
||||
end.
|
||||
|
||||
lookup_captcha(Id) ->
|
||||
case ets:lookup(captcha, Id) of
|
||||
[C] -> {ok, C};
|
||||
_ -> {error, enoent}
|
||||
end.
|
||||
|
||||
check_captcha(Id, ProvidedKey) ->
|
||||
case ets:lookup(captcha, Id) of
|
||||
[#captcha{pid = Pid, args = Args, key = ValidKey,
|
||||
tref = Tref}] ->
|
||||
ets:delete(captcha, Id),
|
||||
erlang:cancel_timer(Tref),
|
||||
if ValidKey == ProvidedKey ->
|
||||
if is_pid(Pid) -> Pid ! {captcha_succeed, Args};
|
||||
true -> ok
|
||||
end,
|
||||
captcha_valid;
|
||||
true ->
|
||||
if is_pid(Pid) -> Pid ! {captcha_failed, Args};
|
||||
true -> ok
|
||||
end,
|
||||
captcha_non_valid
|
||||
end;
|
||||
_ -> captcha_not_found
|
||||
end.
|
||||
|
||||
clean_treap(Treap, CleanPriority) ->
|
||||
case treap:is_empty(Treap) of
|
||||
true ->
|
||||
Treap;
|
||||
true -> Treap;
|
||||
false ->
|
||||
{_Key, Priority, _Value} = treap:get_root(Treap),
|
||||
if
|
||||
Priority > CleanPriority ->
|
||||
if Priority > CleanPriority ->
|
||||
clean_treap(treap:delete_root(Treap), CleanPriority);
|
||||
true ->
|
||||
Treap
|
||||
true -> Treap
|
||||
end
|
||||
end.
|
||||
|
||||
now_priority() ->
|
||||
{MSec, Sec, USec} = now(),
|
||||
-((MSec*1000000 + Sec)*1000000 + USec).
|
||||
-((MSec * 1000000 + Sec) * 1000000 + USec).
|
||||
|
@ -31,8 +31,6 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_config.hrl").
|
||||
|
||||
-compile([export_all]).
|
||||
|
||||
%% TODO:
|
||||
%% We want to implement library checking at launch time to issue
|
||||
%% human readable user messages.
|
||||
@ -87,7 +85,7 @@ get_db_used() ->
|
||||
fun([Domain, DB], Acc) ->
|
||||
case check_odbc_option(
|
||||
ejabberd_config:get_local_option(
|
||||
{auth_method, Domain})) of
|
||||
{auth_method, Domain}, fun(V) -> V end)) of
|
||||
true -> [get_db_type(DB)|Acc];
|
||||
_ -> Acc
|
||||
end
|
||||
|
@ -228,7 +228,8 @@ init() ->
|
||||
ets:new(ejabberd_commands, [named_table, set, public,
|
||||
{keypos, #ejabberd_commands.name}]).
|
||||
|
||||
%% @spec ([ejabberd_commands()]) -> ok
|
||||
-spec register_commands([ejabberd_commands()]) -> ok.
|
||||
|
||||
%% @doc Register ejabberd commands.
|
||||
%% If a command is already registered, a warning is printed and the old command is preserved.
|
||||
register_commands(Commands) ->
|
||||
@ -243,7 +244,8 @@ register_commands(Commands) ->
|
||||
end,
|
||||
Commands).
|
||||
|
||||
%% @spec ([ejabberd_commands()]) -> ok
|
||||
-spec unregister_commands([ejabberd_commands()]) -> ok.
|
||||
|
||||
%% @doc Unregister ejabberd commands.
|
||||
unregister_commands(Commands) ->
|
||||
lists:foreach(
|
||||
@ -252,7 +254,8 @@ unregister_commands(Commands) ->
|
||||
end,
|
||||
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.
|
||||
list_commands() ->
|
||||
Commands = ets:match(ejabberd_commands,
|
||||
@ -262,7 +265,8 @@ list_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.
|
||||
get_command_format(Name) ->
|
||||
Matched = ets:match(ejabberd_commands,
|
||||
@ -277,7 +281,8 @@ get_command_format(Name) ->
|
||||
{Args, Result}
|
||||
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.
|
||||
get_command_definition(Name) ->
|
||||
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]),
|
||||
apply(Module, Function, Arguments).
|
||||
|
||||
-spec get_tags_commands() -> [{string(), [string()]}].
|
||||
|
||||
%% @spec () -> [{Tag::string(), [CommandName::string()]}]
|
||||
%% @doc Get all the tags and associated commands.
|
||||
get_tags_commands() ->
|
||||
@ -377,6 +384,9 @@ check_access_commands(AccessCommands, Auth, Method, Command, Arguments) ->
|
||||
L when is_list(L) -> ok
|
||||
end.
|
||||
|
||||
-spec check_auth(noauth) -> noauth_provided;
|
||||
({binary(), binary(), binary()}) -> {ok, binary(), binary()}.
|
||||
|
||||
check_auth(noauth) ->
|
||||
no_auth_provided;
|
||||
check_auth({User, Server, Password}) ->
|
||||
@ -391,7 +401,7 @@ check_access(all, _) ->
|
||||
check_access(Access, Auth) ->
|
||||
{ok, User, Server} = check_auth(Auth),
|
||||
%% 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;
|
||||
deny -> false
|
||||
end.
|
||||
|
@ -19,10 +19,32 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-record(ejabberd_commands, {name, tags = [],
|
||||
desc = "", longdesc = "",
|
||||
module, function,
|
||||
args = [], result = rescode}).
|
||||
-type aterm() :: {atom(), atype()}.
|
||||
-type atype() :: integer | string | binary |
|
||||
{tuple, [aterm()]} | {list, aterm()}.
|
||||
-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{
|
||||
%% name = atom(),
|
||||
@ -50,3 +72,4 @@
|
||||
|
||||
%% @type rterm() = {Name::atom(), Type::rtype()}.
|
||||
%% A result term is a tuple with the term name and the term type.
|
||||
|
||||
|
@ -29,9 +29,13 @@
|
||||
|
||||
-export([start/0, load_file/1,
|
||||
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([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_config.hrl").
|
||||
@ -96,11 +100,15 @@ load_file(File) ->
|
||||
%% in which the options 'include_config_file' were parsed
|
||||
%% and the terms in those files were included.
|
||||
%% @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) ->
|
||||
File = get_absolute_path(File1),
|
||||
case file:consult(File) of
|
||||
{ok, Terms} ->
|
||||
include_config_files(Terms);
|
||||
BinTerms = strings_to_binary(Terms),
|
||||
include_config_files(BinTerms);
|
||||
{error, {LineNumber, erl_parse, _ParseMessage} = Reason} ->
|
||||
ExitText = describe_config_problem(File, Reason, LineNumber),
|
||||
?ERROR_MSG(ExitText, []),
|
||||
@ -159,7 +167,7 @@ normalize_hosts(Hosts) ->
|
||||
normalize_hosts([], PrepHosts) ->
|
||||
lists:reverse(PrepHosts);
|
||||
normalize_hosts([Host|Hosts], PrepHosts) ->
|
||||
case jlib:nodeprep(Host) of
|
||||
case jlib:nodeprep(iolist_to_binary(Host)) of
|
||||
error ->
|
||||
?ERROR_MSG("Can't load config file: "
|
||||
"invalid host name [~p]", [Host]),
|
||||
@ -564,7 +572,6 @@ set_opts(State) ->
|
||||
exit("Error reading Mnesia database")
|
||||
end.
|
||||
|
||||
|
||||
add_global_option(Opt, Val) ->
|
||||
mnesia:transaction(fun() ->
|
||||
mnesia:write(#config{key = Opt,
|
||||
@ -577,23 +584,63 @@ add_local_option(Opt, Val) ->
|
||||
value = Val})
|
||||
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
|
||||
[#config{value = Val}] ->
|
||||
Val;
|
||||
prepare_opt_val(Opt, Val, F, Default);
|
||||
_ ->
|
||||
undefined
|
||||
Default
|
||||
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
|
||||
[#local_config{value = Val}] ->
|
||||
Val;
|
||||
prepare_opt_val(Opt, Val, F, Default);
|
||||
_ ->
|
||||
undefined
|
||||
Default
|
||||
end.
|
||||
|
||||
-spec get_vh_by_auth_method(atom()) -> [binary()].
|
||||
|
||||
%% Return the list of hosts handled by a given module
|
||||
get_vh_by_auth_method(AuthMethod) ->
|
||||
mnesia:dirty_select(local_config,
|
||||
@ -613,8 +660,25 @@ is_file_readable(Path) ->
|
||||
false
|
||||
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_blocking_odbc) -> {mod_blocking, odbc};
|
||||
replace_module(mod_caps_odbc) -> {mod_caps, odbc};
|
||||
replace_module(mod_irc_odbc) -> {mod_irc, odbc};
|
||||
replace_module(mod_last_odbc) -> {mod_last, odbc};
|
||||
replace_module(mod_muc_odbc) -> {mod_muc, odbc};
|
||||
@ -632,10 +696,161 @@ replace_modules(Modules) ->
|
||||
fun({Module, Opts}) ->
|
||||
case replace_module(Module) of
|
||||
{NewModule, DBType} ->
|
||||
emit_deprecation_warning(Module, NewModule, DBType),
|
||||
NewOpts = [{db_type, DBType} |
|
||||
lists:keydelete(db_type, 1, Opts)],
|
||||
{NewModule, NewOpts};
|
||||
NewModule ->
|
||||
if Module /= NewModule ->
|
||||
emit_deprecation_warning(Module, NewModule);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{NewModule, Opts}
|
||||
end
|
||||
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(local_config, {key, value}).
|
||||
-record(state, {opts = [],
|
||||
hosts = [],
|
||||
override_local = false,
|
||||
override_global = false,
|
||||
override_acls = false}).
|
||||
-record(config, {key :: any(), value :: any()}).
|
||||
|
||||
-record(local_config, {key :: any(), value :: any()}).
|
||||
|
||||
-type config() :: #config{}.
|
||||
-type local_config() :: #local_config{}.
|
||||
|
||||
-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
|
||||
true ->
|
||||
SNode ++ "@" ++ inet_db:gethostname() ++
|
||||
"." ++ inet_db:res_option(domain);
|
||||
lists:flatten([SNode, "@", inet_db:gethostname(),
|
||||
".", inet_db:res_option(domain)]);
|
||||
false ->
|
||||
SNode ++ "@" ++ inet_db:gethostname();
|
||||
lists:flatten([SNode, "@", inet_db:gethostname()]);
|
||||
_ ->
|
||||
SNode
|
||||
end
|
||||
@ -124,6 +124,8 @@ unregister_commands(CmdDescs, Module, Function) ->
|
||||
%% Process
|
||||
%%-----------------------------
|
||||
|
||||
-spec process([string()]) -> non_neg_integer().
|
||||
|
||||
%% The commands status, stop and restart are defined here to ensure
|
||||
%% they are usable even if ejabberd is completely stopped.
|
||||
process(["status"]) ->
|
||||
@ -159,7 +161,7 @@ process(["mnesia", "info"]) ->
|
||||
mnesia:info(),
|
||||
?STATUS_SUCCESS;
|
||||
|
||||
process(["mnesia", Arg]) when is_list(Arg) ->
|
||||
process(["mnesia", Arg]) ->
|
||||
case catch mnesia:system_info(list_to_atom(Arg)) of
|
||||
{'EXIT', Error} -> ?PRINT("Error: ~p~n", [Error]);
|
||||
Return -> ?PRINT("~p~n", [Return])
|
||||
@ -190,8 +192,9 @@ process(["help" | Mode]) ->
|
||||
print_usage_help(MaxC, ShCode),
|
||||
?STATUS_SUCCESS;
|
||||
[CmdString | _] ->
|
||||
CmdStringU = ejabberd_regexp:greplace(CmdString, "-", "_"),
|
||||
print_usage_commands(CmdStringU, MaxC, ShCode),
|
||||
CmdStringU = ejabberd_regexp:greplace(
|
||||
list_to_binary(CmdString), <<"-">>, <<"_">>),
|
||||
print_usage_commands(binary_to_list(CmdStringU), MaxC, ShCode),
|
||||
?STATUS_SUCCESS
|
||||
end;
|
||||
|
||||
@ -233,11 +236,8 @@ process2(Args, Auth, AccessCommands) ->
|
||||
end.
|
||||
|
||||
get_accesscommands() ->
|
||||
case ejabberd_config:get_local_option(ejabberdctl_access_commands) of
|
||||
ACs when is_list(ACs) -> ACs;
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
ejabberd_config:get_local_option(ejabberdctl_access_commands,
|
||||
fun(V) when is_list(V) -> V end, []).
|
||||
|
||||
%%-----------------------------
|
||||
%% Command calling
|
||||
@ -281,8 +281,9 @@ try_call_command(Args, Auth, AccessCommands) ->
|
||||
|
||||
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()} | {error, ErrorType}
|
||||
call_command([CmdString | Args], Auth, AccessCommands) ->
|
||||
CmdStringU = ejabberd_regexp:greplace(CmdString, "-", "_"),
|
||||
Command = list_to_atom(CmdStringU),
|
||||
CmdStringU = ejabberd_regexp:greplace(
|
||||
list_to_binary(CmdString), <<"-">>, <<"_">>),
|
||||
Command = list_to_atom(binary_to_list(CmdStringU)),
|
||||
case ejabberd_commands:get_command_format(Command) of
|
||||
{error, command_unknown} ->
|
||||
{error, command_unknown};
|
||||
@ -331,10 +332,12 @@ format_args(Args, ArgsFormat) ->
|
||||
|
||||
format_arg(Arg, integer) ->
|
||||
format_arg2(Arg, "~d");
|
||||
format_arg(Arg, binary) ->
|
||||
list_to_binary(format_arg(Arg, string));
|
||||
format_arg("", string) ->
|
||||
"";
|
||||
format_arg(Arg, string) ->
|
||||
NumChars = integer_to_list(string:len(Arg)),
|
||||
NumChars = integer_to_list(length(Arg)),
|
||||
Parse = "~" ++ NumChars ++ "c",
|
||||
format_arg2(Arg, Parse).
|
||||
|
||||
@ -540,24 +543,25 @@ split_desc_segments(MaxL, Words) ->
|
||||
join(L, Words) ->
|
||||
join(L, Words, 0, [], []).
|
||||
|
||||
join(_L, [], _LenLastSeg, LastSeg, ResSeg) ->
|
||||
ResSeg2 = [lists:reverse(LastSeg) | ResSeg],
|
||||
lists:reverse(ResSeg2);
|
||||
join(L, [Word | Words], LenLastSeg, LastSeg, ResSeg) ->
|
||||
LWord = length(Word),
|
||||
case LWord + LenLastSeg < L of
|
||||
join(_Len, [], _CurSegLen, CurSeg, AllSegs) ->
|
||||
lists:reverse([CurSeg | AllSegs]);
|
||||
join(Len, [Word | Tail], CurSegLen, CurSeg, AllSegs) ->
|
||||
WordLen = length(Word),
|
||||
SegSize = WordLen + CurSegLen + 1,
|
||||
{NewCurSeg, NewAllSegs, NewCurSegLen} =
|
||||
if SegSize < Len ->
|
||||
{[CurSeg, " ", Word], AllSegs, SegSize};
|
||||
true ->
|
||||
%% This word fits in the last segment
|
||||
%% If this word ends with "\n", reset column counter
|
||||
case string:str(Word, "\n") of
|
||||
{Word, [CurSeg | AllSegs], WordLen}
|
||||
end,
|
||||
NewLen = case string:str(Word, "\n") of
|
||||
0 ->
|
||||
join(L, Words, LenLastSeg+LWord+1, [" ", Word | LastSeg], ResSeg);
|
||||
NewCurSegLen;
|
||||
_ ->
|
||||
join(L, Words, LWord+1, [" ", Word | LastSeg], ResSeg)
|
||||
end;
|
||||
false ->
|
||||
join(L, Words, LWord, [" ", Word], [lists:reverse(LastSeg) | ResSeg])
|
||||
end.
|
||||
0
|
||||
end,
|
||||
join(Len, Tail, NewLen, NewCurSeg, NewAllSegs).
|
||||
|
||||
|
||||
format_command_lines(CALD, MaxCmdLen, MaxC, ShCode, dual)
|
||||
when MaxC - MaxCmdLen < 40 ->
|
||||
@ -568,7 +572,8 @@ format_command_lines(CALD, MaxCmdLen, MaxC, ShCode, dual) ->
|
||||
lists:map(
|
||||
fun({Cmd, Args, CmdArgsL, 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"]
|
||||
end, CALD);
|
||||
|
||||
@ -608,7 +613,8 @@ print_usage_tags(Tag, MaxC, ShCode) ->
|
||||
end,
|
||||
CommandsList = lists:map(
|
||||
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,
|
||||
args = Args,
|
||||
desc = Desc} = C,
|
||||
@ -689,10 +695,10 @@ filter_commands(All, SubString) ->
|
||||
end.
|
||||
|
||||
filter_commands_regexp(All, Glob) ->
|
||||
RegExp = ejabberd_regexp:sh_to_awk(Glob),
|
||||
RegExp = ejabberd_regexp:sh_to_awk(list_to_binary(Glob)),
|
||||
lists:filter(
|
||||
fun(Command) ->
|
||||
case ejabberd_regexp:run(Command, RegExp) of
|
||||
case ejabberd_regexp:run(list_to_binary(Command), RegExp) of
|
||||
match ->
|
||||
true;
|
||||
nomatch ->
|
||||
|
@ -20,6 +20,9 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-define(STATUS_SUCCESS, 0).
|
||||
|
||||
-define(STATUS_ERROR, 1).
|
||||
|
||||
-define(STATUS_USAGE, 2).
|
||||
|
||||
-define(STATUS_BADRPC, 3).
|
||||
|
@ -25,6 +25,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_frontend_socket).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_server).
|
||||
@ -48,8 +49,8 @@
|
||||
sockname/1, peername/1]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
|
||||
-record(state, {sockmod, socket, receiver}).
|
||||
|
||||
@ -69,17 +70,17 @@ start_link(Module, SockMod, Socket, Opts, Receiver) ->
|
||||
start(Module, SockMod, Socket, Opts) ->
|
||||
case Module:socket_type() of
|
||||
xml_stream ->
|
||||
MaxStanzaSize =
|
||||
case lists:keysearch(max_stanza_size, 1, Opts) of
|
||||
MaxStanzaSize = case lists:keysearch(max_stanza_size, 1,
|
||||
Opts)
|
||||
of
|
||||
{value, {_, Size}} -> Size;
|
||||
_ -> infinity
|
||||
end,
|
||||
Receiver = ejabberd_receiver:start(Socket, SockMod, none, MaxStanzaSize),
|
||||
Receiver = ejabberd_receiver:start(Socket, SockMod,
|
||||
none, MaxStanzaSize),
|
||||
case SockMod:controlling_process(Socket, Receiver) of
|
||||
ok ->
|
||||
ok;
|
||||
{error, _Reason} ->
|
||||
SockMod:close(Socket)
|
||||
ok -> ok;
|
||||
{error, _Reason} -> SockMod:close(Socket)
|
||||
end,
|
||||
supervisor:start_child(ejabberd_frontend_socket_sup,
|
||||
[Module, SockMod, Socket, Opts, Receiver]);
|
||||
@ -108,8 +109,7 @@ compress(FsmRef) ->
|
||||
FsmRef.
|
||||
|
||||
compress(FsmRef, Data) ->
|
||||
gen_server:call(FsmRef, {compress, Data}),
|
||||
FsmRef.
|
||||
gen_server:call(FsmRef, {compress, Data}), FsmRef.
|
||||
|
||||
reset_stream(FsmRef) ->
|
||||
gen_server:call(FsmRef, reset_stream).
|
||||
@ -120,8 +120,7 @@ send(FsmRef, Data) ->
|
||||
change_shaper(FsmRef, Shaper) ->
|
||||
gen_server:call(FsmRef, {change_shaper, Shaper}).
|
||||
|
||||
monitor(FsmRef) ->
|
||||
erlang:monitor(process, FsmRef).
|
||||
monitor(FsmRef) -> erlang:monitor(process, FsmRef).
|
||||
|
||||
get_sockmod(FsmRef) ->
|
||||
gen_server:call(FsmRef, get_sockmod).
|
||||
@ -132,11 +131,9 @@ get_peer_certificate(FsmRef) ->
|
||||
get_verify_result(FsmRef) ->
|
||||
gen_server:call(FsmRef, get_verify_result).
|
||||
|
||||
close(FsmRef) ->
|
||||
gen_server:call(FsmRef, close).
|
||||
close(FsmRef) -> gen_server:call(FsmRef, close).
|
||||
|
||||
sockname(FsmRef) ->
|
||||
gen_server:call(FsmRef, sockname).
|
||||
sockname(FsmRef) -> gen_server:call(FsmRef, sockname).
|
||||
|
||||
peername(_FsmRef) ->
|
||||
%% TODO: Frontend improvements planned by Aleksey
|
||||
@ -156,7 +153,6 @@ peername(_FsmRef) ->
|
||||
%% Description: Initiates the server
|
||||
%%--------------------------------------------------------------------
|
||||
init([Module, SockMod, Socket, Opts, Receiver]) ->
|
||||
%% TODO: monitor the receiver
|
||||
Node = ejabberd_node_groups:get_closest_node(backend),
|
||||
{SockMod2, Socket2} = check_starttls(SockMod, Socket, Receiver, Opts),
|
||||
{ok, Pid} =
|
||||
@ -188,7 +184,8 @@ handle_call({starttls, TLSOpts, Data}, _From, State) ->
|
||||
catch (State#state.sockmod):send(
|
||||
State#state.socket, Data),
|
||||
Reply = ok,
|
||||
{reply, Reply, State#state{socket = TLSSocket, sockmod = tls},
|
||||
{reply, Reply,
|
||||
State#state{socket = TLSSocket, sockmod = tls},
|
||||
?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(compress, _From, State) ->
|
||||
@ -208,42 +205,35 @@ handle_call({compress, Data}, _From, State) ->
|
||||
catch (State#state.sockmod):send(
|
||||
State#state.socket, Data),
|
||||
Reply = ok,
|
||||
{reply, Reply, State#state{socket = ZlibSocket, sockmod = ejabberd_zlib},
|
||||
{reply, Reply,
|
||||
State#state{socket = ZlibSocket, sockmod = ejabberd_zlib},
|
||||
?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(reset_stream, _From, State) ->
|
||||
ejabberd_receiver:reset_stream(State#state.receiver),
|
||||
Reply = ok,
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call({send, Data}, _From, State) ->
|
||||
catch (State#state.sockmod):send(
|
||||
State#state.socket, Data),
|
||||
catch (State#state.sockmod):send(State#state.socket, Data),
|
||||
Reply = ok,
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
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, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(get_sockmod, _From, State) ->
|
||||
Reply = State#state.sockmod,
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(get_peer_certificate, _From, State) ->
|
||||
Reply = tls:get_peer_certificate(State#state.socket),
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(get_verify_result, _From, State) ->
|
||||
Reply = tls:get_verify_result(State#state.socket),
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(close, _From, State) ->
|
||||
ejabberd_receiver:close(State#state.receiver),
|
||||
Reply = ok,
|
||||
{stop, normal, Reply, State};
|
||||
|
||||
handle_call(sockname, _From, State) ->
|
||||
#state{sockmod = SockMod, socket = Socket} = State,
|
||||
Reply =
|
||||
@ -254,21 +244,15 @@ handle_call(sockname, _From, State) ->
|
||||
SockMod:sockname(Socket)
|
||||
end,
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(peername, _From, State) ->
|
||||
#state{sockmod = SockMod, socket = Socket} = State,
|
||||
Reply =
|
||||
case SockMod of
|
||||
gen_tcp ->
|
||||
inet:peername(Socket);
|
||||
_ ->
|
||||
SockMod:peername(Socket)
|
||||
Reply = case SockMod of
|
||||
gen_tcp -> inet:peername(Socket);
|
||||
_ -> SockMod:peername(Socket)
|
||||
end,
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT}.
|
||||
Reply = ok, {reply, Reply, State, ?HIBERNATE_TIMEOUT}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
||||
@ -286,7 +270,8 @@ handle_cast(_Msg, State) ->
|
||||
%% Description: Handling all non call/cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
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};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State, ?HIBERNATE_TIMEOUT}.
|
||||
@ -298,15 +283,13 @@ handle_info(_Info, State) ->
|
||||
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
terminate(_Reason, _State) -> ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
%% Description: Convert process state when code is changed
|
||||
%%--------------------------------------------------------------------
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
|
@ -67,58 +67,76 @@
|
||||
start_link() ->
|
||||
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.
|
||||
add(Hook, Function, Seq) when is_function(Function) ->
|
||||
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, undefined, Function, Seq);
|
||||
|
||||
%% @spec (Hook::atom(), Module::atom(), Function::atom(), Seq::integer()) -> ok
|
||||
%% @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.
|
||||
add(Hook, 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) ->
|
||||
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) ->
|
||||
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) ->
|
||||
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.
|
||||
delete(Hook, Function, Seq) when is_function(Function) ->
|
||||
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, undefined, Function, Seq);
|
||||
|
||||
%% @spec (Hook::atom(), Module::atom(), Function::atom(), Seq::integer()) -> ok
|
||||
%% @doc Delete a module and function from this hook.
|
||||
%% It is important to indicate exactly the same information than when the call was added.
|
||||
delete(Hook, 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) ->
|
||||
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, 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) ->
|
||||
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.
|
||||
%% If a call returns stop, no more calls are performed.
|
||||
run(Hook, Args) ->
|
||||
run(Hook, global, Args).
|
||||
|
||||
-spec run(atom(), binary() | global, list()) -> ok.
|
||||
|
||||
run(Hook, Host, Args) ->
|
||||
case ets:lookup(hooks, {Hook, Host}) of
|
||||
[{_, Ls}] ->
|
||||
@ -127,7 +145,8 @@ run(Hook, Host, Args) ->
|
||||
ok
|
||||
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.
|
||||
%% The arguments passed to the function are: [Val | Args].
|
||||
%% 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, global, Val, Args).
|
||||
|
||||
-spec run_fold(atom(), binary() | global, any(), list()) -> any().
|
||||
|
||||
run_fold(Hook, Host, Val, Args) ->
|
||||
case ets:lookup(hooks, {Hook, Host}) of
|
||||
[{_, Ls}] ->
|
||||
|
@ -35,7 +35,8 @@
|
||||
stop_listener/2,
|
||||
parse_listener_portip/2,
|
||||
add_listener/3,
|
||||
delete_listener/2
|
||||
delete_listener/2,
|
||||
validate_cfg/1
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@ -53,7 +54,7 @@ init(_) ->
|
||||
{ok, {{one_for_one, 10, 1}, []}}.
|
||||
|
||||
bind_tcp_ports() ->
|
||||
case ejabberd_config:get_local_option(listen) of
|
||||
case ejabberd_config:get_local_option(listen, fun validate_cfg/1) of
|
||||
undefined ->
|
||||
ignore;
|
||||
Ls ->
|
||||
@ -77,7 +78,8 @@ bind_tcp_port(PortIP, Module, RawOpts) ->
|
||||
udp -> ok;
|
||||
_ ->
|
||||
ListenSocket = listen_tcp(PortIP, Module, SockOpts, Port, IPS),
|
||||
ets:insert(listen_sockets, {PortIP, ListenSocket})
|
||||
ets:insert(listen_sockets, {PortIP, ListenSocket}),
|
||||
ok
|
||||
end
|
||||
catch
|
||||
throw:{error, Error} ->
|
||||
@ -85,7 +87,7 @@ bind_tcp_port(PortIP, Module, RawOpts) ->
|
||||
end.
|
||||
|
||||
start_listeners() ->
|
||||
case ejabberd_config:get_local_option(listen) of
|
||||
case ejabberd_config:get_local_option(listen, fun validate_cfg/1) of
|
||||
undefined ->
|
||||
ignore;
|
||||
Ls ->
|
||||
@ -215,17 +217,17 @@ parse_listener_portip(PortIP, Opts) ->
|
||||
case add_proto(PortIP, Opts) of
|
||||
{P, Prot} ->
|
||||
T = get_ip_tuple(IPOpt, IPVOpt),
|
||||
S = inet_parse:ntoa(T),
|
||||
S = jlib:ip_to_list(T),
|
||||
{P, T, S, Prot};
|
||||
{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, S, Prot} when is_integer(P) and is_list(S) ->
|
||||
[S | _] = string:tokens(S, "/"),
|
||||
{ok, T} = inet_parse:address(S),
|
||||
{P, S, Prot} when is_integer(P) and is_binary(S) ->
|
||||
[S | _] = str:tokens(S, <<"/">>),
|
||||
{ok, T} = inet_parse:address(binary_to_list(S)),
|
||||
{P, T, S, Prot}
|
||||
end,
|
||||
IPV = case size(IPT) of
|
||||
IPV = case tuple_size(IPT) of
|
||||
4 -> inet;
|
||||
8 -> inet6
|
||||
end,
|
||||
@ -337,7 +339,7 @@ start_listener2(Port, Module, Opts) ->
|
||||
start_listener_sup(Port, Module, Opts).
|
||||
|
||||
start_module_sup(_Port, Module) ->
|
||||
Proc1 = gen_mod:get_module_proc("sup", Module),
|
||||
Proc1 = gen_mod:get_module_proc(<<"sup">>, Module),
|
||||
ChildSpec1 =
|
||||
{Proc1,
|
||||
{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).
|
||||
|
||||
stop_listeners() ->
|
||||
Ports = ejabberd_config:get_local_option(listen),
|
||||
Ports = ejabberd_config:get_local_option(listen, fun validate_cfg/1),
|
||||
lists:foreach(
|
||||
fun({PortIpNetp, Module, _Opts}) ->
|
||||
delete_listener(PortIpNetp, Module)
|
||||
@ -390,7 +392,8 @@ add_listener(PortIP, Module, Opts) ->
|
||||
PortIP1 = {Port, IPT, Proto},
|
||||
case start_listener(PortIP1, Module, Opts) of
|
||||
{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 ->
|
||||
[];
|
||||
Ls ->
|
||||
@ -420,7 +423,8 @@ delete_listener(PortIP, Module) ->
|
||||
delete_listener(PortIP, Module, Opts) ->
|
||||
{Port, IPT, _, _, Proto, _} = parse_listener_portip(PortIP, Opts),
|
||||
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 ->
|
||||
[];
|
||||
Ls ->
|
||||
@ -430,11 +434,16 @@ delete_listener(PortIP, Module, Opts) ->
|
||||
ejabberd_config:add_local_option(listen, Ports1),
|
||||
stop_listener(PortIP1, Module).
|
||||
|
||||
|
||||
-spec is_frontend({frontend, module} | module()) -> boolean().
|
||||
|
||||
is_frontend({frontend, _Module}) -> true;
|
||||
is_frontend(_) -> false.
|
||||
|
||||
%% @doc(FrontMod) -> atom()
|
||||
%% where FrontMod = atom() | {frontend, atom()}
|
||||
-spec strip_frontend({frontend, module()} | module()) -> module().
|
||||
|
||||
strip_frontend({frontend, 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;
|
||||
eaddrinuse ->
|
||||
"IP address and port number already used: "
|
||||
++IPS++" "++integer_to_list(Port);
|
||||
++binary_to_list(IPS)++" "++integer_to_list(Port);
|
||||
_ ->
|
||||
format_error(Reason)
|
||||
end,
|
||||
@ -520,3 +529,44 @@ format_error(Reason) ->
|
||||
ReasonStr ->
|
||||
ReasonStr
|
||||
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).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_server).
|
||||
@ -32,30 +33,27 @@
|
||||
%% API
|
||||
-export([start_link/0]).
|
||||
|
||||
-export([route/3,
|
||||
route_iq/4,
|
||||
route_iq/5,
|
||||
process_iq_reply/3,
|
||||
register_iq_handler/4,
|
||||
register_iq_handler/5,
|
||||
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
|
||||
]).
|
||||
-export([route/3, route_iq/4, route_iq/5,
|
||||
process_iq_reply/3, register_iq_handler/4,
|
||||
register_iq_handler/5, 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
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
|
||||
-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).
|
||||
|
||||
@ -70,7 +68,8 @@
|
||||
%% Description: Starts the server
|
||||
%%--------------------------------------------------------------------
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [],
|
||||
[]).
|
||||
|
||||
process_iq(From, To, Packet) ->
|
||||
IQ = jlib:iq_query_info(Packet),
|
||||
@ -80,19 +79,16 @@ process_iq(From, To, Packet) ->
|
||||
case ets:lookup(?IQTABLE, {XMLNS, Host}) of
|
||||
[{_, Module, Function}] ->
|
||||
ResIQ = Module:Function(From, To, IQ),
|
||||
if
|
||||
ResIQ /= ignore ->
|
||||
ejabberd_router:route(
|
||||
To, From, jlib:iq_to_xml(ResIQ));
|
||||
true ->
|
||||
ok
|
||||
if ResIQ /= ignore ->
|
||||
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
|
||||
true -> ok
|
||||
end;
|
||||
[{_, Module, Function, Opts}] ->
|
||||
gen_iq_handler:handle(Host, Module, Function, Opts,
|
||||
From, To, IQ);
|
||||
[] ->
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERR_FEATURE_NOT_IMPLEMENTED),
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_FEATURE_NOT_IMPLEMENTED),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
reply ->
|
||||
@ -106,14 +102,10 @@ process_iq(From, To, Packet) ->
|
||||
|
||||
process_iq_reply(From, To, #iq{id = ID} = IQ) ->
|
||||
case get_iq_callback(ID) of
|
||||
{ok, undefined, Function} ->
|
||||
Function(IQ),
|
||||
ok;
|
||||
{ok, undefined, Function} -> Function(IQ), ok;
|
||||
{ok, Module, Function} ->
|
||||
Module:Function(From, To, IQ),
|
||||
ok;
|
||||
_ ->
|
||||
nothing
|
||||
Module:Function(From, To, IQ), ok;
|
||||
_ -> nothing
|
||||
end.
|
||||
|
||||
route(From, To, Packet) ->
|
||||
@ -121,14 +113,14 @@ route(From, To, Packet) ->
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nwhen processing: ~p",
|
||||
[Reason, {From, To, Packet}]);
|
||||
_ ->
|
||||
ok
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
route_iq(From, To, IQ, F) ->
|
||||
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 ->
|
||||
ID = randoms:get_string(),
|
||||
Host = From#jid.lserver,
|
||||
@ -139,15 +131,16 @@ route_iq(From, To, #iq{type = Type} = IQ, F, Timeout) when is_function(F) ->
|
||||
end,
|
||||
ejabberd_router:route(From, To, Packet).
|
||||
|
||||
register_iq_response_handler(Host, ID, Module, Function) ->
|
||||
register_iq_response_handler(Host, ID, Module, Function, undefined).
|
||||
register_iq_response_handler(Host, ID, Module,
|
||||
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
|
||||
undefined ->
|
||||
?IQ_TIMEOUT;
|
||||
N when is_integer(N), N > 0 ->
|
||||
N
|
||||
undefined -> ?IQ_TIMEOUT;
|
||||
N when is_integer(N), N > 0 -> N
|
||||
end,
|
||||
TRef = erlang:start_timer(Timeout, ejabberd_local, ID),
|
||||
mnesia:dirty_write(#iq_response{id = ID,
|
||||
@ -156,14 +149,15 @@ register_iq_response_handler(_Host, ID, Module, Function, Timeout0) ->
|
||||
timer = TRef}).
|
||||
|
||||
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) ->
|
||||
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) ->
|
||||
catch get_iq_callback(ID),
|
||||
ok.
|
||||
catch get_iq_callback(ID), ok.
|
||||
|
||||
unregister_iq_handler(Host, XMLNS) ->
|
||||
ejabberd_local ! {unregister_iq_handler, Host, XMLNS}.
|
||||
@ -172,7 +166,8 @@ refresh_iq_handlers() ->
|
||||
ejabberd_local ! refresh_iq_handlers.
|
||||
|
||||
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),
|
||||
stop.
|
||||
|
||||
@ -188,12 +183,15 @@ bounce_resource_packet(From, To, Packet) ->
|
||||
%% Description: Initiates the server
|
||||
%%--------------------------------------------------------------------
|
||||
init([]) ->
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
ejabberd_router:register_route(Host, {apply, ?MODULE, route}),
|
||||
lists:foreach(fun (Host) ->
|
||||
ejabberd_router:register_route(Host,
|
||||
{apply, ?MODULE,
|
||||
route}),
|
||||
ejabberd_hooks:add(local_send_to_resource_hook, Host,
|
||||
?MODULE, bounce_resource_packet, 100)
|
||||
end, ?MYHOSTS),
|
||||
?MODULE, bounce_resource_packet,
|
||||
100)
|
||||
end,
|
||||
?MYHOSTS),
|
||||
catch ets:new(?IQTABLE, [named_table, public]),
|
||||
update_table(),
|
||||
mnesia:create_table(iq_response,
|
||||
@ -212,70 +210,68 @@ init([]) ->
|
||||
%% Description: Handling call messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_info(Info, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% 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) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nwhen processing: ~p",
|
||||
[Reason, {From, To, Packet}]);
|
||||
_ ->
|
||||
ok
|
||||
_ -> ok
|
||||
end,
|
||||
{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}),
|
||||
catch mod_disco:register_feature(Host, XMLNS),
|
||||
{noreply, State};
|
||||
handle_info({register_iq_handler, Host, XMLNS, Module, Function, Opts}, State) ->
|
||||
ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function, Opts}),
|
||||
handle_info({register_iq_handler, Host, XMLNS, Module,
|
||||
Function, Opts},
|
||||
State) ->
|
||||
ets:insert(?IQTABLE,
|
||||
{{XMLNS, Host}, Module, Function, Opts}),
|
||||
catch mod_disco:register_feature(Host, XMLNS),
|
||||
{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
|
||||
[{_, Module, Function, Opts}] ->
|
||||
gen_iq_handler:stop_iq_handler(Module, Function, Opts);
|
||||
_ ->
|
||||
ok
|
||||
_ -> ok
|
||||
end,
|
||||
ets:delete(?IQTABLE, {XMLNS, Host}),
|
||||
catch mod_disco:unregister_feature(Host, XMLNS),
|
||||
{noreply, State};
|
||||
handle_info(refresh_iq_handlers, State) ->
|
||||
lists:foreach(
|
||||
fun(T) ->
|
||||
lists:foreach(fun (T) ->
|
||||
case T of
|
||||
{{XMLNS, Host}, _Module, _Function, _Opts} ->
|
||||
catch mod_disco:register_feature(Host, XMLNS);
|
||||
{{XMLNS, Host}, _Module, _Function} ->
|
||||
catch mod_disco:register_feature(Host, XMLNS);
|
||||
_ ->
|
||||
ok
|
||||
_ -> ok
|
||||
end
|
||||
end, ets:tab2list(?IQTABLE)),
|
||||
end,
|
||||
ets:tab2list(?IQTABLE)),
|
||||
{noreply, State};
|
||||
handle_info({timeout, _TRef, ID}, State) ->
|
||||
process_iq_timeout(ID),
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: terminate(Reason, State) -> void()
|
||||
%% 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.
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
%% Description: Convert process state when code is changed
|
||||
%%--------------------------------------------------------------------
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
handle_info(_Info, State) -> {noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) -> ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
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]),
|
||||
if
|
||||
To#jid.luser /= "" ->
|
||||
if To#jid.luser /= <<"">> ->
|
||||
ejabberd_sm:route(From, To, Packet);
|
||||
To#jid.lresource == "" ->
|
||||
{xmlelement, Name, _Attrs, _Els} = Packet,
|
||||
To#jid.lresource == <<"">> ->
|
||||
#xmlel{name = Name} = Packet,
|
||||
case Name of
|
||||
"iq" ->
|
||||
process_iq(From, To, Packet);
|
||||
"message" ->
|
||||
ok;
|
||||
"presence" ->
|
||||
ok;
|
||||
_ ->
|
||||
ok
|
||||
<<"iq">> -> process_iq(From, To, Packet);
|
||||
<<"message">> -> ok;
|
||||
<<"presence">> -> ok;
|
||||
_ -> ok
|
||||
end;
|
||||
true ->
|
||||
{xmlelement, _Name, Attrs, _Els} = Packet,
|
||||
case xml:get_attr_s("type", Attrs) of
|
||||
"error" -> ok;
|
||||
"result" -> ok;
|
||||
#xmlel{attrs = Attrs} = Packet,
|
||||
case xml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"error">> -> ok;
|
||||
<<"result">> -> ok;
|
||||
_ ->
|
||||
ejabberd_hooks:run(local_send_to_resource_hook,
|
||||
To#jid.lserver,
|
||||
[From, To, Packet])
|
||||
To#jid.lserver, [From, To, Packet])
|
||||
end
|
||||
end.
|
||||
|
||||
@ -366,12 +357,6 @@ process_iq_timeout() ->
|
||||
cancel_timer(TRef) ->
|
||||
case erlang:cancel_timer(TRef) of
|
||||
false ->
|
||||
receive
|
||||
{timeout, TRef, _} ->
|
||||
ok
|
||||
after 0 ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
receive {timeout, TRef, _} -> ok after 0 -> ok end;
|
||||
_ -> ok
|
||||
end.
|
||||
|
@ -60,20 +60,20 @@ start_link() ->
|
||||
|
||||
join(Name) ->
|
||||
PG = {?MODULE, Name},
|
||||
?PG2:create(PG),
|
||||
?PG2:join(PG, whereis(?MODULE)).
|
||||
pg2:create(PG),
|
||||
pg2:join(PG, whereis(?MODULE)).
|
||||
|
||||
leave(Name) ->
|
||||
PG = {?MODULE, Name},
|
||||
?PG2:leave(PG, whereis(?MODULE)).
|
||||
pg2:leave(PG, whereis(?MODULE)).
|
||||
|
||||
get_members(Name) ->
|
||||
PG = {?MODULE, Name},
|
||||
[node(P) || P <- ?PG2:get_members(PG)].
|
||||
[node(P) || P <- pg2:get_members(PG)].
|
||||
|
||||
get_closest_node(Name) ->
|
||||
PG = {?MODULE, Name},
|
||||
node(?PG2:get_closest_pid(PG)).
|
||||
node(pg2:get_closest_pid(PG)).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
@ -88,7 +88,7 @@ get_closest_node(Name) ->
|
||||
%%--------------------------------------------------------------------
|
||||
init([]) ->
|
||||
{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 ->
|
||||
{true, false};
|
||||
backend ->
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -25,54 +25,51 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_rdbms).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
start() ->
|
||||
%% Check if ejabberd has been compiled with ODBC
|
||||
case catch ejabberd_odbc_sup:module_info() of
|
||||
{'EXIT',{undef,_}} ->
|
||||
?INFO_MSG("ejabberd has not been compiled with relational database support. Skipping database startup.", []);
|
||||
_ ->
|
||||
%% If compiled with ODBC, start ODBC on the needed host
|
||||
start_hosts()
|
||||
{'EXIT', {undef, _}} ->
|
||||
?INFO_MSG("ejabberd has not been compiled with "
|
||||
"relational database support. Skipping "
|
||||
"database startup.",
|
||||
[]);
|
||||
_ -> start_hosts()
|
||||
end.
|
||||
|
||||
%% Start relationnal DB module on the nodes where it is needed
|
||||
start_hosts() ->
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
lists:foreach(fun (Host) ->
|
||||
case needs_odbc(Host) of
|
||||
true -> start_odbc(Host);
|
||||
false -> ok
|
||||
end
|
||||
end, ?MYHOSTS).
|
||||
end,
|
||||
?MYHOSTS).
|
||||
|
||||
%% Start the ODBC module on the given host
|
||||
start_odbc(Host) ->
|
||||
Supervisor_name = gen_mod:get_module_proc(Host, ejabberd_odbc_sup),
|
||||
ChildSpec =
|
||||
{Supervisor_name,
|
||||
{ejabberd_odbc_sup, start_link, [Host]},
|
||||
transient,
|
||||
infinity,
|
||||
supervisor,
|
||||
[ejabberd_odbc_sup]},
|
||||
Supervisor_name = gen_mod:get_module_proc(Host,
|
||||
ejabberd_odbc_sup),
|
||||
ChildSpec = {Supervisor_name,
|
||||
{ejabberd_odbc_sup, start_link, [Host]}, transient,
|
||||
infinity, supervisor, [ejabberd_odbc_sup]},
|
||||
case supervisor:start_child(ejabberd_sup, ChildSpec) of
|
||||
{ok, _PID} ->
|
||||
ok;
|
||||
{ok, _PID} -> ok;
|
||||
_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)
|
||||
end.
|
||||
|
||||
%% Returns true if we have configured odbc_server for the given host
|
||||
needs_odbc(Host) ->
|
||||
LHost = jlib:nameprep(Host),
|
||||
case ejabberd_config:get_local_option({odbc_server, LHost}) of
|
||||
undefined ->
|
||||
false;
|
||||
_ -> true
|
||||
end.
|
||||
ejabberd_config:get_local_option(
|
||||
{odbc_server, LHost}, fun(_) -> true end, false).
|
||||
|
@ -25,6 +25,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_receiver).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_server).
|
||||
@ -41,18 +42,19 @@
|
||||
close/1]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-record(state, {socket,
|
||||
sock_mod,
|
||||
shaper_state,
|
||||
c2s_pid,
|
||||
max_stanza_size,
|
||||
xml_stream_state,
|
||||
timeout}).
|
||||
-record(state,
|
||||
{socket :: inet:socket() | tls:tls_socket() | ejabberd_zlib:zlib_socket(),
|
||||
sock_mod = gen_tcp :: gen_tcp | tls | ejabberd_zlib,
|
||||
shaper_state = none :: shaper:shaper(),
|
||||
c2s_pid :: pid(),
|
||||
max_stanza_size = infinity :: non_neg_integer() | infinity,
|
||||
xml_stream_state :: xml_stream:xml_stream_state(),
|
||||
timeout = infinity:: timeout()}).
|
||||
|
||||
-define(HIBERNATE_TIMEOUT, 90000).
|
||||
|
||||
@ -63,9 +65,16 @@
|
||||
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
|
||||
%% 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) ->
|
||||
gen_server:start_link(
|
||||
?MODULE, [Socket, SockMod, Shaper, MaxStanzaSize], []).
|
||||
gen_server:start_link(?MODULE,
|
||||
[Socket, SockMod, Shaper, MaxStanzaSize], []).
|
||||
|
||||
-spec start(inet:socket(), atom(), shaper:shaper()) -> undefined | pid().
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% 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, infinity).
|
||||
|
||||
-spec start(inet:socket(), atom(), shaper:shaper(),
|
||||
non_neg_integer() | infinity) -> undefined | pid().
|
||||
|
||||
start(Socket, SockMod, Shaper, MaxStanzaSize) ->
|
||||
{ok, Pid} = supervisor:start_child(
|
||||
ejabberd_receiver_sup,
|
||||
{ok, Pid} =
|
||||
supervisor:start_child(ejabberd_receiver_sup,
|
||||
[Socket, SockMod, Shaper, MaxStanzaSize]),
|
||||
Pid.
|
||||
|
||||
-spec change_shaper(pid(), shaper:shaper()) -> ok.
|
||||
|
||||
change_shaper(Pid, Shaper) ->
|
||||
gen_server:cast(Pid, {change_shaper, Shaper}).
|
||||
|
||||
reset_stream(Pid) ->
|
||||
do_call(Pid, reset_stream).
|
||||
-spec reset_stream(pid()) -> ok | {error, any()}.
|
||||
|
||||
reset_stream(Pid) -> do_call(Pid, reset_stream).
|
||||
|
||||
-spec starttls(pid(), iodata()) -> {ok, tls:tls_socket()} | {error, any()}.
|
||||
|
||||
starttls(Pid, TLSSocket) ->
|
||||
do_call(Pid, {starttls, TLSSocket}).
|
||||
|
||||
-spec compress(pid(), iodata() | undefined) -> {error, any()} |
|
||||
{ok, ejabberd_zlib:zlib_socket()}.
|
||||
|
||||
compress(Pid, ZlibSocket) ->
|
||||
do_call(Pid, {compress, ZlibSocket}).
|
||||
|
||||
-spec become_controller(pid(), pid()) -> ok | {error, any()}.
|
||||
|
||||
become_controller(Pid, C2SPid) ->
|
||||
do_call(Pid, {become_controller, C2SPid}).
|
||||
|
||||
-spec close(pid()) -> ok.
|
||||
|
||||
close(Pid) ->
|
||||
gen_server:cast(Pid, close).
|
||||
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
@ -112,16 +137,13 @@ close(Pid) ->
|
||||
init([Socket, SockMod, Shaper, MaxStanzaSize]) ->
|
||||
ShaperState = shaper:new(Shaper),
|
||||
Timeout = case SockMod of
|
||||
ssl ->
|
||||
20;
|
||||
_ ->
|
||||
infinity
|
||||
ssl -> 20;
|
||||
_ -> infinity
|
||||
end,
|
||||
{ok, #state{socket = Socket,
|
||||
sock_mod = SockMod,
|
||||
{ok,
|
||||
#state{socket = Socket, sock_mod = SockMod,
|
||||
shaper_state = ShaperState,
|
||||
max_stanza_size = MaxStanzaSize,
|
||||
timeout = Timeout}}.
|
||||
max_stanza_size = MaxStanzaSize, timeout = Timeout}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
||||
@ -137,11 +159,12 @@ handle_call({starttls, TLSSocket}, _From,
|
||||
c2s_pid = C2SPid,
|
||||
max_stanza_size = MaxStanzaSize} = State) ->
|
||||
close_stream(XMLStreamState),
|
||||
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
|
||||
NewXMLStreamState = xml_stream:new(C2SPid,
|
||||
MaxStanzaSize),
|
||||
NewState = State#state{socket = TLSSocket,
|
||||
sock_mod = tls,
|
||||
xml_stream_state = NewXMLStreamState},
|
||||
case tls:recv_data(TLSSocket, "") of
|
||||
case tls:recv_data(TLSSocket, <<"">>) of
|
||||
{ok, TLSData} ->
|
||||
{reply, ok, process_data(TLSData, NewState), ?HIBERNATE_TIMEOUT};
|
||||
{error, _Reason} ->
|
||||
@ -152,11 +175,12 @@ handle_call({compress, ZlibSocket}, _From,
|
||||
c2s_pid = C2SPid,
|
||||
max_stanza_size = MaxStanzaSize} = State) ->
|
||||
close_stream(XMLStreamState),
|
||||
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
|
||||
NewXMLStreamState = xml_stream:new(C2SPid,
|
||||
MaxStanzaSize),
|
||||
NewState = State#state{socket = ZlibSocket,
|
||||
sock_mod = ejabberd_zlib,
|
||||
xml_stream_state = NewXMLStreamState},
|
||||
case ejabberd_zlib:recv_data(ZlibSocket, "") of
|
||||
case ejabberd_zlib:recv_data(ZlibSocket, <<"">>) of
|
||||
{ok, ZlibData} ->
|
||||
{reply, ok, process_data(ZlibData, NewState), ?HIBERNATE_TIMEOUT};
|
||||
{error, _Reason} ->
|
||||
@ -164,12 +188,14 @@ handle_call({compress, ZlibSocket}, _From,
|
||||
end;
|
||||
handle_call(reset_stream, _From,
|
||||
#state{xml_stream_state = XMLStreamState,
|
||||
c2s_pid = C2SPid,
|
||||
max_stanza_size = MaxStanzaSize} = State) ->
|
||||
c2s_pid = C2SPid, max_stanza_size = MaxStanzaSize} =
|
||||
State) ->
|
||||
close_stream(XMLStreamState),
|
||||
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
|
||||
NewXMLStreamState = xml_stream:new(C2SPid,
|
||||
MaxStanzaSize),
|
||||
Reply = ok,
|
||||
{reply, Reply, State#state{xml_stream_state = NewXMLStreamState},
|
||||
{reply, Reply,
|
||||
State#state{xml_stream_state = NewXMLStreamState},
|
||||
?HIBERNATE_TIMEOUT};
|
||||
handle_call({become_controller, C2SPid}, _From, State) ->
|
||||
XMLStreamState = xml_stream:new(C2SPid, State#state.max_stanza_size),
|
||||
@ -179,8 +205,7 @@ handle_call({become_controller, C2SPid}, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, NewState, ?HIBERNATE_TIMEOUT};
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT}.
|
||||
Reply = ok, {reply, Reply, State, ?HIBERNATE_TIMEOUT}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
||||
@ -190,9 +215,9 @@ handle_call(_Request, _From, State) ->
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast({change_shaper, Shaper}, State) ->
|
||||
NewShaperState = shaper:new(Shaper),
|
||||
{noreply, State#state{shaper_state = NewShaperState}, ?HIBERNATE_TIMEOUT};
|
||||
handle_cast(close, State) ->
|
||||
{stop, normal, State};
|
||||
{noreply, State#state{shaper_state = NewShaperState},
|
||||
?HIBERNATE_TIMEOUT};
|
||||
handle_cast(close, State) -> {stop, normal, State};
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State, ?HIBERNATE_TIMEOUT}.
|
||||
|
||||
@ -203,25 +228,23 @@ handle_cast(_Msg, State) ->
|
||||
%% Description: Handling all non call/cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_info({Tag, _TCPSocket, Data},
|
||||
#state{socket = Socket,
|
||||
sock_mod = SockMod} = State)
|
||||
when (Tag == tcp) or (Tag == ssl) or (Tag == ejabberd_xml) ->
|
||||
#state{socket = Socket, sock_mod = SockMod} = State)
|
||||
when (Tag == tcp) or (Tag == ssl) or
|
||||
(Tag == ejabberd_xml) ->
|
||||
case SockMod of
|
||||
tls ->
|
||||
case tls:recv_data(Socket, Data) of
|
||||
{ok, TLSData} ->
|
||||
{noreply, process_data(TLSData, State),
|
||||
?HIBERNATE_TIMEOUT};
|
||||
{error, _Reason} ->
|
||||
{stop, normal, State}
|
||||
{error, _Reason} -> {stop, normal, State}
|
||||
end;
|
||||
ejabberd_zlib ->
|
||||
case ejabberd_zlib:recv_data(Socket, Data) of
|
||||
{ok, ZlibData} ->
|
||||
{noreply, process_data(ZlibData, State),
|
||||
?HIBERNATE_TIMEOUT};
|
||||
{error, _Reason} ->
|
||||
{stop, normal, State}
|
||||
{error, _Reason} -> {stop, normal, State}
|
||||
end;
|
||||
_ ->
|
||||
{noreply, process_data(Data, State), ?HIBERNATE_TIMEOUT}
|
||||
@ -232,16 +255,15 @@ handle_info({Tag, _TCPSocket}, State)
|
||||
handle_info({Tag, _TCPSocket, Reason}, State)
|
||||
when (Tag == tcp_error) or (Tag == ssl_error) ->
|
||||
case Reason of
|
||||
timeout ->
|
||||
{noreply, State, ?HIBERNATE_TIMEOUT};
|
||||
_ ->
|
||||
{stop, normal, State}
|
||||
timeout -> {noreply, State, ?HIBERNATE_TIMEOUT};
|
||||
_ -> {stop, normal, State}
|
||||
end;
|
||||
handle_info({timeout, _Ref, activate}, State) ->
|
||||
activate_socket(State),
|
||||
{noreply, State, ?HIBERNATE_TIMEOUT};
|
||||
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};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State, ?HIBERNATE_TIMEOUT}.
|
||||
@ -253,14 +275,14 @@ handle_info(_Info, State) ->
|
||||
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, #state{xml_stream_state = XMLStreamState,
|
||||
c2s_pid = C2SPid} = State) ->
|
||||
terminate(_Reason,
|
||||
#state{xml_stream_state = XMLStreamState,
|
||||
c2s_pid = C2SPid} =
|
||||
State) ->
|
||||
close_stream(XMLStreamState),
|
||||
if
|
||||
C2SPid /= undefined ->
|
||||
if C2SPid /= undefined ->
|
||||
gen_fsm:send_event(C2SPid, closed);
|
||||
true ->
|
||||
ok
|
||||
true -> ok
|
||||
end,
|
||||
catch (State#state.sock_mod):close(State#state.socket),
|
||||
ok.
|
||||
@ -269,8 +291,7 @@ terminate(_Reason, #state{xml_stream_state = XMLStreamState,
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
%% Description: Convert process state when code is changed
|
||||
%%--------------------------------------------------------------------
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
@ -278,8 +299,7 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
|
||||
activate_socket(#state{socket = Socket,
|
||||
sock_mod = SockMod}) ->
|
||||
PeerName =
|
||||
case SockMod of
|
||||
PeerName = case SockMod of
|
||||
gen_tcp ->
|
||||
inet:setopts(Socket, [{active, once}]),
|
||||
inet:peername(Socket);
|
||||
@ -288,38 +308,35 @@ activate_socket(#state{socket = Socket,
|
||||
SockMod:peername(Socket)
|
||||
end,
|
||||
case PeerName of
|
||||
{error, _Reason} ->
|
||||
self() ! {tcp_closed, Socket};
|
||||
{ok, _} ->
|
||||
ok
|
||||
{error, _Reason} -> self() ! {tcp_closed, Socket};
|
||||
{ok, _} -> ok
|
||||
end.
|
||||
|
||||
%% Data processing for connectors directly generating xmlelement in
|
||||
%% Erlang data structure.
|
||||
%% WARNING: Shaper does not work with Erlang data structure.
|
||||
process_data([], State) ->
|
||||
activate_socket(State),
|
||||
State;
|
||||
process_data([Element|Els], #state{c2s_pid = C2SPid} = State)
|
||||
when element(1, Element) == xmlelement;
|
||||
activate_socket(State), State;
|
||||
process_data([Element | Els],
|
||||
#state{c2s_pid = C2SPid} = State)
|
||||
when element(1, Element) == xmlel;
|
||||
element(1, Element) == xmlstreamstart;
|
||||
element(1, Element) == xmlstreamelement;
|
||||
element(1, Element) == xmlstreamend ->
|
||||
if
|
||||
C2SPid == undefined ->
|
||||
State;
|
||||
if C2SPid == undefined -> State;
|
||||
true ->
|
||||
catch gen_fsm:send_event(C2SPid, element_wrapper(Element)),
|
||||
catch gen_fsm:send_event(C2SPid,
|
||||
element_wrapper(Element)),
|
||||
process_data(Els, State)
|
||||
end;
|
||||
%% Data processing for connectors receivind data as string.
|
||||
process_data(Data,
|
||||
#state{xml_stream_state = XMLStreamState,
|
||||
shaper_state = ShaperState,
|
||||
c2s_pid = C2SPid} = State) ->
|
||||
?DEBUG("Received XML on stream = ~p", [binary_to_list(Data)]),
|
||||
shaper_state = ShaperState, c2s_pid = C2SPid} =
|
||||
State) ->
|
||||
?DEBUG("Received XML on stream = ~p", [(Data)]),
|
||||
XMLStreamState1 = xml_stream:parse(XMLStreamState, Data),
|
||||
{NewShaperState, Pause} = shaper:update(ShaperState, size(Data)),
|
||||
{NewShaperState, Pause} = shaper:update(ShaperState, byte_size(Data)),
|
||||
if
|
||||
C2SPid == undefined ->
|
||||
ok;
|
||||
@ -336,20 +353,16 @@ process_data(Data,
|
||||
%% speaking directly Erlang XML), we wrap it inside the same
|
||||
%% xmlstreamelement coming from the XML parser.
|
||||
element_wrapper(XMLElement)
|
||||
when element(1, XMLElement) == xmlelement ->
|
||||
when element(1, XMLElement) == xmlel ->
|
||||
{xmlstreamelement, XMLElement};
|
||||
element_wrapper(Element) ->
|
||||
Element.
|
||||
element_wrapper(Element) -> Element.
|
||||
|
||||
close_stream(undefined) ->
|
||||
ok;
|
||||
close_stream(undefined) -> ok;
|
||||
close_stream(XMLStreamState) ->
|
||||
xml_stream:close(XMLStreamState).
|
||||
|
||||
do_call(Pid, Msg) ->
|
||||
case catch gen_server:call(Pid, Msg) of
|
||||
{'EXIT', Why} ->
|
||||
{error, Why};
|
||||
Res ->
|
||||
Res
|
||||
{'EXIT', Why} -> {error, Why};
|
||||
Res -> Res
|
||||
end.
|
||||
|
@ -25,19 +25,22 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_regexp).
|
||||
|
||||
-compile([export_all]).
|
||||
|
||||
exec(ReM, ReF, ReA, RgM, RgF, RgA) ->
|
||||
try apply(ReM, ReF, ReA)
|
||||
catch
|
||||
error:undef ->
|
||||
apply(RgM, RgF, RgA);
|
||||
A:B ->
|
||||
{error, {A, B}}
|
||||
exec({ReM, ReF, ReA}, {RgM, RgF, RgA}) ->
|
||||
try apply(ReM, ReF, ReA) catch
|
||||
error:undef -> apply(RgM, RgF, RgA);
|
||||
A:B -> {error, {A, B}}
|
||||
end.
|
||||
|
||||
-spec run(binary(), binary()) -> match | nomatch | {error, any()}.
|
||||
|
||||
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;
|
||||
@ -45,28 +48,49 @@ run(String, Regexp) ->
|
||||
{error, Error} -> {error, Error}
|
||||
end.
|
||||
|
||||
-spec split(binary(), binary()) -> [binary()].
|
||||
|
||||
split(String, Regexp) ->
|
||||
case exec(re, split, [String, Regexp, [{return, list}]], regexp, split, [String, Regexp]) of
|
||||
{ok, FieldList} -> FieldList;
|
||||
case exec({re, split, [String, Regexp, [{return, binary}]]},
|
||||
{regexp, split, [binary_to_list(String),
|
||||
binary_to_list(Regexp)]})
|
||||
of
|
||||
{ok, FieldList} -> [iolist_to_binary(F) || F <- FieldList];
|
||||
{error, Error} -> throw(Error);
|
||||
A -> A
|
||||
end.
|
||||
|
||||
-spec replace(binary(), binary(), binary()) -> binary().
|
||||
|
||||
replace(String, Regexp, New) ->
|
||||
case exec(re, replace, [String, Regexp, New, [{return, list}]], regexp, sub, [String, Regexp, New]) of
|
||||
{ok, NewString, _RepCount} -> NewString;
|
||||
case exec({re, replace, [String, Regexp, New, [{return, binary}]]},
|
||||
{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);
|
||||
A -> A
|
||||
end.
|
||||
|
||||
-spec greplace(binary(), binary(), binary()) -> binary().
|
||||
|
||||
greplace(String, Regexp, New) ->
|
||||
case exec(re, replace, [String, Regexp, New, [global, {return, list}]], regexp, sub, [String, Regexp, New]) of
|
||||
{ok, NewString, _RepCount} -> NewString;
|
||||
case exec({re, replace, [String, Regexp, New, [global, {return, binary}]]},
|
||||
{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);
|
||||
A -> A
|
||||
end.
|
||||
|
||||
-spec sh_to_awk(binary()) -> binary().
|
||||
|
||||
sh_to_awk(ShRegExp) ->
|
||||
case exec(xmerl_regexp, sh_to_awk, [ShRegExp], regexp, sh_to_awk, [ShRegExp]) of
|
||||
A -> A
|
||||
case exec({xmerl_regexp, sh_to_awk, [binary_to_list(ShRegExp)]},
|
||||
{regexp, sh_to_awk, [binary_to_list(ShRegExp)]})
|
||||
of
|
||||
A -> iolist_to_binary(A)
|
||||
end.
|
||||
|
@ -25,6 +25,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_router).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_server).
|
||||
@ -44,13 +45,17 @@
|
||||
-export([start_link/0]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
|
||||
-type local_hint() :: undefined | integer() | {apply, atom(), atom()}.
|
||||
|
||||
-record(route, {domain, pid, local_hint}).
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
%%====================================================================
|
||||
@ -63,6 +68,7 @@
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
-spec route(jid(), jid(), xmlel()) -> ok.
|
||||
|
||||
route(From, To, Packet) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
@ -75,119 +81,131 @@ route(From, To, Packet) ->
|
||||
|
||||
%% Route the error packet only if the originating packet is not an error itself.
|
||||
%% RFC3920 9.3.1
|
||||
-spec route_error(jid(), jid(), xmlel(), xmlel()) -> ok.
|
||||
|
||||
route_error(From, To, ErrPacket, OrigPacket) ->
|
||||
{xmlelement, _Name, Attrs, _Els} = OrigPacket,
|
||||
case "error" == xml:get_attr_s("type", Attrs) of
|
||||
false ->
|
||||
route(From, To, ErrPacket);
|
||||
true ->
|
||||
ok
|
||||
#xmlel{attrs = Attrs} = OrigPacket,
|
||||
case <<"error">> == xml:get_attr_s(<<"type">>, Attrs) of
|
||||
false -> route(From, To, ErrPacket);
|
||||
true -> ok
|
||||
end.
|
||||
|
||||
-spec register_route(binary()) -> term().
|
||||
|
||||
register_route(Domain) ->
|
||||
register_route(Domain, undefined).
|
||||
|
||||
-spec register_route(binary(), local_hint()) -> term().
|
||||
|
||||
register_route(Domain, LocalHint) ->
|
||||
case jlib:nameprep(Domain) of
|
||||
error ->
|
||||
erlang:error({invalid_domain, Domain});
|
||||
error -> erlang:error({invalid_domain, Domain});
|
||||
LDomain ->
|
||||
Pid = self(),
|
||||
case get_component_number(LDomain) of
|
||||
undefined ->
|
||||
F = fun() ->
|
||||
mnesia:write(#route{domain = LDomain,
|
||||
pid = Pid,
|
||||
F = fun () ->
|
||||
mnesia:write(#route{domain = LDomain, pid = Pid,
|
||||
local_hint = LocalHint})
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
N ->
|
||||
F = fun() ->
|
||||
F = fun () ->
|
||||
case mnesia:wread({route, LDomain}) of
|
||||
[] ->
|
||||
mnesia:write(
|
||||
#route{domain = LDomain,
|
||||
mnesia:write(#route{domain = LDomain,
|
||||
pid = Pid,
|
||||
local_hint = 1}),
|
||||
lists:foreach(
|
||||
fun(I) ->
|
||||
mnesia:write(
|
||||
#route{domain = LDomain,
|
||||
pid = undefined,
|
||||
local_hint = I})
|
||||
end, lists:seq(2, N));
|
||||
lists:foreach(fun (I) ->
|
||||
mnesia:write(#route{domain
|
||||
=
|
||||
LDomain,
|
||||
pid
|
||||
=
|
||||
undefined,
|
||||
local_hint
|
||||
=
|
||||
I})
|
||||
end,
|
||||
lists:seq(2, N));
|
||||
Rs ->
|
||||
lists:any(
|
||||
fun(#route{pid = undefined,
|
||||
local_hint = I} = R) ->
|
||||
mnesia:write(
|
||||
#route{domain = LDomain,
|
||||
pid = Pid,
|
||||
local_hint = I}),
|
||||
lists:any(fun (#route{pid = undefined,
|
||||
local_hint = I} =
|
||||
R) ->
|
||||
mnesia:write(#route{domain =
|
||||
LDomain,
|
||||
pid =
|
||||
Pid,
|
||||
local_hint
|
||||
=
|
||||
I}),
|
||||
mnesia:delete_object(R),
|
||||
true;
|
||||
(_) ->
|
||||
false
|
||||
end, Rs)
|
||||
(_) -> false
|
||||
end,
|
||||
Rs)
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F)
|
||||
end
|
||||
end.
|
||||
|
||||
-spec register_routes([binary()]) -> ok.
|
||||
|
||||
register_routes(Domains) ->
|
||||
lists:foreach(fun(Domain) ->
|
||||
register_route(Domain)
|
||||
end, Domains).
|
||||
lists:foreach(fun (Domain) -> register_route(Domain)
|
||||
end,
|
||||
Domains).
|
||||
|
||||
-spec unregister_route(binary()) -> term().
|
||||
|
||||
unregister_route(Domain) ->
|
||||
case jlib:nameprep(Domain) of
|
||||
error ->
|
||||
erlang:error({invalid_domain, Domain});
|
||||
error -> erlang:error({invalid_domain, Domain});
|
||||
LDomain ->
|
||||
Pid = self(),
|
||||
case get_component_number(LDomain) of
|
||||
undefined ->
|
||||
F = fun() ->
|
||||
case mnesia:match_object(
|
||||
#route{domain = LDomain,
|
||||
pid = Pid,
|
||||
_ = '_'}) of
|
||||
[R] ->
|
||||
mnesia:delete_object(R);
|
||||
_ ->
|
||||
ok
|
||||
F = fun () ->
|
||||
case mnesia:match_object(#route{domain = LDomain,
|
||||
pid = Pid, _ = '_'})
|
||||
of
|
||||
[R] -> mnesia:delete_object(R);
|
||||
_ -> ok
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
_ ->
|
||||
F = fun() ->
|
||||
case mnesia:match_object(#route{domain=LDomain,
|
||||
pid = Pid,
|
||||
_ = '_'}) of
|
||||
F = fun () ->
|
||||
case mnesia:match_object(#route{domain = LDomain,
|
||||
pid = Pid, _ = '_'})
|
||||
of
|
||||
[R] ->
|
||||
I = R#route.local_hint,
|
||||
mnesia:write(
|
||||
#route{domain = LDomain,
|
||||
mnesia:write(#route{domain = LDomain,
|
||||
pid = undefined,
|
||||
local_hint = I}),
|
||||
mnesia:delete_object(R);
|
||||
_ ->
|
||||
ok
|
||||
_ -> ok
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F)
|
||||
end
|
||||
end.
|
||||
|
||||
unregister_routes(Domains) ->
|
||||
lists:foreach(fun(Domain) ->
|
||||
unregister_route(Domain)
|
||||
end, Domains).
|
||||
-spec unregister_routes([binary()]) -> ok.
|
||||
|
||||
unregister_routes(Domains) ->
|
||||
lists:foreach(fun (Domain) -> unregister_route(Domain)
|
||||
end,
|
||||
Domains).
|
||||
|
||||
-spec dirty_get_all_routes() -> [binary()].
|
||||
|
||||
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() ->
|
||||
lists:usort(mnesia:dirty_all_keys(route)).
|
||||
@ -207,17 +225,14 @@ dirty_get_all_domains() ->
|
||||
init([]) ->
|
||||
update_tables(),
|
||||
mnesia:create_table(route,
|
||||
[{ram_copies, [node()]},
|
||||
{type, bag},
|
||||
{attributes,
|
||||
record_info(fields, route)}]),
|
||||
[{ram_copies, [node()]}, {type, bag},
|
||||
{attributes, record_info(fields, route)}]),
|
||||
mnesia:add_table_copy(route, node(), ram_copies),
|
||||
mnesia:subscribe({table, route, simple}),
|
||||
lists:foreach(
|
||||
fun(Pid) ->
|
||||
erlang:monitor(process, Pid)
|
||||
lists:foreach(fun (Pid) -> erlang:monitor(process, Pid)
|
||||
end,
|
||||
mnesia:dirty_select(route, [{{route, '_', '$1', '_'}, [], ['$1']}])),
|
||||
mnesia:dirty_select(route,
|
||||
[{{route, '_', '$1', '_'}, [], ['$1']}])),
|
||||
{ok, #state{}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@ -230,8 +245,7 @@ init([]) ->
|
||||
%% Description: Handling call messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
Reply = ok, {reply, Reply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
||||
@ -239,8 +253,7 @@ handle_call(_Request, _From, State) ->
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
handle_cast(_Msg, State) -> {noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_info(Info, State) -> {noreply, State} |
|
||||
@ -253,36 +266,32 @@ handle_info({route, From, To, Packet}, State) ->
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nwhen processing: ~p",
|
||||
[Reason, {From, To, Packet}]);
|
||||
_ ->
|
||||
ok
|
||||
_ -> ok
|
||||
end,
|
||||
{noreply, State};
|
||||
handle_info({mnesia_table_event, {write, #route{pid = Pid}, _ActivityId}},
|
||||
handle_info({mnesia_table_event,
|
||||
{write, #route{pid = Pid}, _ActivityId}},
|
||||
State) ->
|
||||
erlang:monitor(process, Pid),
|
||||
{noreply, State};
|
||||
erlang:monitor(process, Pid), {noreply, State};
|
||||
handle_info({'DOWN', _Ref, _Type, Pid, _Info}, State) ->
|
||||
F = fun() ->
|
||||
Es = mnesia:select(
|
||||
route,
|
||||
[{#route{pid = Pid, _ = '_'},
|
||||
[],
|
||||
['$_']}]),
|
||||
lists:foreach(
|
||||
fun(E) ->
|
||||
if
|
||||
is_integer(E#route.local_hint) ->
|
||||
F = fun () ->
|
||||
Es = mnesia:select(route,
|
||||
[{#route{pid = Pid, _ = '_'}, [], ['$_']}]),
|
||||
lists:foreach(fun (E) ->
|
||||
if is_integer(E#route.local_hint) ->
|
||||
LDomain = E#route.domain,
|
||||
I = E#route.local_hint,
|
||||
mnesia:write(
|
||||
#route{domain = LDomain,
|
||||
pid = undefined,
|
||||
local_hint = I}),
|
||||
mnesia:write(#route{domain =
|
||||
LDomain,
|
||||
pid =
|
||||
undefined,
|
||||
local_hint =
|
||||
I}),
|
||||
mnesia:delete_object(E);
|
||||
true ->
|
||||
mnesia:delete_object(E)
|
||||
true -> mnesia:delete_object(E)
|
||||
end
|
||||
end, Es)
|
||||
end,
|
||||
Es)
|
||||
end,
|
||||
mnesia:transaction(F),
|
||||
{noreply, State};
|
||||
@ -310,107 +319,93 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
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]),
|
||||
case ejabberd_hooks:run_fold(filter_packet,
|
||||
{OrigFrom, OrigTo, OrigPacket}, []) of
|
||||
{OrigFrom, OrigTo, OrigPacket}, [])
|
||||
of
|
||||
{From, To, Packet} ->
|
||||
LDstDomain = To#jid.lserver,
|
||||
case mnesia:dirty_read(route, LDstDomain) of
|
||||
[] ->
|
||||
ejabberd_s2s:route(From, To, Packet);
|
||||
[] -> ejabberd_s2s:route(From, To, Packet);
|
||||
[R] ->
|
||||
Pid = R#route.pid,
|
||||
if
|
||||
node(Pid) == node() ->
|
||||
if node(Pid) == node() ->
|
||||
case R#route.local_hint of
|
||||
{apply, Module, Function} ->
|
||||
Module:Function(From, To, Packet);
|
||||
_ ->
|
||||
Pid ! {route, From, To, Packet}
|
||||
_ -> Pid ! {route, From, To, Packet}
|
||||
end;
|
||||
is_pid(Pid) ->
|
||||
Pid ! {route, From, To, Packet};
|
||||
true ->
|
||||
drop
|
||||
is_pid(Pid) -> Pid ! {route, From, To, Packet};
|
||||
true -> drop
|
||||
end;
|
||||
Rs ->
|
||||
Value = case ejabberd_config:get_local_option(
|
||||
{domain_balancing, LDstDomain}) of
|
||||
Value = case
|
||||
ejabberd_config:get_local_option({domain_balancing,
|
||||
LDstDomain}, fun(D) when is_atom(D) -> D end)
|
||||
of
|
||||
undefined -> now();
|
||||
random -> now();
|
||||
source -> jlib:jid_tolower(From);
|
||||
destination -> jlib:jid_tolower(To);
|
||||
bare_source ->
|
||||
jlib:jid_remove_resource(
|
||||
jlib:jid_tolower(From));
|
||||
jlib:jid_remove_resource(jlib:jid_tolower(From));
|
||||
bare_destination ->
|
||||
jlib:jid_remove_resource(
|
||||
jlib:jid_tolower(To))
|
||||
jlib:jid_remove_resource(jlib:jid_tolower(To))
|
||||
end,
|
||||
case get_component_number(LDstDomain) of
|
||||
undefined ->
|
||||
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,
|
||||
if
|
||||
is_pid(Pid) ->
|
||||
Pid ! {route, From, To, Packet};
|
||||
true ->
|
||||
drop
|
||||
if is_pid(Pid) -> Pid ! {route, From, To, Packet};
|
||||
true -> drop
|
||||
end;
|
||||
LRs ->
|
||||
R = lists:nth(erlang:phash(Value, length(LRs)), LRs),
|
||||
R = lists:nth(erlang:phash(Value, str:len(LRs)),
|
||||
LRs),
|
||||
Pid = R#route.pid,
|
||||
case R#route.local_hint of
|
||||
{apply, Module, Function} ->
|
||||
Module:Function(From, To, Packet);
|
||||
_ ->
|
||||
Pid ! {route, From, To, Packet}
|
||||
_ -> Pid ! {route, From, To, Packet}
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
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,
|
||||
if
|
||||
is_pid(Pid) ->
|
||||
Pid ! {route, From, To, Packet};
|
||||
true ->
|
||||
drop
|
||||
if is_pid(Pid) -> Pid ! {route, From, To, Packet};
|
||||
true -> drop
|
||||
end
|
||||
end
|
||||
end;
|
||||
drop ->
|
||||
ok
|
||||
drop -> ok
|
||||
end.
|
||||
|
||||
get_component_number(LDomain) ->
|
||||
case ejabberd_config:get_local_option(
|
||||
{domain_balancing_component_number, LDomain}) of
|
||||
N when is_integer(N),
|
||||
N > 1 ->
|
||||
N;
|
||||
_ ->
|
||||
undefined
|
||||
case
|
||||
ejabberd_config:get_local_option({domain_balancing_component_number,
|
||||
LDomain}, fun(D) -> D end)
|
||||
of
|
||||
N when is_integer(N), N > 1 -> N;
|
||||
_ -> undefined
|
||||
end.
|
||||
|
||||
|
||||
update_tables() ->
|
||||
case catch mnesia:table_info(route, attributes) of
|
||||
[domain, node, pid] ->
|
||||
mnesia:delete_table(route);
|
||||
[domain, pid] ->
|
||||
mnesia:delete_table(route);
|
||||
[domain, pid, local_hint] ->
|
||||
ok;
|
||||
{'EXIT', _} ->
|
||||
ok
|
||||
[domain, node, pid] -> mnesia:delete_table(route);
|
||||
[domain, pid] -> mnesia:delete_table(route);
|
||||
[domain, pid, local_hint] -> ok;
|
||||
{'EXIT', _} -> ok
|
||||
end,
|
||||
case lists:member(local_route, mnesia:system_info(tables)) of
|
||||
true ->
|
||||
mnesia:delete_table(local_route);
|
||||
false ->
|
||||
ok
|
||||
case lists:member(local_route,
|
||||
mnesia:system_info(tables))
|
||||
of
|
||||
true -> mnesia:delete_table(local_route);
|
||||
false -> ok
|
||||
end.
|
||||
|
||||
|
@ -25,49 +25,52 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_s2s).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/0,
|
||||
route/3,
|
||||
have_connection/1,
|
||||
has_key/2,
|
||||
get_connections_pids/1,
|
||||
try_register/1,
|
||||
remove_connection/3,
|
||||
find_connection/2,
|
||||
dirty_get_connections/0,
|
||||
allow_host/2,
|
||||
incoming_s2s_number/0,
|
||||
outgoing_s2s_number/0,
|
||||
-export([start_link/0, route/3, have_connection/1,
|
||||
has_key/2, get_connections_pids/1, 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,
|
||||
list_temporarily_blocked_hosts/0,
|
||||
external_host_overloaded/1,
|
||||
is_temporarly_blocked/1
|
||||
]).
|
||||
external_host_overloaded/1, is_temporarly_blocked/1]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
|
||||
%% ejabberd API
|
||||
-export([get_info_s2s_connections/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
|
||||
-include("ejabberd_commands.hrl").
|
||||
|
||||
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER, 1).
|
||||
|
||||
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE, 1).
|
||||
|
||||
-define(S2S_OVERLOAD_BLOCK_PERIOD, 60).
|
||||
|
||||
%% 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(temporarily_blocked, {host, timestamp}).
|
||||
-record(temporarily_blocked, {host = <<"">> :: binary(),
|
||||
timestamp = now() :: erlang:timestamp()}).
|
||||
|
||||
-type temporarily_blocked() :: #temporarily_blocked{}.
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
@ -77,57 +80,73 @@
|
||||
%% Description: Starts the server
|
||||
%%--------------------------------------------------------------------
|
||||
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) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nwhen processing: ~p",
|
||||
[Reason, {From, To, Packet}]);
|
||||
_ ->
|
||||
ok
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
clean_temporarily_blocked_table() ->
|
||||
mnesia:clear_table(temporarily_blocked).
|
||||
|
||||
-spec list_temporarily_blocked_hosts() -> [temporarily_blocked()].
|
||||
|
||||
list_temporarily_blocked_hosts() ->
|
||||
ets:tab2list(temporarily_blocked).
|
||||
|
||||
-spec external_host_overloaded(binary()) -> {aborted, any()} | {atomic, ok}.
|
||||
|
||||
external_host_overloaded(Host) ->
|
||||
?INFO_MSG("Disabling connections from ~s for ~p seconds", [Host, ?S2S_OVERLOAD_BLOCK_PERIOD]),
|
||||
mnesia:transaction( fun() ->
|
||||
mnesia:write(#temporarily_blocked{host = Host, timestamp = now()})
|
||||
?INFO_MSG("Disabling connections from ~s for ~p "
|
||||
"seconds",
|
||||
[Host, ?S2S_OVERLOAD_BLOCK_PERIOD]),
|
||||
mnesia:transaction(fun () ->
|
||||
mnesia:write(#temporarily_blocked{host = Host,
|
||||
timestamp =
|
||||
now()})
|
||||
end).
|
||||
|
||||
-spec is_temporarly_blocked(binary()) -> boolean().
|
||||
|
||||
is_temporarly_blocked(Host) ->
|
||||
case mnesia:dirty_read(temporarily_blocked, Host) of
|
||||
[] -> false;
|
||||
[#temporarily_blocked{timestamp = T}=Entry] ->
|
||||
[#temporarily_blocked{timestamp = T} = Entry] ->
|
||||
case timer:now_diff(now(), T) of
|
||||
N when N > ?S2S_OVERLOAD_BLOCK_PERIOD * 1000 * 1000 ->
|
||||
mnesia:dirty_delete_object(Entry),
|
||||
false;
|
||||
_ ->
|
||||
true
|
||||
N when N > (?S2S_OVERLOAD_BLOCK_PERIOD) * 1000 * 1000 ->
|
||||
mnesia:dirty_delete_object(Entry), false;
|
||||
_ -> true
|
||||
end
|
||||
end.
|
||||
|
||||
-spec remove_connection({binary(), binary()},
|
||||
pid(), binary()) -> {atomic, ok} |
|
||||
ok |
|
||||
{aborted, any()}.
|
||||
|
||||
remove_connection(FromTo, Pid, Key) ->
|
||||
case catch mnesia:dirty_match_object(s2s, #s2s{fromto = FromTo,
|
||||
pid = Pid,
|
||||
_ = '_'}) of
|
||||
case catch mnesia:dirty_match_object(s2s,
|
||||
#s2s{fromto = FromTo, pid = Pid,
|
||||
_ = '_'})
|
||||
of
|
||||
[#s2s{pid = Pid, key = Key}] ->
|
||||
F = fun() ->
|
||||
mnesia:delete_object(#s2s{fromto = FromTo,
|
||||
pid = Pid,
|
||||
F = fun () ->
|
||||
mnesia:delete_object(#s2s{fromto = FromTo, pid = Pid,
|
||||
key = Key})
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
_ ->
|
||||
ok
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
-spec have_connection({binary(), binary()}) -> boolean().
|
||||
|
||||
have_connection(FromTo) ->
|
||||
case catch mnesia:dirty_read(s2s, FromTo) of
|
||||
[_] ->
|
||||
@ -136,6 +155,8 @@ have_connection(FromTo) ->
|
||||
false
|
||||
end.
|
||||
|
||||
-spec has_key({binary(), binary()}, binary()) -> boolean().
|
||||
|
||||
has_key(FromTo, Key) ->
|
||||
case mnesia:dirty_select(s2s,
|
||||
[{#s2s{fromto = FromTo, key = Key, _ = '_'},
|
||||
@ -147,6 +168,8 @@ has_key(FromTo, Key) ->
|
||||
true
|
||||
end.
|
||||
|
||||
-spec get_connections_pids({binary(), binary()}) -> [pid()].
|
||||
|
||||
get_connections_pids(FromTo) ->
|
||||
case catch mnesia:dirty_read(s2s, FromTo) of
|
||||
L when is_list(L) ->
|
||||
@ -155,33 +178,32 @@ get_connections_pids(FromTo) ->
|
||||
[]
|
||||
end.
|
||||
|
||||
-spec try_register({binary(), binary()}) -> {key, binary()} | false.
|
||||
|
||||
try_register(FromTo) ->
|
||||
Key = randoms:get_string(),
|
||||
MaxS2SConnectionsNumber = max_s2s_connections_number(FromTo),
|
||||
MaxS2SConnectionsNumberPerNode =
|
||||
max_s2s_connections_number_per_node(FromTo),
|
||||
F = fun() ->
|
||||
F = fun () ->
|
||||
L = mnesia:read({s2s, FromTo}),
|
||||
NeededConnections = needed_connections_number(
|
||||
L, MaxS2SConnectionsNumber,
|
||||
NeededConnections = needed_connections_number(L,
|
||||
MaxS2SConnectionsNumber,
|
||||
MaxS2SConnectionsNumberPerNode),
|
||||
if
|
||||
NeededConnections > 0 ->
|
||||
mnesia:write(#s2s{fromto = FromTo,
|
||||
pid = self(),
|
||||
if NeededConnections > 0 ->
|
||||
mnesia:write(#s2s{fromto = FromTo, pid = self(),
|
||||
key = Key}),
|
||||
{key, Key};
|
||||
true ->
|
||||
false
|
||||
true -> false
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, Res} ->
|
||||
Res;
|
||||
_ ->
|
||||
false
|
||||
{atomic, Res} -> Res;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
-spec dirty_get_connections() -> [{binary(), binary()}].
|
||||
|
||||
dirty_get_connections() ->
|
||||
mnesia:dirty_all_keys(s2s).
|
||||
|
||||
@ -242,12 +264,10 @@ handle_info({route, From, To, Packet}, State) ->
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nwhen processing: ~p",
|
||||
[Reason, {From, To, Packet}]);
|
||||
_ ->
|
||||
ok
|
||||
_ -> ok
|
||||
end,
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
handle_info(_Info, State) -> {noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: terminate(Reason, State) -> void()
|
||||
@ -284,72 +304,75 @@ clean_table_from_bad_node(Node) ->
|
||||
mnesia:async_dirty(F).
|
||||
|
||||
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]),
|
||||
case find_connection(From, To) of
|
||||
{atomic, Pid} when is_pid(Pid) ->
|
||||
?DEBUG("sending to process ~p~n", [Pid]),
|
||||
{xmlelement, Name, Attrs, Els} = Packet,
|
||||
NewAttrs = jlib:replace_from_to_attrs(jlib:jid_to_string(From),
|
||||
jlib:jid_to_string(To),
|
||||
Attrs),
|
||||
#xmlel{name = Name, attrs = Attrs, children = Els} =
|
||||
Packet,
|
||||
NewAttrs =
|
||||
jlib:replace_from_to_attrs(jlib:jid_to_string(From),
|
||||
jlib:jid_to_string(To), Attrs),
|
||||
#jid{lserver = MyServer} = From,
|
||||
ejabberd_hooks:run(
|
||||
s2s_send_packet,
|
||||
MyServer,
|
||||
ejabberd_hooks:run(s2s_send_packet, MyServer,
|
||||
[From, To, Packet]),
|
||||
send_element(Pid, {xmlelement, Name, NewAttrs, Els}),
|
||||
send_element(Pid,
|
||||
#xmlel{name = Name, attrs = NewAttrs, children = Els}),
|
||||
ok;
|
||||
{aborted, _Reason} ->
|
||||
case xml:get_tag_attr_s("type", Packet) of
|
||||
"error" -> ok;
|
||||
"result" -> ok;
|
||||
case xml:get_tag_attr_s(<<"type">>, Packet) of
|
||||
<<"error">> -> ok;
|
||||
<<"result">> -> ok;
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERR_SERVICE_UNAVAILABLE),
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end,
|
||||
false
|
||||
end.
|
||||
|
||||
-spec find_connection(jid(), jid()) -> {aborted, any()} | {atomic, pid()}.
|
||||
|
||||
find_connection(From, To) ->
|
||||
#jid{lserver = MyServer} = From,
|
||||
#jid{lserver = Server} = To,
|
||||
FromTo = {MyServer, Server},
|
||||
MaxS2SConnectionsNumber = max_s2s_connections_number(FromTo),
|
||||
MaxS2SConnectionsNumber =
|
||||
max_s2s_connections_number(FromTo),
|
||||
MaxS2SConnectionsNumberPerNode =
|
||||
max_s2s_connections_number_per_node(FromTo),
|
||||
?DEBUG("Finding connection for ~p~n", [FromTo]),
|
||||
case catch mnesia:dirty_read(s2s, FromTo) of
|
||||
{'EXIT', Reason} ->
|
||||
{aborted, Reason};
|
||||
{'EXIT', Reason} -> {aborted, Reason};
|
||||
[] ->
|
||||
%% We try to establish all the connections if the host is not a
|
||||
%% service and if the s2s host is not blacklisted or
|
||||
%% 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 ->
|
||||
NeededConnections = needed_connections_number(
|
||||
[], MaxS2SConnectionsNumber,
|
||||
NeededConnections = needed_connections_number([],
|
||||
MaxS2SConnectionsNumber,
|
||||
MaxS2SConnectionsNumberPerNode),
|
||||
open_several_connections(
|
||||
NeededConnections, MyServer,
|
||||
open_several_connections(NeededConnections, MyServer,
|
||||
Server, From, FromTo,
|
||||
MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode);
|
||||
false ->
|
||||
{aborted, error}
|
||||
MaxS2SConnectionsNumber,
|
||||
MaxS2SConnectionsNumberPerNode);
|
||||
false -> {aborted, error}
|
||||
end;
|
||||
L when is_list(L) ->
|
||||
NeededConnections = needed_connections_number(
|
||||
L, MaxS2SConnectionsNumber,
|
||||
NeededConnections = needed_connections_number(L,
|
||||
MaxS2SConnectionsNumber,
|
||||
MaxS2SConnectionsNumberPerNode),
|
||||
if
|
||||
NeededConnections > 0 ->
|
||||
if NeededConnections > 0 ->
|
||||
%% We establish the missing connections for this pair.
|
||||
open_several_connections(
|
||||
NeededConnections, MyServer,
|
||||
open_several_connections(NeededConnections, MyServer,
|
||||
Server, From, FromTo,
|
||||
MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode);
|
||||
MaxS2SConnectionsNumber,
|
||||
MaxS2SConnectionsNumberPerNode);
|
||||
true ->
|
||||
%% We choose a connexion from the pool of opened ones.
|
||||
{atomic, choose_connection(From, L)}
|
||||
@ -364,26 +387,23 @@ choose_pid(From, Pids) ->
|
||||
[] -> Pids;
|
||||
Ps -> Ps
|
||||
end,
|
||||
% Use sticky connections based on the JID of the sender (whithout
|
||||
% the resource to ensure that a muc room always uses the same
|
||||
% connection)
|
||||
Pid = lists:nth(erlang:phash(jlib:jid_remove_resource(From), length(Pids1)),
|
||||
Pid =
|
||||
lists:nth(erlang:phash(jlib:jid_remove_resource(From),
|
||||
length(Pids1)),
|
||||
Pids1),
|
||||
?DEBUG("Using ejabberd_s2s_out ~p~n", [Pid]),
|
||||
Pid.
|
||||
|
||||
open_several_connections(N, MyServer, Server, From, FromTo,
|
||||
MaxS2SConnectionsNumber,
|
||||
open_several_connections(N, MyServer, Server, From,
|
||||
FromTo, MaxS2SConnectionsNumber,
|
||||
MaxS2SConnectionsNumberPerNode) ->
|
||||
ConnectionsResult =
|
||||
[new_connection(MyServer, Server, From, FromTo,
|
||||
MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode)
|
||||
ConnectionsResult = [new_connection(MyServer, Server,
|
||||
From, FromTo, MaxS2SConnectionsNumber,
|
||||
MaxS2SConnectionsNumberPerNode)
|
||||
|| _N <- lists:seq(1, N)],
|
||||
case [PID || {atomic, PID} <- ConnectionsResult] of
|
||||
[] ->
|
||||
hd(ConnectionsResult);
|
||||
PIDs ->
|
||||
{atomic, choose_pid(From, PIDs)}
|
||||
[] -> hd(ConnectionsResult);
|
||||
PIDs -> {atomic, choose_pid(From, PIDs)}
|
||||
end.
|
||||
|
||||
new_connection(MyServer, Server, From, FromTo,
|
||||
@ -393,39 +413,36 @@ new_connection(MyServer, Server, From, FromTo,
|
||||
MyServer, Server, {new, Key}),
|
||||
F = fun() ->
|
||||
L = mnesia:read({s2s, FromTo}),
|
||||
NeededConnections = needed_connections_number(
|
||||
L, MaxS2SConnectionsNumber,
|
||||
NeededConnections = needed_connections_number(L,
|
||||
MaxS2SConnectionsNumber,
|
||||
MaxS2SConnectionsNumberPerNode),
|
||||
if
|
||||
NeededConnections > 0 ->
|
||||
mnesia:write(#s2s{fromto = FromTo,
|
||||
pid = Pid,
|
||||
if NeededConnections > 0 ->
|
||||
mnesia:write(#s2s{fromto = FromTo, pid = Pid,
|
||||
key = Key}),
|
||||
?INFO_MSG("New s2s connection started ~p", [Pid]),
|
||||
Pid;
|
||||
true ->
|
||||
choose_connection(From, L)
|
||||
true -> choose_connection(From, L)
|
||||
end
|
||||
end,
|
||||
TRes = mnesia:transaction(F),
|
||||
case TRes of
|
||||
{atomic, Pid} ->
|
||||
ejabberd_s2s_out:start_connection(Pid);
|
||||
_ ->
|
||||
ejabberd_s2s_out:stop_connection(Pid)
|
||||
{atomic, Pid} -> ejabberd_s2s_out:start_connection(Pid);
|
||||
_ -> ejabberd_s2s_out:stop_connection(Pid)
|
||||
end,
|
||||
TRes.
|
||||
|
||||
max_s2s_connections_number({From, To}) ->
|
||||
case acl:match_rule(
|
||||
From, max_s2s_connections, jlib:make_jid("", To, "")) of
|
||||
case acl:match_rule(From, max_s2s_connections,
|
||||
jlib:make_jid(<<"">>, To, <<"">>))
|
||||
of
|
||||
Max when is_integer(Max) -> Max;
|
||||
_ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER
|
||||
end.
|
||||
|
||||
max_s2s_connections_number_per_node({From, To}) ->
|
||||
case acl:match_rule(
|
||||
From, max_s2s_connections_per_node, jlib:make_jid("", To, "")) of
|
||||
case acl:match_rule(From, max_s2s_connections_per_node,
|
||||
jlib:make_jid(<<"">>, To, <<"">>))
|
||||
of
|
||||
Max when is_integer(Max) -> Max;
|
||||
_ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE
|
||||
end.
|
||||
@ -443,45 +460,46 @@ needed_connections_number(Ls, MaxS2SConnectionsNumber,
|
||||
%% --------------------------------------------------------------------
|
||||
is_service(From, To) ->
|
||||
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
|
||||
false;
|
||||
_ ->
|
||||
Hosts = ?MYHOSTS,
|
||||
P = fun(ParentDomain) -> lists:member(ParentDomain, Hosts) end,
|
||||
undefined ->
|
||||
Hosts = (?MYHOSTS),
|
||||
P = fun (ParentDomain) ->
|
||||
lists:member(ParentDomain, Hosts)
|
||||
end,
|
||||
lists:any(P, parent_domains(To#jid.lserver))
|
||||
end.
|
||||
|
||||
parent_domains(Domain) ->
|
||||
lists:foldl(
|
||||
fun(Label, []) ->
|
||||
[Label];
|
||||
lists:foldl(fun (Label, []) -> [Label];
|
||||
(Label, [Head | Tail]) ->
|
||||
[Label ++ "." ++ Head, Head | Tail]
|
||||
end, [], lists:reverse(string:tokens(Domain, "."))).
|
||||
|
||||
send_element(Pid, El) ->
|
||||
Pid ! {send_element, El}.
|
||||
[<<Label/binary, ".", Head/binary>>, Head | Tail]
|
||||
end,
|
||||
[], lists:reverse(str:tokens(Domain, <<".">>))).
|
||||
|
||||
send_element(Pid, El) -> Pid ! {send_element, El}.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% ejabberd commands
|
||||
|
||||
commands() ->
|
||||
[
|
||||
#ejabberd_commands{name = incoming_s2s_number,
|
||||
[#ejabberd_commands{name = incoming_s2s_number,
|
||||
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,
|
||||
args = [],
|
||||
result = {s2s_incoming, integer}},
|
||||
args = [], result = {s2s_incoming, integer}},
|
||||
#ejabberd_commands{name = outgoing_s2s_number,
|
||||
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,
|
||||
args = [],
|
||||
result = {s2s_outgoing, integer}}
|
||||
].
|
||||
args = [], result = {s2s_outgoing, integer}}].
|
||||
|
||||
incoming_s2s_number() ->
|
||||
length(supervisor:which_children(ejabberd_s2s_in_sup)).
|
||||
@ -489,28 +507,21 @@ incoming_s2s_number() ->
|
||||
outgoing_s2s_number() ->
|
||||
length(supervisor:which_children(ejabberd_s2s_out_sup)).
|
||||
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Update Mnesia tables
|
||||
|
||||
update_tables() ->
|
||||
case catch mnesia:table_info(s2s, type) of
|
||||
bag ->
|
||||
ok;
|
||||
{'EXIT', _} ->
|
||||
ok;
|
||||
_ ->
|
||||
% XXX TODO convert it ?
|
||||
mnesia:delete_table(s2s)
|
||||
bag -> ok;
|
||||
{'EXIT', _} -> ok;
|
||||
_ -> mnesia:delete_table(s2s)
|
||||
end,
|
||||
case catch mnesia:table_info(s2s, attributes) of
|
||||
[fromto, node, key] ->
|
||||
mnesia:transform_table(s2s, ignore, [fromto, pid, key]),
|
||||
mnesia:clear_table(s2s);
|
||||
[fromto, pid, key] ->
|
||||
ok;
|
||||
{'EXIT', _} ->
|
||||
ok
|
||||
[fromto, pid, key] -> ok;
|
||||
{'EXIT', _} -> ok
|
||||
end,
|
||||
case lists:member(local_s2s, mnesia:system_info(tables)) of
|
||||
true ->
|
||||
@ -521,30 +532,37 @@ update_tables() ->
|
||||
|
||||
%% Check if host is in blacklist or white list
|
||||
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) ->
|
||||
Hosts = ?MYHOSTS,
|
||||
case lists:dropwhile(
|
||||
fun(ParentDomain) ->
|
||||
Hosts = (?MYHOSTS),
|
||||
case lists:dropwhile(fun (ParentDomain) ->
|
||||
not lists:member(ParentDomain, Hosts)
|
||||
end, parent_domains(MyServer)) of
|
||||
[MyHost|_] ->
|
||||
allow_host1(MyHost, S2SHost);
|
||||
[] ->
|
||||
allow_host1(MyServer, S2SHost)
|
||||
end,
|
||||
parent_domains(MyServer))
|
||||
of
|
||||
[MyHost | _] -> allow_host1(MyHost, S2SHost);
|
||||
[] -> allow_host1(MyServer, S2SHost)
|
||||
end.
|
||||
|
||||
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;
|
||||
allow -> true;
|
||||
_ ->
|
||||
case ejabberd_config:get_local_option({s2s_default_policy, MyHost}) of
|
||||
undefined ->
|
||||
case ejabberd_config:get_local_option(
|
||||
{s2s_default_policy, MyHost},
|
||||
fun(deny) -> deny; (allow) -> allow end)
|
||||
of
|
||||
deny -> false;
|
||||
_ ->
|
||||
case ejabberd_hooks:run_fold(s2s_allow_host, MyHost,
|
||||
allow, [MyHost, S2SHost]) of
|
||||
allow, [MyHost, S2SHost])
|
||||
of
|
||||
deny -> false;
|
||||
allow -> true;
|
||||
_ -> true
|
||||
@ -556,27 +574,32 @@ allow_host1(MyHost, S2SHost) ->
|
||||
%% @spec (Type) -> [Info]
|
||||
%% where Type = in | out
|
||||
%% Info = [{InfoName::atom(), InfoValue::any()}]
|
||||
|
||||
get_info_s2s_connections(Type) ->
|
||||
ChildType = case Type of
|
||||
in -> ejabberd_s2s_in_sup;
|
||||
out -> ejabberd_s2s_out_sup
|
||||
end,
|
||||
Connections = supervisor:which_children(ChildType),
|
||||
get_s2s_info(Connections,Type).
|
||||
get_s2s_info(Connections, Type).
|
||||
|
||||
get_s2s_info(Connections,Type)->
|
||||
complete_s2s_info(Connections,Type,[]).
|
||||
complete_s2s_info([],_,Result)->
|
||||
Result;
|
||||
complete_s2s_info([Connection|T],Type,Result)->
|
||||
{_,PID,_,_}=Connection,
|
||||
get_s2s_info(Connections, Type) ->
|
||||
complete_s2s_info(Connections, Type, []).
|
||||
|
||||
complete_s2s_info([], _, Result) -> Result;
|
||||
complete_s2s_info([Connection | T], Type, Result) ->
|
||||
{_, PID, _, _} = Connection,
|
||||
State = get_s2s_state(PID),
|
||||
complete_s2s_info(T,Type,[State|Result]).
|
||||
complete_s2s_info(T, Type, [State | Result]).
|
||||
|
||||
get_s2s_state(S2sPid)->
|
||||
Infos = case gen_fsm:sync_send_all_state_event(S2sPid,get_state_infos) of
|
||||
-spec get_s2s_state(pid()) -> [{status, open | closed | error} | {s2s_pid, pid()}].
|
||||
|
||||
get_s2s_state(S2sPid) ->
|
||||
Infos = case gen_fsm:sync_send_all_state_event(S2sPid,
|
||||
get_state_infos)
|
||||
of
|
||||
{state_infos, Is} -> [{status, open} | Is];
|
||||
{noproc,_} -> [{status, closed}]; %% Connection closed
|
||||
{badrpc,_} -> [{status, error}]
|
||||
{noproc, _} -> [{status, closed}]; %% Connection closed
|
||||
{badrpc, _} -> [{status, error}]
|
||||
end,
|
||||
[{s2s_pid, S2sPid} | Infos].
|
||||
|
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).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-define(GEN_FSM, p1_fsm).
|
||||
@ -32,82 +33,78 @@
|
||||
-behaviour(?GEN_FSM).
|
||||
|
||||
%% External exports
|
||||
-export([start/2,
|
||||
start_link/2,
|
||||
send_text/2,
|
||||
send_element/2,
|
||||
socket_type/0]).
|
||||
-export([start/2, start_link/2, send_text/2,
|
||||
send_element/2, socket_type/0]).
|
||||
|
||||
%% gen_fsm callbacks
|
||||
-export([init/1,
|
||||
wait_for_stream/2,
|
||||
wait_for_handshake/2,
|
||||
stream_established/2,
|
||||
handle_event/3,
|
||||
handle_sync_event/4,
|
||||
code_change/4,
|
||||
handle_info/3,
|
||||
terminate/3,
|
||||
print_state/1]).
|
||||
-export([init/1, wait_for_stream/2,
|
||||
wait_for_handshake/2, stream_established/2,
|
||||
handle_event/3, handle_sync_event/4, code_change/4,
|
||||
handle_info/3, terminate/3, print_state/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
|
||||
-record(state, {socket, sockmod, streamid,
|
||||
hosts, password, access,
|
||||
check_from}).
|
||||
-record(state,
|
||||
{socket :: ejabberd_socket:socket_state(),
|
||||
sockmod = ejabberd_socket :: ejabberd_socket | ejabberd_frontend_socket,
|
||||
streamid = <<"">> :: binary(),
|
||||
hosts = [] :: [binary()],
|
||||
password = <<"">> :: binary(),
|
||||
access :: atom(),
|
||||
check_from = true :: boolean()}).
|
||||
|
||||
%-define(DBGFSM, true).
|
||||
|
||||
-ifdef(DBGFSM).
|
||||
|
||||
-define(FSMOPTS, [{debug, [trace]}]).
|
||||
|
||||
-else.
|
||||
|
||||
-define(FSMOPTS, []).
|
||||
|
||||
-endif.
|
||||
|
||||
-define(STREAM_HEADER,
|
||||
"<?xml version='1.0'?>"
|
||||
"<stream:stream "
|
||||
"xmlns:stream='http://etherx.jabber.org/streams' "
|
||||
"xmlns='jabber:component:accept' "
|
||||
"id='~s' from='~s'>"
|
||||
).
|
||||
<<"<?xml version='1.0'?><stream:stream "
|
||||
"xmlns:stream='http://etherx.jabber.org/stream"
|
||||
"s' xmlns='jabber:component:accept' id='~s' "
|
||||
"from='~s'>">>).
|
||||
|
||||
-define(STREAM_TRAILER, "</stream:stream>").
|
||||
-define(STREAM_TRAILER, <<"</stream:stream>">>).
|
||||
|
||||
-define(INVALID_HEADER_ERR,
|
||||
"<stream:stream "
|
||||
"xmlns:stream='http://etherx.jabber.org/streams'>"
|
||||
"<stream:error>Invalid Stream Header</stream:error>"
|
||||
"</stream:stream>"
|
||||
).
|
||||
<<"<stream:stream xmlns:stream='http://etherx.ja"
|
||||
"bber.org/streams'><stream:error>Invalid "
|
||||
"Stream Header</stream:error></stream:stream>">>).
|
||||
|
||||
-define(INVALID_HANDSHAKE_ERR,
|
||||
"<stream:error>"
|
||||
"<not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>"
|
||||
"<text xmlns='urn:ietf:params:xml:ns:xmpp-streams' xml:lang='en'>"
|
||||
"Invalid Handshake</text>"
|
||||
"</stream:error>"
|
||||
"</stream:stream>"
|
||||
).
|
||||
<<"<stream:error><not-authorized xmlns='urn:ietf"
|
||||
":params:xml:ns:xmpp-streams'/><text "
|
||||
"xmlns='urn:ietf:params:xml:ns:xmpp-streams' "
|
||||
"xml:lang='en'>Invalid Handshake</text></strea"
|
||||
"m:error></stream:stream>">>).
|
||||
|
||||
-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,
|
||||
xml:element_to_string(?SERR_INVALID_NAMESPACE)).
|
||||
xml:element_to_binary(?SERR_INVALID_NAMESPACE)).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(SockData, Opts) ->
|
||||
supervisor:start_child(ejabberd_service_sup, [SockData, Opts]).
|
||||
supervisor:start_child(ejabberd_service_sup,
|
||||
[SockData, Opts]).
|
||||
|
||||
start_link(SockData, Opts) ->
|
||||
?GEN_FSM:start_link(ejabberd_service, [SockData, Opts],
|
||||
fsm_limit_opts(Opts) ++ ?FSMOPTS).
|
||||
(?GEN_FSM):start_link(ejabberd_service,
|
||||
[SockData, Opts], fsm_limit_opts(Opts) ++ (?FSMOPTS)).
|
||||
|
||||
socket_type() ->
|
||||
xml_stream.
|
||||
socket_type() -> xml_stream.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Callback functions from gen_fsm
|
||||
@ -126,12 +123,11 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
{value, {_, A}} -> A;
|
||||
_ -> all
|
||||
end,
|
||||
{Hosts, Password} =
|
||||
case lists:keysearch(hosts, 1, Opts) of
|
||||
{Hosts, Password} = case lists:keysearch(hosts, 1, Opts)
|
||||
of
|
||||
{value, {_, Hs, HOpts}} ->
|
||||
case lists:keysearch(password, 1, HOpts) of
|
||||
{value, {_, P}} ->
|
||||
{Hs, P};
|
||||
{value, {_, P}} -> {Hs, P};
|
||||
_ ->
|
||||
% TODO: generate error
|
||||
false
|
||||
@ -140,8 +136,7 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
case lists:keysearch(host, 1, Opts) of
|
||||
{value, {_, H, HOpts}} ->
|
||||
case lists:keysearch(password, 1, HOpts) of
|
||||
{value, {_, P}} ->
|
||||
{[H], P};
|
||||
{value, {_, P}} -> {[H], P};
|
||||
_ ->
|
||||
% TODO: generate error
|
||||
false
|
||||
@ -155,19 +150,17 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
{value, {_, S}} -> S;
|
||||
_ -> none
|
||||
end,
|
||||
CheckFrom = case lists:keysearch(service_check_from, 1, Opts) of
|
||||
CheckFrom = case lists:keysearch(service_check_from, 1,
|
||||
Opts)
|
||||
of
|
||||
{value, {_, CF}} -> CF;
|
||||
_ -> true
|
||||
end,
|
||||
SockMod:change_shaper(Socket, Shaper),
|
||||
{ok, wait_for_stream, #state{socket = Socket,
|
||||
sockmod = SockMod,
|
||||
streamid = new_id(),
|
||||
hosts = Hosts,
|
||||
password = Password,
|
||||
access = Access,
|
||||
check_from = CheckFrom
|
||||
}}.
|
||||
{ok, wait_for_stream,
|
||||
#state{socket = Socket, sockmod = SockMod,
|
||||
streamid = new_id(), hosts = Hosts, password = Password,
|
||||
access = Access, check_from = CheckFrom}}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: StateName/2
|
||||
@ -176,14 +169,11 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
%% {stop, Reason, NewStateData}
|
||||
%%----------------------------------------------------------------------
|
||||
|
||||
wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
"jabber:component:accept" ->
|
||||
%% Note: XEP-0114 requires to check that destination is a Jabber
|
||||
%% component served by this Jabber server.
|
||||
%% However several transports don't respect that,
|
||||
%% so ejabberd doesn't check 'to' attribute (EJAB-717)
|
||||
To = xml:get_attr_s("to", Attrs),
|
||||
wait_for_stream({xmlstreamstart, _Name, Attrs},
|
||||
StateData) ->
|
||||
case xml:get_attr_s(<<"xmlns">>, Attrs) of
|
||||
<<"jabber:component:accept">> ->
|
||||
To = xml:get_attr_s(<<"to">>, Attrs),
|
||||
Header = io_lib:format(?STREAM_HEADER,
|
||||
[StateData#state.streamid, xml:crypt(To)]),
|
||||
send_text(StateData, Header),
|
||||
@ -192,55 +182,52 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
send_text(StateData, ?INVALID_HEADER_ERR),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
|
||||
wait_for_stream({xmlstreamerror, _}, StateData) ->
|
||||
Header = io_lib:format(?STREAM_HEADER,
|
||||
["none", ?MYNAME]),
|
||||
[<<"none">>, ?MYNAME]),
|
||||
send_text(StateData,
|
||||
Header ++ ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
|
||||
<<(list_to_binary(Header))/binary, (?INVALID_XML_ERR)/binary,
|
||||
(?STREAM_TRAILER)/binary>>),
|
||||
{stop, normal, StateData};
|
||||
|
||||
wait_for_stream(closed, StateData) ->
|
||||
{stop, normal, 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
|
||||
{"handshake", Digest} ->
|
||||
case sha:sha(StateData#state.streamid ++
|
||||
StateData#state.password) of
|
||||
{<<"handshake">>, Digest} ->
|
||||
case sha:sha(<<(StateData#state.streamid)/binary,
|
||||
(StateData#state.password)/binary>>)
|
||||
of
|
||||
Digest ->
|
||||
send_text(StateData, "<handshake/>"),
|
||||
lists:foreach(
|
||||
fun(H) ->
|
||||
send_text(StateData, <<"<handshake/>">>),
|
||||
lists:foreach(fun (H) ->
|
||||
ejabberd_router:register_route(H),
|
||||
?INFO_MSG("Route registered for service ~p~n", [H])
|
||||
end, StateData#state.hosts),
|
||||
?INFO_MSG("Route registered for service ~p~n",
|
||||
[H])
|
||||
end,
|
||||
StateData#state.hosts),
|
||||
{next_state, stream_established, StateData};
|
||||
_ ->
|
||||
send_text(StateData, ?INVALID_HANDSHAKE_ERR),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
_ ->
|
||||
{next_state, wait_for_handshake, StateData}
|
||||
_ -> {next_state, wait_for_handshake, StateData}
|
||||
end;
|
||||
|
||||
wait_for_handshake({xmlstreamend, _Name}, StateData) ->
|
||||
{stop, normal, 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};
|
||||
|
||||
wait_for_handshake(closed, StateData) ->
|
||||
{stop, normal, StateData}.
|
||||
|
||||
|
||||
stream_established({xmlstreamelement, El}, StateData) ->
|
||||
NewEl = jlib:remove_attr("xmlns", El),
|
||||
{xmlelement, Name, Attrs, _Els} = NewEl,
|
||||
From = xml:get_attr_s("from", Attrs),
|
||||
NewEl = jlib:remove_attr(<<"xmlns">>, El),
|
||||
#xmlel{name = Name, attrs = Attrs} = NewEl,
|
||||
From = xml:get_attr_s(<<"from">>, Attrs),
|
||||
FromJID = case StateData#state.check_from of
|
||||
%% If the admin does not want to check the from field
|
||||
%% when accept packets from any address.
|
||||
@ -259,15 +246,15 @@ stream_established({xmlstreamelement, El}, StateData) ->
|
||||
_ -> error
|
||||
end
|
||||
end,
|
||||
To = xml:get_attr_s("to", Attrs),
|
||||
To = xml:get_attr_s(<<"to">>, Attrs),
|
||||
ToJID = case To of
|
||||
"" -> error;
|
||||
<<"">> -> error;
|
||||
_ -> jlib:string_to_jid(To)
|
||||
end,
|
||||
if ((Name == "iq") or
|
||||
(Name == "message") or
|
||||
(Name == "presence")) and
|
||||
(ToJID /= error) and (FromJID /= error) ->
|
||||
if ((Name == <<"iq">>) or (Name == <<"message">>) or
|
||||
(Name == <<"presence">>))
|
||||
and (ToJID /= error)
|
||||
and (FromJID /= error) ->
|
||||
ejabberd_router:route(FromJID, ToJID, NewEl);
|
||||
true ->
|
||||
Err = jlib:make_error_reply(NewEl, ?ERR_BAD_REQUEST),
|
||||
@ -275,21 +262,16 @@ stream_established({xmlstreamelement, El}, StateData) ->
|
||||
error
|
||||
end,
|
||||
{next_state, stream_established, StateData};
|
||||
|
||||
stream_established({xmlstreamend, _Name}, StateData) ->
|
||||
% TODO
|
||||
{stop, normal, 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};
|
||||
|
||||
stream_established(closed, StateData) ->
|
||||
% TODO
|
||||
{stop, normal, StateData}.
|
||||
|
||||
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: StateName/3
|
||||
%% Returns: {next_state, NextStateName, NextStateData} |
|
||||
@ -321,9 +303,9 @@ handle_event(_Event, StateName, StateData) ->
|
||||
%% {stop, Reason, NewStateData} |
|
||||
%% {stop, Reason, Reply, NewStateData}
|
||||
%%----------------------------------------------------------------------
|
||||
handle_sync_event(_Event, _From, StateName, StateData) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, StateName, StateData}.
|
||||
handle_sync_event(_Event, _From, StateName,
|
||||
StateData) ->
|
||||
Reply = ok, {reply, Reply, StateName, StateData}.
|
||||
|
||||
code_change(_OldVsn, StateName, StateData, _Extra) ->
|
||||
{ok, StateName, StateData}.
|
||||
@ -340,14 +322,19 @@ handle_info({send_text, Text}, StateName, StateData) ->
|
||||
handle_info({send_element, El}, StateName, StateData) ->
|
||||
send_element(StateData, El),
|
||||
{next_state, StateName, StateData};
|
||||
handle_info({route, From, To, Packet}, StateName, StateData) ->
|
||||
case acl:match_rule(global, StateData#state.access, From) of
|
||||
handle_info({route, From, To, Packet}, StateName,
|
||||
StateData) ->
|
||||
case acl:match_rule(global, StateData#state.access,
|
||||
From)
|
||||
of
|
||||
allow ->
|
||||
{xmlelement, Name, Attrs, Els} = Packet,
|
||||
Attrs2 = jlib:replace_from_to_attrs(jlib:jid_to_string(From),
|
||||
jlib:jid_to_string(To),
|
||||
Attrs),
|
||||
Text = xml:element_to_binary({xmlelement, Name, Attrs2, Els}),
|
||||
#xmlel{name = Name, attrs = Attrs, children = Els} =
|
||||
Packet,
|
||||
Attrs2 =
|
||||
jlib:replace_from_to_attrs(jlib:jid_to_string(From),
|
||||
jlib:jid_to_string(To), Attrs),
|
||||
Text = xml:element_to_binary(#xmlel{name = Name,
|
||||
attrs = Attrs2, children = Els}),
|
||||
send_text(StateData, Text);
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
|
||||
@ -358,7 +345,6 @@ handle_info(Info, StateName, StateData) ->
|
||||
?ERROR_MSG("Unexpected info: ~p", [Info]),
|
||||
{next_state, StateName, StateData}.
|
||||
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: terminate/3
|
||||
%% Purpose: Shutdown the fsm
|
||||
@ -368,12 +354,11 @@ terminate(Reason, StateName, StateData) ->
|
||||
?INFO_MSG("terminated: ~p", [Reason]),
|
||||
case StateName of
|
||||
stream_established ->
|
||||
lists:foreach(
|
||||
fun(H) ->
|
||||
lists:foreach(fun (H) ->
|
||||
ejabberd_router:unregister_route(H)
|
||||
end, StateData#state.hosts);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
StateData#state.hosts);
|
||||
_ -> ok
|
||||
end,
|
||||
(StateData#state.sockmod):close(StateData#state.socket),
|
||||
ok.
|
||||
@ -383,31 +368,30 @@ terminate(Reason, StateName, StateData) ->
|
||||
%% Purpose: Prepare the state to be printed on error log
|
||||
%% Returns: State to print
|
||||
%%----------------------------------------------------------------------
|
||||
print_state(State) ->
|
||||
State.
|
||||
print_state(State) -> State.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
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_text(StateData, xml:element_to_binary(El)).
|
||||
|
||||
new_id() ->
|
||||
randoms:get_string().
|
||||
new_id() -> randoms:get_string().
|
||||
|
||||
fsm_limit_opts(Opts) ->
|
||||
case lists:keysearch(max_fsm_queue, 1, Opts) of
|
||||
{value, {_, N}} when is_integer(N) ->
|
||||
[{max_queue, N}];
|
||||
_ ->
|
||||
case ejabberd_config:get_local_option(max_fsm_queue) of
|
||||
N when is_integer(N) ->
|
||||
[{max_queue, N}];
|
||||
_ ->
|
||||
[]
|
||||
case ejabberd_config:get_local_option(
|
||||
max_fsm_queue,
|
||||
fun(I) when is_integer(I), I > 0 -> I end) of
|
||||
undefined -> [];
|
||||
N -> [{max_queue, N}]
|
||||
end
|
||||
end.
|
||||
|
@ -25,6 +25,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_sm).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_server).
|
||||
@ -58,12 +59,15 @@
|
||||
]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
|
||||
-include("ejabberd_commands.hrl").
|
||||
|
||||
-include("mod_privacy.hrl").
|
||||
|
||||
-record(session, {sid, usr, us, priority, info}).
|
||||
@ -80,26 +84,40 @@
|
||||
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
|
||||
%% 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() ->
|
||||
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) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nwhen processing: ~p",
|
||||
[Reason, {From, To, Packet}]);
|
||||
_ ->
|
||||
ok
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
-spec open_session(sid(), binary(), binary(), binary(), info()) -> ok.
|
||||
|
||||
open_session(SID, User, Server, Resource, Info) ->
|
||||
set_session(SID, User, Server, Resource, undefined, Info),
|
||||
mnesia:dirty_update_counter(session_counter,
|
||||
jlib:nameprep(Server), 1),
|
||||
check_for_sessions_to_replace(User, Server, Resource),
|
||||
JID = jlib:make_jid(User, Server, Resource),
|
||||
ejabberd_hooks:run(sm_register_connection_hook, JID#jid.lserver,
|
||||
[SID, JID, Info]).
|
||||
ejabberd_hooks:run(sm_register_connection_hook,
|
||||
JID#jid.lserver, [SID, JID, Info]).
|
||||
|
||||
-spec close_session(sid(), binary(), binary(), binary()) -> ok.
|
||||
|
||||
close_session(SID, User, Server, Resource) ->
|
||||
Info = case mnesia:dirty_read({session, SID}) of
|
||||
@ -113,27 +131,29 @@ close_session(SID, User, Server, Resource) ->
|
||||
end,
|
||||
mnesia:sync_dirty(F),
|
||||
JID = jlib:make_jid(User, Server, Resource),
|
||||
ejabberd_hooks:run(sm_remove_connection_hook, JID#jid.lserver,
|
||||
[SID, JID, Info]).
|
||||
ejabberd_hooks:run(sm_remove_connection_hook,
|
||||
JID#jid.lserver, [SID, JID, Info]).
|
||||
|
||||
check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
|
||||
case ejabberd_auth:is_user_exists(User, Server) of
|
||||
true ->
|
||||
Acc;
|
||||
false ->
|
||||
{stop, false}
|
||||
true -> Acc;
|
||||
false -> {stop, false}
|
||||
end.
|
||||
|
||||
-spec bounce_offline_message(jid(), jid(), xmlel()) -> stop.
|
||||
|
||||
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),
|
||||
stop.
|
||||
|
||||
-spec disconnect_removed_user(binary(), binary()) -> ok.
|
||||
|
||||
disconnect_removed_user(User, Server) ->
|
||||
ejabberd_sm:route(jlib:make_jid("", "", ""),
|
||||
jlib:make_jid(User, Server, ""),
|
||||
{xmlelement, "broadcast", [],
|
||||
[{exit, "User removed"}]}).
|
||||
ejabberd_sm:route(jlib:make_jid(<<"">>, <<"">>, <<"">>),
|
||||
jlib:make_jid(User, Server, <<"">>),
|
||||
{broadcast, {exit, <<"User removed">>}}).
|
||||
|
||||
get_user_resources(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
@ -146,6 +166,8 @@ get_user_resources(User, Server) ->
|
||||
[element(3, S#session.usr) || S <- clean_session_list(Ss)]
|
||||
end.
|
||||
|
||||
-spec get_user_ip(binary(), binary(), binary()) -> ip().
|
||||
|
||||
get_user_ip(User, Server, Resource) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
@ -159,6 +181,8 @@ get_user_ip(User, Server, Resource) ->
|
||||
proplists:get_value(ip, Session#session.info)
|
||||
end.
|
||||
|
||||
-spec get_user_info(binary(), binary(), binary()) -> info() | offline.
|
||||
|
||||
get_user_info(User, Server, Resource) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
@ -175,21 +199,40 @@ get_user_info(User, Server, Resource) ->
|
||||
[{node, Node}, {conn, Conn}, {ip, IP}]
|
||||
end.
|
||||
|
||||
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),
|
||||
-spec set_presence(sid(), binary(), binary(), binary(),
|
||||
prio(), xmlel(), info()) -> ok.
|
||||
|
||||
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]).
|
||||
|
||||
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),
|
||||
-spec unset_presence(sid(), binary(), binary(),
|
||||
binary(), binary(), info()) -> ok.
|
||||
|
||||
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]).
|
||||
|
||||
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),
|
||||
ejabberd_hooks:run(unset_presence_hook, jlib:nameprep(Server),
|
||||
ejabberd_hooks:run(unset_presence_hook,
|
||||
jlib:nameprep(Server),
|
||||
[User, Server, Resource, Status]).
|
||||
|
||||
-spec get_session_pid(binary(), binary(), binary()) -> none | pid().
|
||||
|
||||
get_session_pid(User, Server, Resource) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
@ -200,6 +243,8 @@ get_session_pid(User, Server, Resource) ->
|
||||
_ -> none
|
||||
end.
|
||||
|
||||
-spec dirty_get_sessions_list() -> [ljid()].
|
||||
|
||||
dirty_get_sessions_list() ->
|
||||
mnesia:dirty_select(
|
||||
session,
|
||||
@ -216,11 +261,11 @@ dirty_get_my_sessions_list() ->
|
||||
|
||||
get_vh_session_list(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
mnesia:dirty_select(
|
||||
session,
|
||||
mnesia:dirty_select(session,
|
||||
[{#session{usr = '$1', _ = '_'},
|
||||
[{'==', {element, 2, '$1'}, LServer}],
|
||||
['$1']}]).
|
||||
[{'==', {element, 2, '$1'}, LServer}], ['$1']}]).
|
||||
|
||||
-spec get_vh_session_list(binary()) -> [ljid()].
|
||||
|
||||
get_vh_session_number(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
@ -236,10 +281,16 @@ get_vh_session_number(Server) ->
|
||||
end.
|
||||
|
||||
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) ->
|
||||
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) ->
|
||||
ejabberd_sm ! {unregister_iq_handler, Host, XMLNS}.
|
||||
@ -293,8 +344,7 @@ init([]) ->
|
||||
%% Description: Handling call messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
Reply = ok, {reply, Reply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
||||
@ -302,8 +352,7 @@ handle_call(_Request, _From, State) ->
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
handle_cast(_Msg, 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) ->
|
||||
ets:insert(sm_iqtable, {{XMLNS, Host}, Module, Function}),
|
||||
{noreply, State};
|
||||
handle_info({register_iq_handler, Host, XMLNS, Module, Function, Opts}, State) ->
|
||||
ets:insert(sm_iqtable, {{XMLNS, Host}, Module, Function, Opts}),
|
||||
handle_info({register_iq_handler, Host, XMLNS, Module,
|
||||
Function, Opts},
|
||||
State) ->
|
||||
ets:insert(sm_iqtable,
|
||||
{{XMLNS, Host}, Module, Function, Opts}),
|
||||
{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
|
||||
[{_, Module, Function, Opts}] ->
|
||||
gen_iq_handler:stop_iq_handler(Module, Function, Opts);
|
||||
_ ->
|
||||
ok
|
||||
_ -> ok
|
||||
end,
|
||||
ets:delete(sm_iqtable, {XMLNS, Host}),
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
handle_info(_Info, State) -> {noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: terminate(Reason, State) -> void()
|
||||
@ -356,8 +407,7 @@ terminate(_Reason, _State) ->
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
%% Description: Convert process state when code is changed
|
||||
%%--------------------------------------------------------------------
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
@ -369,12 +419,9 @@ set_session(SID, User, Server, Resource, Priority, Info) ->
|
||||
LResource = jlib:resourceprep(Resource),
|
||||
US = {LUser, LServer},
|
||||
USR = {LUser, LServer, LResource},
|
||||
F = fun() ->
|
||||
mnesia:write(#session{sid = SID,
|
||||
usr = USR,
|
||||
us = US,
|
||||
priority = Priority,
|
||||
info = Info})
|
||||
F = fun () ->
|
||||
mnesia:write(#session{sid = SID, usr = USR, us = US,
|
||||
priority = Priority, info = Info})
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
@ -408,101 +455,100 @@ recount_session_table(Node) ->
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
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]),
|
||||
#jid{user = User, server = Server,
|
||||
luser = LUser, lserver = LServer, lresource = LResource} = To,
|
||||
{xmlelement, Name, Attrs, _Els} = Packet,
|
||||
#xmlel{name = Name, attrs = Attrs} = Packet,
|
||||
case LResource of
|
||||
"" ->
|
||||
<<"">> ->
|
||||
case Name of
|
||||
"presence" ->
|
||||
{Pass, _Subsc} =
|
||||
case xml:get_attr_s("type", Attrs) of
|
||||
"subscribe" ->
|
||||
Reason = xml:get_path_s(
|
||||
Packet,
|
||||
[{elem, "status"}, cdata]),
|
||||
{is_privacy_allow(From, To, Packet) andalso
|
||||
ejabberd_hooks:run_fold(
|
||||
roster_in_subscription,
|
||||
<<"presence">> ->
|
||||
{Pass, _Subsc} = case xml:get_attr_s(<<"type">>, Attrs)
|
||||
of
|
||||
<<"subscribe">> ->
|
||||
Reason = xml:get_path_s(Packet,
|
||||
[{elem,
|
||||
<<"status">>},
|
||||
cdata]),
|
||||
{is_privacy_allow(From, To, Packet)
|
||||
andalso
|
||||
ejabberd_hooks:run_fold(roster_in_subscription,
|
||||
LServer,
|
||||
false,
|
||||
[User, Server, From, subscribe, Reason]),
|
||||
[User, Server,
|
||||
From,
|
||||
subscribe,
|
||||
Reason]),
|
||||
true};
|
||||
"subscribed" ->
|
||||
{is_privacy_allow(From, To, Packet) andalso
|
||||
ejabberd_hooks:run_fold(
|
||||
roster_in_subscription,
|
||||
<<"subscribed">> ->
|
||||
{is_privacy_allow(From, To, Packet)
|
||||
andalso
|
||||
ejabberd_hooks:run_fold(roster_in_subscription,
|
||||
LServer,
|
||||
false,
|
||||
[User, Server, From, subscribed, ""]),
|
||||
[User, Server,
|
||||
From,
|
||||
subscribed,
|
||||
<<"">>]),
|
||||
true};
|
||||
"unsubscribe" ->
|
||||
{is_privacy_allow(From, To, Packet) andalso
|
||||
ejabberd_hooks:run_fold(
|
||||
roster_in_subscription,
|
||||
<<"unsubscribe">> ->
|
||||
{is_privacy_allow(From, To, Packet)
|
||||
andalso
|
||||
ejabberd_hooks:run_fold(roster_in_subscription,
|
||||
LServer,
|
||||
false,
|
||||
[User, Server, From, unsubscribe, ""]),
|
||||
[User, Server,
|
||||
From,
|
||||
unsubscribe,
|
||||
<<"">>]),
|
||||
true};
|
||||
"unsubscribed" ->
|
||||
{is_privacy_allow(From, To, Packet) andalso
|
||||
ejabberd_hooks:run_fold(
|
||||
roster_in_subscription,
|
||||
<<"unsubscribed">> ->
|
||||
{is_privacy_allow(From, To, Packet)
|
||||
andalso
|
||||
ejabberd_hooks:run_fold(roster_in_subscription,
|
||||
LServer,
|
||||
false,
|
||||
[User, Server, From, unsubscribed, ""]),
|
||||
[User, Server,
|
||||
From,
|
||||
unsubscribed,
|
||||
<<"">>]),
|
||||
true};
|
||||
_ ->
|
||||
{true, false}
|
||||
_ -> {true, false}
|
||||
end,
|
||||
if Pass ->
|
||||
PResources = get_user_present_resources(
|
||||
LUser, LServer),
|
||||
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) ->
|
||||
PResources = get_user_present_resources(LUser, LServer),
|
||||
lists:foreach(fun ({_, R}) ->
|
||||
do_route(From,
|
||||
jlib:jid_replace_resource(To, R),
|
||||
jlib:jid_replace_resource(To,
|
||||
R),
|
||||
Packet)
|
||||
end, get_user_resources(User, Server));
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
PResources);
|
||||
true -> ok
|
||||
end;
|
||||
<<"message">> -> route_message(From, To, Packet);
|
||||
<<"iq">> -> process_iq(From, To, Packet);
|
||||
_ -> ok
|
||||
end;
|
||||
_ ->
|
||||
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
|
||||
"message" ->
|
||||
route_message(From, To, Packet);
|
||||
"iq" ->
|
||||
case xml:get_attr_s("type", Attrs) of
|
||||
"error" -> ok;
|
||||
"result" -> ok;
|
||||
<<"message">> -> route_message(From, To, Packet);
|
||||
<<"iq">> ->
|
||||
case xml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"error">> -> ok;
|
||||
<<"result">> -> ok;
|
||||
_ ->
|
||||
Err =
|
||||
jlib:make_error_reply(
|
||||
Packet, ?ERR_SERVICE_UNAVAILABLE),
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
_ ->
|
||||
?DEBUG("packet droped~n", [])
|
||||
_ -> ?DEBUG("packet droped~n", [])
|
||||
end;
|
||||
Ss ->
|
||||
Session = lists:max(Ss),
|
||||
@ -519,7 +565,8 @@ do_route(From, To, Packet) ->
|
||||
is_privacy_allow(From, To, Packet) ->
|
||||
User = To#jid.user,
|
||||
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]),
|
||||
is_privacy_allow(From, To, Packet, PrivacyList).
|
||||
|
||||
@ -528,13 +575,10 @@ is_privacy_allow(From, To, Packet) ->
|
||||
is_privacy_allow(From, To, Packet, PrivacyList) ->
|
||||
User = To#jid.user,
|
||||
Server = To#jid.server,
|
||||
allow == ejabberd_hooks:run_fold(
|
||||
privacy_check_packet, Server,
|
||||
allow ==
|
||||
ejabberd_hooks:run_fold(privacy_check_packet, Server,
|
||||
allow,
|
||||
[User,
|
||||
Server,
|
||||
PrivacyList,
|
||||
{From, To, Packet},
|
||||
[User, Server, PrivacyList, {From, To, Packet},
|
||||
in]).
|
||||
|
||||
route_message(From, To, Packet) ->
|
||||
@ -542,14 +586,14 @@ route_message(From, To, Packet) ->
|
||||
LServer = To#jid.lserver,
|
||||
PrioRes = get_user_present_resources(LUser, LServer),
|
||||
case catch lists:max(PrioRes) of
|
||||
{Priority, _R} when is_integer(Priority), Priority >= 0 ->
|
||||
lists:foreach(
|
||||
%% Route messages to all priority that equals the max, if
|
||||
%% positive
|
||||
fun({P, R}) when P == Priority ->
|
||||
{Priority, _R}
|
||||
when is_integer(Priority), Priority >= 0 ->
|
||||
lists:foreach(fun ({P, R}) when P == Priority ->
|
||||
LResource = jlib:resourceprep(R),
|
||||
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
|
||||
Ss ->
|
||||
@ -559,71 +603,61 @@ route_message(From, To, Packet) ->
|
||||
Pid ! {route, From, To, Packet}
|
||||
end;
|
||||
%% Ignore other priority:
|
||||
({_Prio, _Res}) ->
|
||||
ok
|
||||
({_Prio, _Res}) -> ok
|
||||
end,
|
||||
PrioRes);
|
||||
_ ->
|
||||
case xml:get_tag_attr_s("type", Packet) of
|
||||
"error" ->
|
||||
ok;
|
||||
"groupchat" ->
|
||||
case xml:get_tag_attr_s(<<"type">>, Packet) of
|
||||
<<"error">> -> ok;
|
||||
<<"groupchat">> ->
|
||||
bounce_offline_message(From, To, Packet);
|
||||
"headline" ->
|
||||
<<"headline">> ->
|
||||
bounce_offline_message(From, To, Packet);
|
||||
_ ->
|
||||
case ejabberd_auth:is_user_exists(LUser, LServer) of
|
||||
true ->
|
||||
case is_privacy_allow(From, To, Packet) of
|
||||
true ->
|
||||
ejabberd_hooks:run(offline_message_hook,
|
||||
LServer,
|
||||
ejabberd_hooks:run(offline_message_hook, LServer,
|
||||
[From, To, Packet]);
|
||||
false ->
|
||||
ok
|
||||
false -> ok
|
||||
end;
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERR_SERVICE_UNAVAILABLE),
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
clean_session_list(Ss) ->
|
||||
clean_session_list(lists:keysort(#session.usr, Ss), []).
|
||||
|
||||
clean_session_list([], Res) ->
|
||||
Res;
|
||||
clean_session_list([S], Res) ->
|
||||
[S | Res];
|
||||
clean_session_list([], Res) -> Res;
|
||||
clean_session_list([S], Res) -> [S | Res];
|
||||
clean_session_list([S1, S2 | Rest], Res) ->
|
||||
if
|
||||
S1#session.usr == S2#session.usr ->
|
||||
if
|
||||
S1#session.sid > S2#session.sid ->
|
||||
if S1#session.usr == S2#session.usr ->
|
||||
if S1#session.sid > S2#session.sid ->
|
||||
clean_session_list([S1 | Rest], Res);
|
||||
true ->
|
||||
clean_session_list([S2 | Rest], Res)
|
||||
true -> clean_session_list([S2 | Rest], Res)
|
||||
end;
|
||||
true ->
|
||||
clean_session_list([S2 | Rest], [S1 | Res])
|
||||
true -> clean_session_list([S2 | Rest], [S1 | Res])
|
||||
end.
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
get_user_present_resources(LUser, LServer) ->
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_index_read(session, US, #session.us) of
|
||||
{'EXIT', _Reason} ->
|
||||
[];
|
||||
case catch mnesia:dirty_index_read(session, US,
|
||||
#session.us)
|
||||
of
|
||||
{'EXIT', _Reason} -> [];
|
||||
Ss ->
|
||||
[{S#session.priority, element(3, S#session.usr)} ||
|
||||
S <- clean_session_list(Ss), is_integer(S#session.priority)]
|
||||
[{S#session.priority, element(3, S#session.usr)}
|
||||
|| S <- clean_session_list(Ss),
|
||||
is_integer(S#session.priority)]
|
||||
end.
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
@ -633,59 +667,51 @@ check_for_sessions_to_replace(User, Server, Resource) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
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_max_sessions(LUser, LServer).
|
||||
|
||||
check_existing_resources(LUser, LServer, LResource) ->
|
||||
SIDs = get_resource_sessions(LUser, LServer, LResource),
|
||||
if
|
||||
SIDs == [] -> ok;
|
||||
if SIDs == [] -> ok;
|
||||
true ->
|
||||
%% A connection exist with the same resource. We replace it:
|
||||
MaxSID = lists:max(SIDs),
|
||||
lists:foreach(
|
||||
fun({_, Pid} = S) when S /= MaxSID ->
|
||||
lists:foreach(fun ({_, Pid} = S) when S /= MaxSID ->
|
||||
Pid ! replaced;
|
||||
(_) -> ok
|
||||
end, SIDs)
|
||||
end,
|
||||
SIDs)
|
||||
end.
|
||||
|
||||
-spec is_existing_resource(binary(), binary(), binary()) -> boolean().
|
||||
|
||||
is_existing_resource(LUser, LServer, LResource) ->
|
||||
[] /= get_resource_sessions(LUser, LServer, LResource).
|
||||
|
||||
get_resource_sessions(User, Server, Resource) ->
|
||||
USR = {jlib:nodeprep(User), jlib:nameprep(Server), jlib:resourceprep(Resource)},
|
||||
mnesia:dirty_select(
|
||||
session,
|
||||
[{#session{sid = '$1', usr = USR, _ = '_'}, [], ['$1']}]).
|
||||
USR = {jlib:nodeprep(User), jlib:nameprep(Server),
|
||||
jlib:resourceprep(Resource)},
|
||||
mnesia:dirty_select(session,
|
||||
[{#session{sid = '$1', usr = USR, _ = '_'}, [],
|
||||
['$1']}]).
|
||||
|
||||
check_max_sessions(LUser, LServer) ->
|
||||
%% If the max number of sessions for a given is reached, we replace the
|
||||
%% first one
|
||||
SIDs = mnesia:dirty_select(
|
||||
session,
|
||||
[{#session{sid = '$1', us = {LUser, LServer}, _ = '_'}, [],
|
||||
['$1']}]),
|
||||
SIDs = mnesia:dirty_select(session,
|
||||
[{#session{sid = '$1', us = {LUser, LServer},
|
||||
_ = '_'},
|
||||
[], ['$1']}]),
|
||||
MaxSessions = get_max_user_sessions(LUser, LServer),
|
||||
if
|
||||
length(SIDs) =< MaxSessions ->
|
||||
ok;
|
||||
true ->
|
||||
{_, Pid} = lists:min(SIDs),
|
||||
Pid ! replaced
|
||||
if length(SIDs) =< MaxSessions -> ok;
|
||||
true -> {_, Pid} = lists:min(SIDs), Pid ! replaced
|
||||
end.
|
||||
|
||||
|
||||
%% Get the user_max_session setting
|
||||
%% This option defines the max number of time a given users are allowed to
|
||||
%% log in
|
||||
%% Defaults to infinity
|
||||
get_max_user_sessions(LUser, Host) ->
|
||||
case acl:match_rule(
|
||||
Host, max_user_sessions, jlib:make_jid(LUser, Host, "")) of
|
||||
case acl:match_rule(Host, max_user_sessions,
|
||||
jlib:make_jid(LUser, Host, <<"">>))
|
||||
of
|
||||
Max when is_integer(Max) -> Max;
|
||||
infinity -> infinity;
|
||||
_ -> ?MAX_USER_SESSIONS
|
||||
@ -701,69 +727,67 @@ process_iq(From, To, Packet) ->
|
||||
case ets:lookup(sm_iqtable, {XMLNS, Host}) of
|
||||
[{_, Module, Function}] ->
|
||||
ResIQ = Module:Function(From, To, IQ),
|
||||
if
|
||||
ResIQ /= ignore ->
|
||||
ejabberd_router:route(To, From,
|
||||
jlib:iq_to_xml(ResIQ));
|
||||
true ->
|
||||
ok
|
||||
if ResIQ /= ignore ->
|
||||
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
|
||||
true -> ok
|
||||
end;
|
||||
[{_, Module, Function, Opts}] ->
|
||||
gen_iq_handler:handle(Host, Module, Function, Opts,
|
||||
From, To, IQ);
|
||||
[] ->
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERR_SERVICE_UNAVAILABLE),
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
reply ->
|
||||
ok;
|
||||
reply -> ok;
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
|
||||
ejabberd_router:route(To, From, Err),
|
||||
ok
|
||||
end.
|
||||
|
||||
force_update_presence({LUser, _LServer} = US) ->
|
||||
case catch mnesia:dirty_index_read(session, US, #session.us) of
|
||||
{'EXIT', _Reason} ->
|
||||
ok;
|
||||
Ss ->
|
||||
lists:foreach(fun(#session{sid = {_, Pid}}) ->
|
||||
Pid ! {force_update_presence, LUser}
|
||||
end, Ss)
|
||||
end.
|
||||
-spec force_update_presence({binary(), binary()}) -> any().
|
||||
|
||||
force_update_presence({LUser, _LServer} = US) ->
|
||||
case catch mnesia:dirty_index_read(session, US,
|
||||
#session.us)
|
||||
of
|
||||
{'EXIT', _Reason} -> ok;
|
||||
Ss ->
|
||||
lists:foreach(fun (#session{sid = {_, Pid}}) ->
|
||||
Pid ! {force_update_presence, LUser}
|
||||
end,
|
||||
Ss)
|
||||
end.
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%% ejabberd commands
|
||||
|
||||
commands() ->
|
||||
[
|
||||
#ejabberd_commands{name = connected_users,
|
||||
[#ejabberd_commands{name = connected_users,
|
||||
tags = [session],
|
||||
desc = "List all established sessions",
|
||||
module = ?MODULE, function = connected_users,
|
||||
args = [],
|
||||
module = ?MODULE, function = connected_users, args = [],
|
||||
result = {connected_users, {list, {sessions, string}}}},
|
||||
#ejabberd_commands{name = connected_users_number,
|
||||
tags = [session, stats],
|
||||
desc = "Get the number of established sessions",
|
||||
module = ?MODULE, function = connected_users_number,
|
||||
args = [],
|
||||
result = {num_sessions, integer}},
|
||||
args = [], result = {num_sessions, integer}},
|
||||
#ejabberd_commands{name = user_resources,
|
||||
tags = [session],
|
||||
desc = "List user's connected resources",
|
||||
module = ?MODULE, function = user_resources,
|
||||
args = [{user, string}, {host, string}],
|
||||
result = {resources, {list, {resource, string}}}}
|
||||
].
|
||||
result = {resources, {list, {resource, string}}}}].
|
||||
|
||||
-spec connected_users() -> [binary()].
|
||||
|
||||
connected_users() ->
|
||||
USRs = dirty_get_sessions_list(),
|
||||
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() ->
|
||||
length(dirty_get_sessions_list()).
|
||||
@ -778,24 +802,18 @@ user_resources(User, Server) ->
|
||||
|
||||
update_tables() ->
|
||||
case catch mnesia:table_info(session, attributes) of
|
||||
[ur, user, node] ->
|
||||
mnesia:delete_table(session);
|
||||
[ur, user, pid] ->
|
||||
mnesia:delete_table(session);
|
||||
[usr, us, pid] ->
|
||||
mnesia:delete_table(session);
|
||||
[ur, user, node] -> mnesia:delete_table(session);
|
||||
[ur, user, pid] -> mnesia:delete_table(session);
|
||||
[usr, us, pid] -> mnesia:delete_table(session);
|
||||
[sid, usr, us, priority] ->
|
||||
mnesia:delete_table(session);
|
||||
[sid, usr, us, priority, info] ->
|
||||
ok;
|
||||
{'EXIT', _} ->
|
||||
ok
|
||||
[sid, usr, us, priority, info] -> ok;
|
||||
{'EXIT', _} -> ok
|
||||
end,
|
||||
case lists:member(presence, mnesia:system_info(tables)) of
|
||||
true ->
|
||||
mnesia:delete_table(presence);
|
||||
false ->
|
||||
ok
|
||||
case lists:member(presence, mnesia:system_info(tables))
|
||||
of
|
||||
true -> mnesia:delete_table(presence);
|
||||
false -> ok
|
||||
end,
|
||||
case lists:member(local_session, mnesia:system_info(tables)) of
|
||||
true ->
|
||||
|
@ -25,6 +25,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_socket).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
%% API
|
||||
@ -47,8 +48,29 @@
|
||||
sockname/1, peername/1]).
|
||||
|
||||
-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
|
||||
@ -60,54 +82,51 @@
|
||||
start(Module, SockMod, Socket, Opts) ->
|
||||
case Module:socket_type() of
|
||||
xml_stream ->
|
||||
MaxStanzaSize =
|
||||
case lists:keysearch(max_stanza_size, 1, Opts) of
|
||||
MaxStanzaSize = case lists:keysearch(max_stanza_size, 1,
|
||||
Opts)
|
||||
of
|
||||
{value, {_, Size}} -> Size;
|
||||
_ -> infinity
|
||||
end,
|
||||
{ReceiverMod, Receiver, RecRef} =
|
||||
case catch SockMod:custom_receiver(Socket) of
|
||||
{ReceiverMod, Receiver, RecRef} = case catch
|
||||
SockMod:custom_receiver(Socket)
|
||||
of
|
||||
{receiver, RecMod, RecPid} ->
|
||||
{RecMod, RecPid, RecMod};
|
||||
_ ->
|
||||
RecPid = ejabberd_receiver:start(
|
||||
Socket, SockMod, none, MaxStanzaSize),
|
||||
{ejabberd_receiver, RecPid, RecPid}
|
||||
RecPid =
|
||||
ejabberd_receiver:start(Socket,
|
||||
SockMod,
|
||||
none,
|
||||
MaxStanzaSize),
|
||||
{ejabberd_receiver, RecPid,
|
||||
RecPid}
|
||||
end,
|
||||
SocketData = #socket_state{sockmod = SockMod,
|
||||
socket = Socket,
|
||||
receiver = RecRef},
|
||||
socket = Socket, receiver = RecRef},
|
||||
case Module:start({?MODULE, SocketData}, Opts) of
|
||||
{ok, Pid} ->
|
||||
case SockMod:controlling_process(Socket, Receiver) of
|
||||
ok ->
|
||||
ok;
|
||||
{error, _Reason} ->
|
||||
SockMod:close(Socket)
|
||||
ok -> ok;
|
||||
{error, _Reason} -> SockMod:close(Socket)
|
||||
end,
|
||||
ReceiverMod:become_controller(Receiver, Pid);
|
||||
{error, _Reason} ->
|
||||
SockMod:close(Socket),
|
||||
case ReceiverMod of
|
||||
ejabberd_receiver ->
|
||||
ReceiverMod:close(Receiver);
|
||||
_ ->
|
||||
ok
|
||||
ejabberd_receiver -> ReceiverMod:close(Receiver);
|
||||
_ -> ok
|
||||
end
|
||||
end;
|
||||
independent ->
|
||||
ok;
|
||||
independent -> ok;
|
||||
raw ->
|
||||
case Module:start({SockMod, Socket}, Opts) of
|
||||
{ok, Pid} ->
|
||||
case SockMod:controlling_process(Socket, Pid) of
|
||||
ok ->
|
||||
ok;
|
||||
{error, _Reason} ->
|
||||
SockMod:close(Socket)
|
||||
ok -> ok;
|
||||
{error, _Reason} -> SockMod:close(Socket)
|
||||
end;
|
||||
{error, _Reason} ->
|
||||
SockMod:close(Socket)
|
||||
{error, _Reason} -> SockMod:close(Socket)
|
||||
end
|
||||
end.
|
||||
|
||||
@ -117,21 +136,18 @@ connect(Addr, Port, Opts) ->
|
||||
connect(Addr, Port, Opts, Timeout) ->
|
||||
case gen_tcp:connect(Addr, Port, Opts, Timeout) of
|
||||
{ok, Socket} ->
|
||||
Receiver = ejabberd_receiver:start(Socket, gen_tcp, none),
|
||||
Receiver = ejabberd_receiver:start(Socket, gen_tcp,
|
||||
none),
|
||||
SocketData = #socket_state{sockmod = gen_tcp,
|
||||
socket = Socket,
|
||||
receiver = Receiver},
|
||||
socket = Socket, receiver = Receiver},
|
||||
Pid = self(),
|
||||
case gen_tcp:controlling_process(Socket, Receiver) of
|
||||
ok ->
|
||||
ejabberd_receiver:become_controller(Receiver, Pid),
|
||||
{ok, SocketData};
|
||||
{error, _Reason} = Error ->
|
||||
gen_tcp:close(Socket),
|
||||
Error
|
||||
{error, _Reason} = Error -> gen_tcp:close(Socket), Error
|
||||
end;
|
||||
{error, _Reason} = Error ->
|
||||
Error
|
||||
{error, _Reason} = Error -> Error
|
||||
end.
|
||||
|
||||
starttls(SocketData, TLSOpts) ->
|
||||
@ -160,11 +176,12 @@ compress(SocketData, Data) ->
|
||||
send(SocketData, Data),
|
||||
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);
|
||||
reset_stream(SocketData) when is_atom(SocketData#socket_state.receiver) ->
|
||||
(SocketData#socket_state.receiver):reset_stream(
|
||||
SocketData#socket_state.socket).
|
||||
reset_stream(SocketData)
|
||||
when is_atom(SocketData#socket_state.receiver) ->
|
||||
(SocketData#socket_state.receiver):reset_stream(SocketData#socket_state.socket).
|
||||
|
||||
%% sockmod=gen_tcp|tls|ejabberd_zlib
|
||||
send(SocketData, Data) ->
|
||||
@ -182,23 +199,29 @@ send(SocketData, Data) ->
|
||||
%% Can only be called when in c2s StateData#state.xml_socket is true
|
||||
%% This function is used for HTTP bind
|
||||
%% sockmod=ejabberd_http_poll|ejabberd_http_bind or any custom module
|
||||
-spec send_xml(socket_state(), xmlel()) -> any().
|
||||
|
||||
send_xml(SocketData, Data) ->
|
||||
catch (SocketData#socket_state.sockmod):send_xml(
|
||||
SocketData#socket_state.socket, Data).
|
||||
catch
|
||||
(SocketData#socket_state.sockmod):send_xml(SocketData#socket_state.socket,
|
||||
Data).
|
||||
|
||||
change_shaper(SocketData, Shaper)
|
||||
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)
|
||||
when is_atom(SocketData#socket_state.receiver) ->
|
||||
(SocketData#socket_state.receiver):change_shaper(
|
||||
SocketData#socket_state.socket, Shaper).
|
||||
(SocketData#socket_state.receiver):change_shaper(SocketData#socket_state.socket,
|
||||
Shaper).
|
||||
|
||||
monitor(SocketData) when is_pid(SocketData#socket_state.receiver) ->
|
||||
erlang:monitor(process, SocketData#socket_state.receiver);
|
||||
monitor(SocketData) when is_atom(SocketData#socket_state.receiver) ->
|
||||
(SocketData#socket_state.receiver):monitor(
|
||||
SocketData#socket_state.socket).
|
||||
monitor(SocketData)
|
||||
when is_pid(SocketData#socket_state.receiver) ->
|
||||
erlang:monitor(process,
|
||||
SocketData#socket_state.receiver);
|
||||
monitor(SocketData)
|
||||
when is_atom(SocketData#socket_state.receiver) ->
|
||||
(SocketData#socket_state.receiver):monitor(SocketData#socket_state.socket).
|
||||
|
||||
get_sockmod(SocketData) ->
|
||||
SocketData#socket_state.sockmod.
|
||||
@ -212,22 +235,21 @@ get_verify_result(SocketData) ->
|
||||
close(SocketData) ->
|
||||
ejabberd_receiver:close(SocketData#socket_state.receiver).
|
||||
|
||||
sockname(#socket_state{sockmod = SockMod, socket = Socket}) ->
|
||||
sockname(#socket_state{sockmod = SockMod,
|
||||
socket = Socket}) ->
|
||||
case SockMod of
|
||||
gen_tcp ->
|
||||
inet:sockname(Socket);
|
||||
_ ->
|
||||
SockMod:sockname(Socket)
|
||||
gen_tcp -> inet:sockname(Socket);
|
||||
_ -> SockMod:sockname(Socket)
|
||||
end.
|
||||
|
||||
peername(#socket_state{sockmod = SockMod, socket = Socket}) ->
|
||||
peername(#socket_state{sockmod = SockMod,
|
||||
socket = Socket}) ->
|
||||
case SockMod of
|
||||
gen_tcp ->
|
||||
inet:peername(Socket);
|
||||
_ ->
|
||||
SockMod:peername(Socket)
|
||||
gen_tcp -> inet:peername(Socket);
|
||||
_ -> SockMod:peername(Socket)
|
||||
end.
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
%====================================================================
|
||||
|
@ -63,6 +63,13 @@ init([]) ->
|
||||
brutal_kill,
|
||||
worker,
|
||||
[ejabberd_router]},
|
||||
Router_multicast =
|
||||
{ejabberd_router_multicast,
|
||||
{ejabberd_router_multicast, start_link, []},
|
||||
permanent,
|
||||
brutal_kill,
|
||||
worker,
|
||||
[ejabberd_router_multicast]},
|
||||
SM =
|
||||
{ejabberd_sm,
|
||||
{ejabberd_sm, start_link, []},
|
||||
@ -189,6 +196,7 @@ init([]) ->
|
||||
NodeGroups,
|
||||
SystemMonitor,
|
||||
Router,
|
||||
Router_multicast,
|
||||
SM,
|
||||
S2S,
|
||||
Local,
|
||||
|
@ -25,20 +25,21 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_system_monitor).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/0,
|
||||
process_command/3,
|
||||
-export([start_link/0, process_command/3,
|
||||
process_remote_command/1]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
|
||||
-record(state, {}).
|
||||
@ -51,37 +52,36 @@
|
||||
%% Description: Starts the server
|
||||
%%--------------------------------------------------------------------
|
||||
start_link() ->
|
||||
LH = case ejabberd_config:get_local_option(watchdog_large_heap) of
|
||||
I when is_integer(I) -> I;
|
||||
_ -> 1000000
|
||||
end,
|
||||
LH = ejabberd_config:get_local_option(
|
||||
watchdog_large_heap,
|
||||
fun(I) when is_integer(I), I > 0 -> I end,
|
||||
1000000),
|
||||
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) ->
|
||||
case To of
|
||||
#jid{luser = "", lresource = "watchdog"} ->
|
||||
{xmlelement, Name, _Attrs, _Els} = Packet,
|
||||
#jid{luser = <<"">>, lresource = <<"watchdog">>} ->
|
||||
#xmlel{name = Name} = Packet,
|
||||
case Name of
|
||||
"message" ->
|
||||
LFrom = jlib:jid_tolower(jlib:jid_remove_resource(From)),
|
||||
<<"message">> ->
|
||||
LFrom =
|
||||
jlib:jid_tolower(jlib:jid_remove_resource(From)),
|
||||
case lists:member(LFrom, get_admin_jids()) of
|
||||
true ->
|
||||
Body = xml:get_path_s(
|
||||
Packet, [{elem, "body"}, cdata]),
|
||||
spawn(fun() ->
|
||||
Body = xml:get_path_s(Packet,
|
||||
[{elem, <<"body">>}, cdata]),
|
||||
spawn(fun () ->
|
||||
process_flag(priority, high),
|
||||
process_command1(From, To, Body)
|
||||
end),
|
||||
stop;
|
||||
false ->
|
||||
ok
|
||||
false -> ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
_ -> ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
%%====================================================================
|
||||
@ -99,11 +99,11 @@ init(Opts) ->
|
||||
LH = proplists:get_value(large_heap, Opts),
|
||||
process_flag(priority, high),
|
||||
erlang:system_monitor(self(), [{large_heap, LH}]),
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
lists:foreach(fun (Host) ->
|
||||
ejabberd_hooks:add(local_send_to_resource_hook, Host,
|
||||
?MODULE, process_command, 50)
|
||||
end, ?MYHOSTS),
|
||||
end,
|
||||
?MYHOSTS),
|
||||
{ok, #state{}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@ -117,18 +117,20 @@ init(Opts) ->
|
||||
%%--------------------------------------------------------------------
|
||||
handle_call({get, large_heap}, _From, State) ->
|
||||
{reply, get_large_heap(), State};
|
||||
handle_call({set, large_heap, NewValue}, _From, State) ->
|
||||
MonSettings = erlang:system_monitor(self(), [{large_heap, NewValue}]),
|
||||
handle_call({set, large_heap, NewValue}, _From,
|
||||
State) ->
|
||||
MonSettings = erlang:system_monitor(self(),
|
||||
[{large_heap, NewValue}]),
|
||||
OldLH = get_large_heap(MonSettings),
|
||||
NewLH = get_large_heap(),
|
||||
{reply, {lh_changed, OldLH, NewLH}, State};
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
Reply = ok, {reply, Reply, State}.
|
||||
|
||||
get_large_heap() ->
|
||||
MonSettings = erlang:system_monitor(),
|
||||
get_large_heap(MonSettings).
|
||||
|
||||
get_large_heap(MonSettings) ->
|
||||
{_MonitorPid, Options} = MonSettings,
|
||||
proplists:get_value(large_heap, Options).
|
||||
@ -139,8 +141,7 @@ get_large_heap(MonSettings) ->
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
handle_cast(_Msg, State) -> {noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_info(Info, State) -> {noreply, State} |
|
||||
@ -149,13 +150,12 @@ handle_cast(_Msg, State) ->
|
||||
%% Description: Handling all non call/cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_info({monitor, Pid, large_heap, Info}, State) ->
|
||||
spawn(fun() ->
|
||||
spawn(fun () ->
|
||||
process_flag(priority, high),
|
||||
process_large_heap(Pid, Info)
|
||||
end),
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
handle_info(_Info, State) -> {noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: terminate(Reason, State) -> void()
|
||||
@ -164,63 +164,48 @@ handle_info(_Info, State) ->
|
||||
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
terminate(_Reason, _State) -> ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
%% Description: Convert process state when code is changed
|
||||
%%--------------------------------------------------------------------
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
process_large_heap(Pid, Info) ->
|
||||
Host = ?MYNAME,
|
||||
case ejabberd_config:get_local_option(watchdog_admins) of
|
||||
JIDs when is_list(JIDs),
|
||||
JIDs /= [] ->
|
||||
Host = (?MYNAME),
|
||||
JIDs = get_admin_jids(),
|
||||
DetailedInfo = detailed_info(Pid),
|
||||
Body = io_lib:format(
|
||||
"(~w) The process ~w is consuming too much memory:~n~p~n"
|
||||
"~s",
|
||||
[node(), Pid, Info, DetailedInfo]),
|
||||
From = jlib:make_jid("", Host, "watchdog"),
|
||||
lists:foreach(
|
||||
fun(S) ->
|
||||
case jlib:string_to_jid(S) of
|
||||
error -> ok;
|
||||
JID ->
|
||||
send_message(From, JID, Body)
|
||||
end
|
||||
end, JIDs);
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
Body = iolist_to_binary(
|
||||
io_lib:format("(~w) The process ~w is consuming too "
|
||||
"much memory:~n~p~n~s",
|
||||
[node(), Pid, Info, DetailedInfo])),
|
||||
From = jlib:make_jid(<<"">>, Host, <<"watchdog">>),
|
||||
lists:foreach(fun (JID) ->
|
||||
send_message(From, jlib:make_jid(JID), Body)
|
||||
end, JIDs).
|
||||
|
||||
send_message(From, To, Body) ->
|
||||
ejabberd_router:route(
|
||||
From, To,
|
||||
{xmlelement, "message", [{"type", "chat"}],
|
||||
[{xmlelement, "body", [],
|
||||
[{xmlcdata, lists:flatten(Body)}]}]}).
|
||||
ejabberd_router:route(From, To,
|
||||
#xmlel{name = <<"message">>,
|
||||
attrs = [{<<"type">>, <<"chat">>}],
|
||||
children =
|
||||
[#xmlel{name = <<"body">>, attrs = [],
|
||||
children =
|
||||
[{xmlcdata, Body}]}]}).
|
||||
|
||||
get_admin_jids() ->
|
||||
case ejabberd_config:get_local_option(watchdog_admins) of
|
||||
JIDs when is_list(JIDs) ->
|
||||
lists:flatmap(
|
||||
fun(S) ->
|
||||
case jlib:string_to_jid(S) of
|
||||
error -> [];
|
||||
JID -> [jlib:jid_tolower(JID)]
|
||||
end
|
||||
end, JIDs);
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
ejabberd_config:get_local_option(
|
||||
watchdog_admins,
|
||||
fun(JIDs) ->
|
||||
[jlib:jid_tolower(
|
||||
jlib:string_to_jid(
|
||||
iolist_to_binary(S))) || S <- JIDs]
|
||||
end, []).
|
||||
|
||||
detailed_info(Pid) ->
|
||||
case process_info(Pid, dictionary) of
|
||||
@ -228,140 +213,122 @@ detailed_info(Pid) ->
|
||||
case lists:keysearch('$ancestors', 1, Dict) of
|
||||
{value, {'$ancestors', [Sup | _]}} ->
|
||||
case Sup of
|
||||
ejabberd_c2s_sup ->
|
||||
c2s_info(Pid);
|
||||
ejabberd_s2s_out_sup ->
|
||||
s2s_out_info(Pid);
|
||||
ejabberd_service_sup ->
|
||||
service_info(Pid);
|
||||
_ ->
|
||||
detailed_info1(Pid)
|
||||
ejabberd_c2s_sup -> c2s_info(Pid);
|
||||
ejabberd_s2s_out_sup -> s2s_out_info(Pid);
|
||||
ejabberd_service_sup -> service_info(Pid);
|
||||
_ -> detailed_info1(Pid)
|
||||
end;
|
||||
_ ->
|
||||
detailed_info1(Pid)
|
||||
_ -> detailed_info1(Pid)
|
||||
end;
|
||||
_ ->
|
||||
detailed_info1(Pid)
|
||||
_ -> detailed_info1(Pid)
|
||||
end.
|
||||
|
||||
detailed_info1(Pid) ->
|
||||
io_lib:format(
|
||||
"~p", [[process_info(Pid, current_function),
|
||||
io_lib:format("~p",
|
||||
[[process_info(Pid, current_function),
|
||||
process_info(Pid, initial_call),
|
||||
process_info(Pid, message_queue_len),
|
||||
process_info(Pid, links),
|
||||
process_info(Pid, dictionary),
|
||||
process_info(Pid, links), process_info(Pid, dictionary),
|
||||
process_info(Pid, heap_size),
|
||||
process_info(Pid, stack_size)
|
||||
]]).
|
||||
process_info(Pid, stack_size)]]).
|
||||
|
||||
c2s_info(Pid) ->
|
||||
["Process type: c2s",
|
||||
check_send_queue(Pid),
|
||||
"\n",
|
||||
[<<"Process type: c2s">>, check_send_queue(Pid),
|
||||
<<"\n">>,
|
||||
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) ->
|
||||
FromTo = mnesia:dirty_select(
|
||||
s2s, [{{s2s, '$1', Pid, '_'}, [], ['$1']}]),
|
||||
["Process type: s2s_out",
|
||||
FromTo = mnesia:dirty_select(s2s,
|
||||
[{{s2s, '$1', Pid, '_'}, [], ['$1']}]),
|
||||
[<<"Process type: s2s_out">>,
|
||||
case FromTo of
|
||||
[{From, To}] ->
|
||||
"\n" ++ io_lib:format("S2S connection: from ~s to ~s",
|
||||
[From, To]);
|
||||
_ ->
|
||||
""
|
||||
<<"\n",
|
||||
(io_lib:format("S2S connection: from ~s to ~s",
|
||||
[From, To]))/binary>>;
|
||||
_ -> <<"">>
|
||||
end,
|
||||
check_send_queue(Pid),
|
||||
"\n",
|
||||
check_send_queue(Pid), <<"\n">>,
|
||||
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) ->
|
||||
Routes = mnesia:dirty_select(
|
||||
route, [{{route, '$1', Pid, '_'}, [], ['$1']}]),
|
||||
["Process type: s2s_out",
|
||||
Routes = mnesia:dirty_select(route,
|
||||
[{{route, '$1', Pid, '_'}, [], ['$1']}]),
|
||||
[<<"Process type: s2s_out">>,
|
||||
case Routes of
|
||||
[Route] ->
|
||||
"\nServiced domain: " ++ Route;
|
||||
_ ->
|
||||
""
|
||||
[Route] -> <<"\nServiced domain: ", Route/binary>>;
|
||||
_ -> <<"">>
|
||||
end,
|
||||
check_send_queue(Pid),
|
||||
"\n",
|
||||
check_send_queue(Pid), <<"\n">>,
|
||||
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) ->
|
||||
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}} ->
|
||||
if
|
||||
MLen > 100 ->
|
||||
if MLen > 100 ->
|
||||
case MFA of
|
||||
{prim_inet, send, 2} ->
|
||||
"\nPossible reason: the process is blocked "
|
||||
"trying to send data over its TCP connection.";
|
||||
<<"\nPossible reason: the process is blocked "
|
||||
"trying to send data over its TCP connection.">>;
|
||||
{M, F, A} ->
|
||||
["\nPossible reason: the process can't process "
|
||||
"messages faster than they arrive. ",
|
||||
[<<"\nPossible reason: the process can't "
|
||||
"process messages faster than they arrive. ">>,
|
||||
io_lib:format("Current function is ~w:~w/~w",
|
||||
[M, F, A])
|
||||
]
|
||||
[M, F, A])]
|
||||
end;
|
||||
true ->
|
||||
""
|
||||
true -> <<"">>
|
||||
end;
|
||||
_ ->
|
||||
""
|
||||
_ -> <<"">>
|
||||
end.
|
||||
|
||||
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) ->
|
||||
Node = list_to_atom(SNode),
|
||||
process_command2([<<"kill">>, SNode, SPid], From, To) ->
|
||||
Node = jlib:binary_to_atom(SNode),
|
||||
remote_command(Node, [kill, SPid], From, To);
|
||||
process_command2(["showlh", SNode], From, To) ->
|
||||
Node = list_to_atom(SNode),
|
||||
process_command2([<<"showlh">>, SNode], From, To) ->
|
||||
Node = jlib:binary_to_atom(SNode),
|
||||
remote_command(Node, [showlh], From, To);
|
||||
process_command2(["setlh", SNode, NewValueString], From, To) ->
|
||||
Node = list_to_atom(SNode),
|
||||
NewValue = list_to_integer(NewValueString),
|
||||
process_command2([<<"setlh">>, SNode, NewValueString],
|
||||
From, To) ->
|
||||
Node = jlib:binary_to_atom(SNode),
|
||||
NewValue = jlib:binary_to_integer(NewValueString),
|
||||
remote_command(Node, [setlh, NewValue], From, To);
|
||||
process_command2(["help"], From, To) ->
|
||||
process_command2([<<"help">>], From, To) ->
|
||||
send_message(To, From, help());
|
||||
process_command2(_, From, To) ->
|
||||
send_message(To, From, help()).
|
||||
|
||||
|
||||
help() ->
|
||||
"Commands:\n"
|
||||
" kill <node> <pid>\n"
|
||||
" showlh <node>\n"
|
||||
" setlh <node> <integer>".
|
||||
|
||||
<<"Commands:\n kill <node> <pid>\n showlh "
|
||||
"<node>\n setlh <node> <integer>">>.
|
||||
|
||||
remote_command(Node, Args, From, To) ->
|
||||
Message =
|
||||
case rpc:call(Node, ?MODULE, process_remote_command, [Args]) of
|
||||
Message = case rpc:call(Node, ?MODULE,
|
||||
process_remote_command, [Args])
|
||||
of
|
||||
{badrpc, Reason} ->
|
||||
io_lib:format("Command failed:~n~p", [Reason]);
|
||||
Result ->
|
||||
Result
|
||||
Result -> Result
|
||||
end,
|
||||
send_message(To, From, Message).
|
||||
send_message(To, From, iolist_to_binary(Message)).
|
||||
|
||||
process_remote_command([kill, SPid]) ->
|
||||
exit(list_to_pid(SPid), kill),
|
||||
"ok";
|
||||
exit(list_to_pid(SPid), kill), <<"ok">>;
|
||||
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]);
|
||||
process_remote_command([setlh, NewValue]) ->
|
||||
{lh_changed, OldLH, NewLH} = gen_server:call(ejabberd_system_monitor, {set, large_heap, NewValue}),
|
||||
io_lib:format("Result of set large heap: ~p --> ~p", [OldLH, NewLH]);
|
||||
process_remote_command(_) ->
|
||||
throw(unknown_command).
|
||||
|
||||
{lh_changed, OldLH, NewLH} =
|
||||
gen_server:call(ejabberd_system_monitor,
|
||||
{set, large_heap, NewValue}),
|
||||
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).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start_link/2, init/1]).
|
||||
@ -32,8 +33,8 @@
|
||||
start_link(Name, Module) ->
|
||||
supervisor:start_link({local, Name}, ?MODULE, Module).
|
||||
|
||||
|
||||
init(Module) ->
|
||||
{ok, {{simple_one_for_one, 10, 1},
|
||||
[{undefined, {Module, start_link, []},
|
||||
temporary, brutal_kill, worker, [Module]}]}}.
|
||||
{ok,
|
||||
{{simple_one_for_one, 10, 1},
|
||||
[{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
|
||||
%% Dialyzer reports a call to missing function; don't worry.
|
||||
eval_script(Script, Apps, LibDirs) ->
|
||||
case lists:member({eval_script, 5}, release_handler_1:module_info(exports)) of
|
||||
true ->
|
||||
release_handler_1:eval_script(Script, Apps, LibDirs, [], []);
|
||||
false ->
|
||||
release_handler_1:eval_script(Script, Apps, LibDirs)
|
||||
end.
|
||||
release_handler_1:eval_script(Script, Apps, LibDirs, [], []).
|
||||
|
||||
%% Get information about the modified modules
|
||||
update_info() ->
|
||||
|
@ -26,7 +26,7 @@ EFLAGS += -pz ..
|
||||
|
||||
# make debug=true to compile Erlang module with debug informations.
|
||||
ifdef debug
|
||||
EFLAGS+=+debug_info +export_all
|
||||
EFLAGS+=+debug_info
|
||||
endif
|
||||
|
||||
ERLSHLIBS = ../ejabberd_zlib_drv.so
|
||||
|
@ -25,169 +25,184 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_zlib).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
-export([start/0, start_link/0,
|
||||
enable_zlib/2, disable_zlib/1,
|
||||
send/2,
|
||||
recv/2, recv/3, recv_data/2,
|
||||
setopts/2,
|
||||
sockname/1, peername/1,
|
||||
get_sockmod/1,
|
||||
controlling_process/2,
|
||||
close/1]).
|
||||
-export([start/0, start_link/0, enable_zlib/2,
|
||||
disable_zlib/1, send/2, recv/2, recv/3, recv_data/2,
|
||||
setopts/2, sockname/1, peername/1, get_sockmod/1,
|
||||
controlling_process/2, close/1]).
|
||||
|
||||
%% Internal exports, call-back functions.
|
||||
-export([init/1,
|
||||
handle_call/3,
|
||||
handle_cast/2,
|
||||
handle_info/2,
|
||||
code_change/3,
|
||||
terminate/2]).
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, code_change/3, terminate/2]).
|
||||
|
||||
-define(DEFLATE, 1).
|
||||
|
||||
-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() ->
|
||||
gen_server:start({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [],
|
||||
[]).
|
||||
|
||||
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;
|
||||
{error, already_loaded} -> ok
|
||||
end,
|
||||
Port = open_port({spawn, "ejabberd_zlib_drv"}, [binary]),
|
||||
Port = open_port({spawn, "ejabberd_zlib_drv"},
|
||||
[binary]),
|
||||
{ok, Port}.
|
||||
|
||||
|
||||
%%% --------------------------------------------------------
|
||||
%%% The call-back functions.
|
||||
%%% --------------------------------------------------------
|
||||
|
||||
handle_call(_, _, State) ->
|
||||
{noreply, State}.
|
||||
handle_call(_, _, State) -> {noreply, State}.
|
||||
|
||||
handle_cast(_, State) ->
|
||||
{noreply, State}.
|
||||
handle_cast(_, State) -> {noreply, State}.
|
||||
|
||||
handle_info({'EXIT', Port, Reason}, Port) ->
|
||||
{stop, {port_died, Reason}, Port};
|
||||
|
||||
handle_info({'EXIT', _Pid, _Reason}, Port) ->
|
||||
{noreply, Port};
|
||||
handle_info(_, State) -> {noreply, State}.
|
||||
|
||||
handle_info(_, State) ->
|
||||
{noreply, State}.
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
terminate(_Reason, Port) ->
|
||||
Port ! {self, close},
|
||||
ok.
|
||||
terminate(_Reason, Port) -> Port ! {self, close}, ok.
|
||||
|
||||
-spec enable_zlib(atom(), inet:socket()) -> {ok, zlib_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;
|
||||
{error, already_loaded} -> ok
|
||||
end,
|
||||
Port = open_port({spawn, "ejabberd_zlib_drv"}, [binary]),
|
||||
{ok, #zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}}.
|
||||
Port = open_port({spawn, "ejabberd_zlib_drv"},
|
||||
[binary]),
|
||||
{ok,
|
||||
#zlibsock{sockmod = SockMod, socket = Socket,
|
||||
zlibport = Port}}.
|
||||
|
||||
disable_zlib(#zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}) ->
|
||||
port_close(Port),
|
||||
{SockMod, Socket}.
|
||||
-spec disable_zlib(zlib_socket()) -> {atom(), inet:socket()}.
|
||||
|
||||
recv(Socket, Length) ->
|
||||
recv(Socket, Length, infinity).
|
||||
recv(#zlibsock{sockmod = SockMod, socket = Socket} = ZlibSock,
|
||||
disable_zlib(#zlibsock{sockmod = SockMod,
|
||||
socket = Socket, zlibport = Port}) ->
|
||||
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) ->
|
||||
case SockMod:recv(Socket, Length, Timeout) of
|
||||
{ok, Packet} ->
|
||||
recv_data(ZlibSock, Packet);
|
||||
{error, _Reason} = Error ->
|
||||
Error
|
||||
{ok, Packet} -> recv_data(ZlibSock, Packet);
|
||||
{error, _Reason} = Error -> Error
|
||||
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
|
||||
gen_tcp ->
|
||||
recv_data2(ZlibSock, Packet);
|
||||
gen_tcp -> recv_data2(ZlibSock, Packet);
|
||||
_ ->
|
||||
case SockMod:recv_data(Socket, Packet) of
|
||||
{ok, Packet2} ->
|
||||
recv_data2(ZlibSock, Packet2);
|
||||
Error ->
|
||||
Error
|
||||
{ok, Packet2} -> recv_data2(ZlibSock, Packet2);
|
||||
Error -> Error
|
||||
end
|
||||
end.
|
||||
|
||||
recv_data2(ZlibSock, Packet) ->
|
||||
case catch recv_data1(ZlibSock, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
{error, Reason};
|
||||
Res ->
|
||||
Res
|
||||
{'EXIT', Reason} -> {error, Reason};
|
||||
Res -> Res
|
||||
end.
|
||||
|
||||
recv_data1(#zlibsock{zlibport = Port} = _ZlibSock, Packet) ->
|
||||
recv_data1(#zlibsock{zlibport = Port} = _ZlibSock,
|
||||
Packet) ->
|
||||
case port_control(Port, ?INFLATE, Packet) of
|
||||
<<0, In/binary>> ->
|
||||
{ok, In};
|
||||
<<1, Error/binary>> ->
|
||||
{error, binary_to_list(Error)}
|
||||
<<0, In/binary>> -> {ok, In};
|
||||
<<1, Error/binary>> -> {error, (Error)}
|
||||
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) ->
|
||||
case port_control(Port, ?DEFLATE, Packet) of
|
||||
<<0, Out/binary>> ->
|
||||
SockMod:send(Socket, Out);
|
||||
<<1, Error/binary>> ->
|
||||
{error, binary_to_list(Error)}
|
||||
<<0, Out/binary>> -> SockMod:send(Socket, Out);
|
||||
<<1, Error/binary>> -> {error, (Error)}
|
||||
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
|
||||
gen_tcp ->
|
||||
inet:setopts(Socket, Opts);
|
||||
_ ->
|
||||
SockMod:setopts(Socket, Opts)
|
||||
gen_tcp -> inet:setopts(Socket, Opts);
|
||||
_ -> SockMod:setopts(Socket, Opts)
|
||||
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
|
||||
gen_tcp ->
|
||||
inet:sockname(Socket);
|
||||
_ ->
|
||||
SockMod:sockname(Socket)
|
||||
gen_tcp -> inet:sockname(Socket);
|
||||
_ -> SockMod:sockname(Socket)
|
||||
end.
|
||||
|
||||
get_sockmod(#zlibsock{sockmod = SockMod}) ->
|
||||
SockMod.
|
||||
-spec get_sockmod(zlib_socket()) -> atom().
|
||||
|
||||
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
|
||||
gen_tcp ->
|
||||
inet:peername(Socket);
|
||||
_ ->
|
||||
SockMod:peername(Socket)
|
||||
gen_tcp -> inet:peername(Socket);
|
||||
_ -> SockMod:peername(Socket)
|
||||
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).
|
||||
|
||||
close(#zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}) ->
|
||||
SockMod:close(Socket),
|
||||
port_close(Port).
|
||||
|
||||
-spec close(zlib_socket()) -> true.
|
||||
|
||||
close(#zlibsock{sockmod = SockMod, socket = Socket,
|
||||
zlibport = Port}) ->
|
||||
SockMod:close(Socket), port_close(Port).
|
||||
|
@ -53,6 +53,7 @@ if [ "$EJABBERD_DOC_PATH" = "" ] ; then
|
||||
fi
|
||||
if [ "$ERLANG_NODE_ARG" != "" ] ; then
|
||||
ERLANG_NODE=$ERLANG_NODE_ARG
|
||||
NODE=${ERLANG_NODE%@*}
|
||||
fi
|
||||
|
||||
# check the proper system user is used
|
||||
|
582
src/ejd2odbc.erl
582
src/ejd2odbc.erl
@ -25,59 +25,12 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejd2odbc).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
%% External exports
|
||||
-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]).
|
||||
-export([export/2, export/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-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).
|
||||
-define(MAX_RECORDS_PER_TRANSACTION, 100).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
@ -89,397 +42,43 @@
|
||||
%%% - Output can be either odbc to export to the configured relational
|
||||
%%% database or "Filename" to export to text file.
|
||||
|
||||
export_passwd(Server, Output) ->
|
||||
export_common(
|
||||
Server, passwd, Output,
|
||||
fun(_Host, {passwd, {LUser, LServer}, {scram, _, _, _, _}} = _R) ->
|
||||
?INFO_MSG("You are trying to export the authentication "
|
||||
"information of the account ~s@~s, but his password "
|
||||
"is stored as SCRAM, and ejabberd ODBC authentication "
|
||||
"doesn't support SCRAM.", [LUser, LServer]),
|
||||
[];
|
||||
(Host, {passwd, {LUser, LServer}, Password} = _R)
|
||||
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).
|
||||
export(Server, Output) ->
|
||||
LServer = jlib:nameprep(iolist_to_binary(Server)),
|
||||
Modules = [ejabberd_auth,
|
||||
mod_announce,
|
||||
mod_caps,
|
||||
mod_irc,
|
||||
mod_last,
|
||||
mod_muc,
|
||||
mod_offline,
|
||||
mod_privacy,
|
||||
mod_private,
|
||||
mod_roster,
|
||||
mod_shared_roster,
|
||||
mod_vcard,
|
||||
mod_vcard_xupdate],
|
||||
IO = prepare_output(Output),
|
||||
lists:foreach(
|
||||
fun(Module) ->
|
||||
export(LServer, IO, Module)
|
||||
end, Modules),
|
||||
close_output(Output, IO).
|
||||
|
||||
export_roster(Server, Output) ->
|
||||
export_common(
|
||||
Server, roster, Output,
|
||||
fun(Host, #roster{usj = {LUser, LServer, LJID}} = R)
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
|
||||
ItemVals = record_to_string(R),
|
||||
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).
|
||||
export(Server, Output, Module) ->
|
||||
LServer = jlib:nameprep(iolist_to_binary(Server)),
|
||||
IO = prepare_output(Output),
|
||||
lists:foreach(
|
||||
fun({Table, ConvertFun}) ->
|
||||
export(LServer, Table, IO, ConvertFun)
|
||||
end, Module:export(Server)),
|
||||
close_output(Output, IO).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
export_common(Server, Table, Output, ConvertFun) ->
|
||||
IO = case Output of
|
||||
odbc ->
|
||||
odbc;
|
||||
_ ->
|
||||
{ok, IODevice} = file:open(Output, [write, raw]),
|
||||
IODevice
|
||||
end,
|
||||
mnesia:transaction(
|
||||
fun() ->
|
||||
export(LServer, Table, IO, ConvertFun) ->
|
||||
F = fun () ->
|
||||
mnesia:read_lock_table(Table),
|
||||
LServer = jlib:nameprep(Server),
|
||||
{_N, SQLs} =
|
||||
mnesia:foldl(
|
||||
fun(R, {N, SQLs} = Acc) ->
|
||||
@ -487,83 +86,54 @@ export_common(Server, Table, Output, ConvertFun) ->
|
||||
[] ->
|
||||
Acc;
|
||||
SQL ->
|
||||
if
|
||||
N < ?MAX_RECORDS_PER_TRANSACTION - 1 ->
|
||||
if N < (?MAX_RECORDS_PER_TRANSACTION) - 1 ->
|
||||
{N + 1, [SQL | SQLs]};
|
||||
true ->
|
||||
%% Execute full SQL transaction
|
||||
output(LServer, IO,
|
||||
["begin;",
|
||||
lists:reverse([SQL | SQLs]),
|
||||
"commit"]),
|
||||
output(LServer,
|
||||
Table, IO,
|
||||
flatten([SQL | SQLs])),
|
||||
{0, []}
|
||||
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,
|
||||
SAsk = case Ask of
|
||||
subscribe -> "S";
|
||||
unsubscribe -> "U";
|
||||
both -> "B";
|
||||
out -> "O";
|
||||
in -> "I";
|
||||
none -> "N"
|
||||
{0, []}, Table),
|
||||
output(LServer, Table, IO, flatten(SQLs))
|
||||
end,
|
||||
SAskMessage =
|
||||
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')"].
|
||||
mnesia:transaction(F).
|
||||
|
||||
groups_to_string(#roster{usj = {User, _Server, JID},
|
||||
groups = Groups}) ->
|
||||
Username = ejabberd_odbc:escape(User),
|
||||
SJID = ejabberd_odbc:escape(jlib:jid_to_string(JID)),
|
||||
[["("
|
||||
"'", Username, "',"
|
||||
"'", SJID, "',"
|
||||
"'", ejabberd_odbc:escape(Group), "')"] || Group <- Groups].
|
||||
output(_LServer, _Table, _IO, []) ->
|
||||
ok;
|
||||
output(LServer, _Table, odbc, SQLs) ->
|
||||
ejabberd_odbc:sql_transaction(LServer, SQLs);
|
||||
output(_LServer, Table, Fd, SQLs) ->
|
||||
file:write(Fd, ["-- \n-- Mnesia table: ", atom_to_list(Table),
|
||||
"\n--\n", SQLs]).
|
||||
|
||||
get_id() ->
|
||||
ID = get(id),
|
||||
put(id, ID+1),
|
||||
ID+1.
|
||||
prepare_output(FileName) when is_list(FileName); is_binary(FileName) ->
|
||||
case file:open(FileName, [write, raw]) of
|
||||
{ok, Fd} ->
|
||||
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@
|
||||
LIBS = @LIBS@
|
||||
|
||||
ASN_FLAGS = -bber_bin +optimize
|
||||
ASN_FLAGS = -bber_bin +optimize +binary_strings
|
||||
|
||||
ERLANG_CFLAGS = @ERLANG_CFLAGS@
|
||||
ERLANG_LIBS = @ERLANG_LIBS@
|
||||
@ -17,7 +17,7 @@ EFLAGS += -pz ..
|
||||
|
||||
# make debug=true to compile Erlang module with debug informations.
|
||||
ifdef debug
|
||||
EFLAGS+=+debug_info +export_all
|
||||
EFLAGS+=+debug_info
|
||||
endif
|
||||
|
||||
OUTDIR = ..
|
||||
@ -31,6 +31,8 @@ ELDAPv3.beam: ELDAPv3.erl
|
||||
|
||||
ELDAPv3.erl: ELDAPv3.asn
|
||||
@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
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -20,20 +20,45 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-define(LDAP_PORT, 389).
|
||||
|
||||
-define(LDAPS_PORT, 636).
|
||||
|
||||
-record(eldap_search, {scope = wholeSubtree,
|
||||
base = [],
|
||||
filter,
|
||||
limit = 0,
|
||||
attributes = [],
|
||||
types_only = false,
|
||||
deref_aliases = neverDerefAliases,
|
||||
timeout = 0}).
|
||||
-type scope() :: baseObject | singleLevel | wholeSubtree.
|
||||
|
||||
-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,
|
||||
referrals}).
|
||||
-record(eldap_search_result, {entries = [] :: [eldap_entry()],
|
||||
referrals = [] :: list()}).
|
||||
|
||||
-record(eldap_entry, {object_name,
|
||||
attributes}).
|
||||
-record(eldap_entry, {object_name = <<>> :: binary(),
|
||||
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).
|
||||
|
||||
%% 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]).
|
||||
|
||||
%%====================================================================
|
||||
@ -50,7 +48,9 @@
|
||||
%%% {ok,{'and',[{'not',{lessOrEqual,{'AttributeValueAssertion',"uid","100"}}},
|
||||
%%% {present,"mail"}]}}
|
||||
%%%-------------------------------------------------------------------
|
||||
parse(L) when is_list(L) ->
|
||||
-spec parse(binary()) -> {error, any()} | {ok, eldap:filter()}.
|
||||
|
||||
parse(L) ->
|
||||
parse(L, []).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
@ -80,8 +80,12 @@ parse(L) when is_list(L) ->
|
||||
%%% "jid",
|
||||
%%% "xramtsov@gmail.com"}}]}}
|
||||
%%%-------------------------------------------------------------------
|
||||
parse(L, SList) when is_list(L), is_list(SList) ->
|
||||
case catch eldap_filter_yecc:parse(scan(L, SList)) of
|
||||
-spec parse(binary(), [{binary(), binary()} |
|
||||
{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 ->
|
||||
{error, Err};
|
||||
{error, {_, _, Msg}} ->
|
||||
@ -95,13 +99,13 @@ parse(L, SList) when is_list(L), is_list(SList) ->
|
||||
%%====================================================================
|
||||
%% 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, "", [], undefined, SList).
|
||||
scan(L, <<"">>, [], undefined, SList).
|
||||
|
||||
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(":=" ++ Rest, Buf, Result, '(', 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, ':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([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) ->
|
||||
lists:reverse(check(Buf, S) ++ Result).
|
||||
|
||||
check([], _) ->
|
||||
check(<<>>, _) ->
|
||||
[];
|
||||
check(Buf, S) ->
|
||||
[{str, 1, do_sub(lists:reverse(Buf), S)}].
|
||||
[{str, 1, binary_to_list(do_sub(Buf, S))}].
|
||||
|
||||
-define(MAX_RECURSION, 100).
|
||||
|
||||
-spec do_sub(binary(), [{binary(), binary()} |
|
||||
{binary(), binary(), pos_integer()}]) -> binary().
|
||||
|
||||
do_sub(S, []) ->
|
||||
S;
|
||||
|
||||
do_sub([], _) ->
|
||||
[];
|
||||
|
||||
do_sub(<<>>, _) ->
|
||||
<<>>;
|
||||
do_sub(S, [{RegExp, New} | T]) ->
|
||||
Result = do_sub(S, {RegExp, replace_amps(New)}, 1),
|
||||
do_sub(Result, T);
|
||||
|
||||
do_sub(S, [{RegExp, New, Times} | T]) ->
|
||||
Result = do_sub(S, {RegExp, replace_amps(New), Times}, 1),
|
||||
do_sub(Result, T).
|
||||
@ -178,9 +182,10 @@ do_sub(S, {RegExp, New, Times}, Iter) ->
|
||||
erlang:error(bad_regexp)
|
||||
end.
|
||||
|
||||
replace_amps(String) ->
|
||||
replace_amps(Bin) ->
|
||||
list_to_binary(
|
||||
lists:flatmap(
|
||||
fun($&) -> "\\&";
|
||||
($\\) -> "\\\\";
|
||||
(Chr) -> [Chr]
|
||||
end, String).
|
||||
end, binary_to_list(Bin))).
|
||||
|
@ -67,5 +67,5 @@ final(Value) -> {final, Value}.
|
||||
'any'(Token, Value) -> [Token, {any, Value}].
|
||||
xattr(Value) -> {type, 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).
|
||||
|
@ -25,24 +25,15 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(eldap_pool).
|
||||
|
||||
-author('xram@jabber.ru').
|
||||
|
||||
%% API
|
||||
-export([
|
||||
start_link/7,
|
||||
bind/3,
|
||||
search/2,
|
||||
modify_passwd/3
|
||||
]).
|
||||
-export([start_link/7, bind/3, search/2,
|
||||
modify_passwd/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-ifdef(SSL40).
|
||||
-define(PG2, pg2).
|
||||
-else.
|
||||
-define(PG2, pg2_backport).
|
||||
-endif.
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
@ -55,26 +46,29 @@ search(PoolName, Opts) ->
|
||||
modify_passwd(PoolName, 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),
|
||||
?PG2:create(PoolName),
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
ID = erlang:ref_to_list(make_ref()),
|
||||
case catch eldap:start_link(ID, [Host|Backups], Port,
|
||||
Rootdn, Passwd, Opts) of
|
||||
{ok, Pid} ->
|
||||
?PG2:join(PoolName, Pid);
|
||||
_ ->
|
||||
pg2:create(PoolName),
|
||||
lists:foreach(fun (Host) ->
|
||||
ID = list_to_binary(erlang:ref_to_list(make_ref())),
|
||||
case catch eldap:start_link(ID, [Host | Backups],
|
||||
Port, Rootdn, Passwd,
|
||||
Opts)
|
||||
of
|
||||
{ok, Pid} -> pg2:join(PoolName, Pid);
|
||||
Err ->
|
||||
?INFO_MSG("Err = ~p", [Err]),
|
||||
error
|
||||
end
|
||||
end, Hosts).
|
||||
end,
|
||||
Hosts).
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
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) ->
|
||||
case catch apply(eldap, F, [Pid | Args]) of
|
||||
{'EXIT', {timeout, _}} ->
|
||||
@ -83,12 +77,10 @@ do_request(Name, {F, Args}) ->
|
||||
?ERROR_MSG("LDAP request failed: eldap:~p(~p)~nReason: ~p",
|
||||
[F, Args, Reason]),
|
||||
{error, Reason};
|
||||
Reply ->
|
||||
Reply
|
||||
Reply -> Reply
|
||||
end;
|
||||
Err ->
|
||||
Err
|
||||
Err -> Err
|
||||
end.
|
||||
|
||||
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,
|
||||
find_ldap_attrs/2,
|
||||
get_ldap_attr/2,
|
||||
usort_attrs/1,
|
||||
get_user_part/2,
|
||||
make_filter/2,
|
||||
get_state/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]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("eldap.hrl").
|
||||
|
||||
%% Generate an 'or' LDAP query on one or several attributes
|
||||
%% If there is only one attribute
|
||||
@ -46,27 +49,34 @@ generate_subfilter([UID]) ->
|
||||
subfilter(UID);
|
||||
%% If there is several attributes
|
||||
generate_subfilter(UIDs) ->
|
||||
"(|" ++ [subfilter(UID) || UID <- UIDs] ++ ")".
|
||||
iolist_to_binary(["(|", [subfilter(UID) || UID <- UIDs], ")"]).
|
||||
%% Subfilter for a single attribute
|
||||
|
||||
subfilter({UIDAttr, UIDAttrFormat}) ->
|
||||
"(" ++ UIDAttr ++ "=" ++ UIDAttrFormat ++ ")";
|
||||
%% The default UiDAttrFormat is %u
|
||||
<<$(, UIDAttr/binary, $=, UIDAttrFormat/binary, $)>>;
|
||||
%% The default UiDAttrFormat is <<"%u">>
|
||||
subfilter({UIDAttr}) ->
|
||||
"(" ++ UIDAttr ++ "=" ++ "%u)".
|
||||
<<$(, UIDAttr/binary, $=, "%u)">>.
|
||||
|
||||
%% Not tail-recursive, but it is not very terribly.
|
||||
%% 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, "%u"} | Rest], Attributes);
|
||||
find_ldap_attrs([{Attr, <<"%u">>} | Rest], Attributes);
|
||||
find_ldap_attrs([{Attr, Format} | Rest], Attributes) ->
|
||||
case get_ldap_attr(Attr, Attributes) of
|
||||
Value when is_list(Value), Value /= "" ->
|
||||
Value when is_binary(Value), Value /= <<>> ->
|
||||
{Value, Format};
|
||||
_ ->
|
||||
find_ldap_attrs(Rest, Attributes)
|
||||
end;
|
||||
find_ldap_attrs([], _) ->
|
||||
"".
|
||||
<<>>.
|
||||
|
||||
-spec get_ldap_attr(binary(), [{binary(), [binary()]}]) -> binary().
|
||||
|
||||
get_ldap_attr(LDAPAttr, Attributes) ->
|
||||
Res = lists:filter(
|
||||
@ -75,31 +85,26 @@ get_ldap_attr(LDAPAttr, Attributes) ->
|
||||
end, Attributes),
|
||||
case Res of
|
||||
[{_, [Value|_]}] -> Value;
|
||||
_ -> ""
|
||||
_ -> <<>>
|
||||
end.
|
||||
|
||||
|
||||
usort_attrs(Attrs) when is_list(Attrs) ->
|
||||
lists:usort(Attrs);
|
||||
usort_attrs(_) ->
|
||||
[].
|
||||
-spec get_user_part(binary(), binary()) -> {ok, binary()} | {error, badmatch}.
|
||||
|
||||
get_user_part(String, Pattern) ->
|
||||
F = fun(S, P) ->
|
||||
First = string:str(P, "%u"),
|
||||
TailLength = length(P) - (First+1),
|
||||
string:sub_string(S, First, length(S) - TailLength)
|
||||
First = str:str(P, <<"%u">>),
|
||||
TailLength = byte_size(P) - (First+1),
|
||||
str:sub_string(S, First, byte_size(S) - TailLength)
|
||||
end,
|
||||
case catch F(String, Pattern) of
|
||||
{'EXIT', _} ->
|
||||
{error, badmatch};
|
||||
Result ->
|
||||
case catch ejabberd_regexp:replace(Pattern, "%u", Result) of
|
||||
case catch ejabberd_regexp:replace(Pattern, <<"%u">>, Result) of
|
||||
{'EXIT', _} ->
|
||||
{error, badmatch};
|
||||
StringRes ->
|
||||
case (string:to_lower(StringRes) ==
|
||||
string:to_lower(String)) of
|
||||
case case_insensitive_match(StringRes, String) of
|
||||
true ->
|
||||
{ok, Result};
|
||||
false ->
|
||||
@ -108,20 +113,25 @@ get_user_part(String, Pattern) ->
|
||||
end
|
||||
end.
|
||||
|
||||
-spec make_filter([{binary(), [binary()]}], [{binary(), binary()}]) -> any().
|
||||
|
||||
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(
|
||||
fun({Name, [Value | _]}) ->
|
||||
case Name of
|
||||
"%u" when Value /= "" ->
|
||||
<<"%u">> when Value /= <<"">> ->
|
||||
case eldap_filter:parse(
|
||||
lists:flatten(generate_subfilter(NewUIDs)),
|
||||
[{"%u", Value}]) of
|
||||
generate_subfilter(NewUIDs),
|
||||
[{<<"%u">>, Value}]) of
|
||||
{ok, F} -> [F];
|
||||
_ -> []
|
||||
end;
|
||||
_ when Value /= "" ->
|
||||
[eldap:substrings(Name, [{any, Value}])];
|
||||
_ when Value /= <<"">> ->
|
||||
[eldap:substrings(
|
||||
Name,
|
||||
[{any, Value}])];
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
@ -133,9 +143,11 @@ make_filter(Data, UIDs) ->
|
||||
eldap:'and'(Filter)
|
||||
end.
|
||||
|
||||
-spec case_insensitive_match(binary(), binary()) -> boolean().
|
||||
|
||||
case_insensitive_match(X, Y) ->
|
||||
X1 = stringprep:tolower(X),
|
||||
Y1 = stringprep:tolower(Y),
|
||||
X1 = str:to_lower(X),
|
||||
Y1 = str:to_lower(Y),
|
||||
if
|
||||
X1 == Y1 -> true;
|
||||
true -> false
|
||||
@ -149,22 +161,194 @@ get_state(Server, Module) ->
|
||||
%% we look from alias domain (%d) and make the substitution
|
||||
%% with the actual host domain
|
||||
%% This help when you need to configure many virtual domains.
|
||||
-spec uids_domain_subst(binary(), [{binary(), binary()}]) ->
|
||||
[{binary(), binary()}].
|
||||
|
||||
uids_domain_subst(Host, UIDs) ->
|
||||
lists:map(fun({U,V}) ->
|
||||
{U, eldap_filter:do_sub(V,[{"%d", Host}])};
|
||||
{U, eldap_filter:do_sub(V,[{<<"%d">>, Host}])};
|
||||
(A) -> A
|
||||
end,
|
||||
UIDs).
|
||||
|
||||
check_filter(undefined) ->
|
||||
ok;
|
||||
check_filter(Filter) ->
|
||||
case eldap_filter:parse(Filter) of
|
||||
{ok, _} ->
|
||||
ok;
|
||||
Err ->
|
||||
?ERROR_MSG("failed to parse LDAP filter:~n"
|
||||
"** Filter: ~p~n"
|
||||
"** Reason: ~p",
|
||||
[Filter, Err])
|
||||
-spec get_opt({atom(), binary()}, list(), fun()) -> any().
|
||||
|
||||
get_opt({Key, Host}, Opts, F) ->
|
||||
get_opt({Key, Host}, Opts, F, undefined).
|
||||
|
||||
-spec get_opt({atom(), binary()}, list(), fun(), any()) -> any().
|
||||
|
||||
get_opt({Key, Host}, Opts, F, Default) ->
|
||||
case gen_mod:get_opt(Key, Opts, F, undefined) of
|
||||
undefined ->
|
||||
ejabberd_config:get_local_option(
|
||||
{Key, Host}, F, Default);
|
||||
Val ->
|
||||
Val
|
||||
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_len, name_start, name_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);
|
||||
} else {
|
||||
ei_x_encode_string(&event_buf, name_start+1);
|
||||
ei_x_encode_binary(&event_buf, name_start+1, strlen(name_start+1));
|
||||
};
|
||||
} 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);
|
||||
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);
|
||||
strcpy(buf, "xmlns:");
|
||||
strcpy(buf+6, prefix);
|
||||
ei_x_encode_string(&xmlns_buf, buf);
|
||||
ei_x_encode_binary(&xmlns_buf, buf, strlen(buf));
|
||||
driver_free(buf);
|
||||
} 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;
|
||||
}
|
||||
@ -229,7 +229,7 @@ static ErlDrvSSizeT expat_erl_control(ErlDrvData drv_data,
|
||||
ei_x_encode_long(&event_buf, XML_ERROR);
|
||||
ei_x_encode_tuple_header(&event_buf, 2);
|
||||
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);
|
||||
|
106
src/extauth.erl
106
src/extauth.erl
@ -25,30 +25,24 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(extauth).
|
||||
|
||||
-author('leifj@it.su.se').
|
||||
|
||||
-export([start/2,
|
||||
stop/1,
|
||||
init/2,
|
||||
check_password/3,
|
||||
set_password/3,
|
||||
try_register/3,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
is_user_exists/2]).
|
||||
-export([start/2, stop/1, init/2, check_password/3,
|
||||
set_password/3, try_register/3, remove_user/2,
|
||||
remove_user/3, is_user_exists/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-define(INIT_TIMEOUT, 60000). % Timeout is in milliseconds: 60 seconds == 60000
|
||||
-define(CALL_TIMEOUT, 10000). % Timeout is in milliseconds: 10 seconds == 10000
|
||||
-define(INIT_TIMEOUT, 60000).
|
||||
|
||||
-define(CALL_TIMEOUT, 10000).
|
||||
|
||||
start(Host, ExtPrg) ->
|
||||
lists:foreach(
|
||||
fun(This) ->
|
||||
lists:foreach(fun (This) ->
|
||||
start_instance(get_process_name(Host, This), ExtPrg)
|
||||
end,
|
||||
lists:seq(0, get_instances(Host)-1)
|
||||
).
|
||||
lists:seq(0, get_instances(Host) - 1)).
|
||||
|
||||
start_instance(ProcessName, ExtPrg) ->
|
||||
spawn(?MODULE, init, [ProcessName, ExtPrg]).
|
||||
@ -59,20 +53,20 @@ restart_instance(ProcessName, ExtPrg) ->
|
||||
|
||||
init(ProcessName, ExtPrg) ->
|
||||
register(ProcessName, self()),
|
||||
process_flag(trap_exit,true),
|
||||
Port = open_port({spawn, ExtPrg}, [{packet,2}]),
|
||||
process_flag(trap_exit, true),
|
||||
Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
|
||||
loop(Port, ?INIT_TIMEOUT, ProcessName, ExtPrg).
|
||||
|
||||
stop(Host) ->
|
||||
lists:foreach(
|
||||
fun(This) ->
|
||||
lists:foreach(fun (This) ->
|
||||
get_process_name(Host, This) ! stop
|
||||
end,
|
||||
lists:seq(0, get_instances(Host)-1)
|
||||
).
|
||||
lists:seq(0, get_instances(Host) - 1)).
|
||||
|
||||
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) ->
|
||||
call_port(Server, ["auth", User, Server, Password]).
|
||||
@ -84,7 +78,9 @@ set_password(User, Server, Password) ->
|
||||
call_port(Server, ["setpass", 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};
|
||||
false -> {error, not_allowed}
|
||||
end.
|
||||
@ -93,27 +89,27 @@ remove_user(User, Server) ->
|
||||
call_port(Server, ["removeuser", User, Server]).
|
||||
|
||||
remove_user(User, Server, Password) ->
|
||||
call_port(Server, ["removeuser3", User, Server, Password]).
|
||||
call_port(Server,
|
||||
["removeuser3", User, Server, Password]).
|
||||
|
||||
call_port(Server, Msg) ->
|
||||
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},
|
||||
receive
|
||||
{eauth,Result} ->
|
||||
Result
|
||||
end.
|
||||
receive {eauth, Result} -> Result end.
|
||||
|
||||
random_instance(MaxNum) ->
|
||||
{A1,A2,A3} = now(),
|
||||
{A1, A2, A3} = now(),
|
||||
random:seed(A1, A2, A3),
|
||||
random:uniform(MaxNum) - 1.
|
||||
|
||||
get_instances(Server) ->
|
||||
case ejabberd_config:get_local_option({extauth_instances, Server}) of
|
||||
Num when is_integer(Num) -> Num;
|
||||
_ -> 1
|
||||
end.
|
||||
ejabberd_config:get_local_option(
|
||||
{extauth_instances, Server},
|
||||
fun(V) when is_integer(V), V > 0 ->
|
||||
V
|
||||
end, 1).
|
||||
|
||||
loop(Port, Timeout, ProcessName, ExtPrg) ->
|
||||
receive
|
||||
@ -121,16 +117,18 @@ loop(Port, Timeout, ProcessName, ExtPrg) ->
|
||||
port_command(Port, encode(Msg)),
|
||||
receive
|
||||
{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)},
|
||||
loop(Port, ?CALL_TIMEOUT, ProcessName, ExtPrg);
|
||||
{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},
|
||||
loop(Port, ?CALL_TIMEOUT, ProcessName, ExtPrg)
|
||||
after
|
||||
Timeout ->
|
||||
?ERROR_MSG("extauth call '~p' didn't receive response", [Msg]),
|
||||
after Timeout ->
|
||||
?ERROR_MSG("extauth call '~p' didn't receive response",
|
||||
[Msg]),
|
||||
Caller ! {eauth, false},
|
||||
Pid = restart_instance(ProcessName, ExtPrg),
|
||||
flush_buffer_and_forward_messages(Pid),
|
||||
@ -138,12 +136,11 @@ loop(Port, Timeout, ProcessName, ExtPrg) ->
|
||||
end;
|
||||
stop ->
|
||||
Port ! {self(), close},
|
||||
receive
|
||||
{Port, closed} ->
|
||||
exit(normal)
|
||||
end;
|
||||
receive {Port, closed} -> exit(normal) end;
|
||||
{'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),
|
||||
flush_buffer_and_forward_messages(Pid),
|
||||
exit(port_terminated)
|
||||
@ -152,22 +149,17 @@ loop(Port, Timeout, ProcessName, ExtPrg) ->
|
||||
flush_buffer_and_forward_messages(Pid) ->
|
||||
receive
|
||||
Message ->
|
||||
Pid ! Message,
|
||||
flush_buffer_and_forward_messages(Pid)
|
||||
after 0 ->
|
||||
true
|
||||
Pid ! Message, flush_buffer_and_forward_messages(Pid)
|
||||
after 0 -> true
|
||||
end.
|
||||
|
||||
join(List, Sep) ->
|
||||
lists:foldl(fun(A, "") -> A;
|
||||
lists:foldl(fun (A, "") -> A;
|
||||
(A, Acc) -> Acc ++ Sep ++ A
|
||||
end, "", List).
|
||||
end,
|
||||
"", List).
|
||||
|
||||
encode(L) ->
|
||||
join(L,":").
|
||||
|
||||
decode([0,0]) ->
|
||||
false;
|
||||
decode([0,1]) ->
|
||||
true.
|
||||
encode(L) -> join(L, ":").
|
||||
|
||||
decode([0, 0]) -> false;
|
||||
decode([0, 1]) -> true.
|
||||
|
@ -25,27 +25,23 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(gen_iq_handler).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/3,
|
||||
add_iq_handler/6,
|
||||
remove_iq_handler/3,
|
||||
stop_iq_handler/3,
|
||||
handle/7,
|
||||
-export([start_link/3, add_iq_handler/6,
|
||||
remove_iq_handler/3, stop_iq_handler/3, handle/7,
|
||||
process_iq/6]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-record(state, {host,
|
||||
module,
|
||||
function}).
|
||||
-record(state, {host, module, function}).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
@ -55,30 +51,34 @@
|
||||
%% Description: Starts the server
|
||||
%%--------------------------------------------------------------------
|
||||
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
|
||||
no_queue ->
|
||||
Component:register_iq_handler(Host, NS, Module, Function, no_queue);
|
||||
Component:register_iq_handler(Host, NS, Module,
|
||||
Function, no_queue);
|
||||
one_queue ->
|
||||
{ok, Pid} = supervisor:start_child(ejabberd_iq_sup,
|
||||
[Host, Module, Function]),
|
||||
Component:register_iq_handler(Host, NS, Module, Function,
|
||||
{one_queue, Pid});
|
||||
Component:register_iq_handler(Host, NS, Module,
|
||||
Function, {one_queue, Pid});
|
||||
{queues, N} ->
|
||||
Pids =
|
||||
lists:map(
|
||||
fun(_) ->
|
||||
{ok, Pid} = supervisor:start_child(
|
||||
ejabberd_iq_sup,
|
||||
[Host, Module, Function]),
|
||||
Pids = lists:map(fun (_) ->
|
||||
{ok, Pid} =
|
||||
supervisor:start_child(ejabberd_iq_sup,
|
||||
[Host, Module,
|
||||
Function]),
|
||||
Pid
|
||||
end, lists:seq(1, N)),
|
||||
Component:register_iq_handler(Host, NS, Module, Function,
|
||||
{queues, Pids});
|
||||
end,
|
||||
lists:seq(1, N)),
|
||||
Component:register_iq_handler(Host, NS, Module,
|
||||
Function, {queues, Pids});
|
||||
parallel ->
|
||||
Component:register_iq_handler(Host, NS, Module, Function, parallel)
|
||||
Component:register_iq_handler(Host, NS, Module,
|
||||
Function, parallel)
|
||||
end.
|
||||
|
||||
remove_iq_handler(Component, Host, NS) ->
|
||||
@ -86,43 +86,37 @@ remove_iq_handler(Component, Host, NS) ->
|
||||
|
||||
stop_iq_handler(_Module, _Function, Opts) ->
|
||||
case Opts of
|
||||
{one_queue, Pid} ->
|
||||
gen_server:call(Pid, stop);
|
||||
{one_queue, Pid} -> gen_server:call(Pid, stop);
|
||||
{queues, Pids} ->
|
||||
lists:foreach(fun(Pid) ->
|
||||
lists:foreach(fun (Pid) ->
|
||||
catch gen_server:call(Pid, stop)
|
||||
end, Pids);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
Pids);
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
handle(Host, Module, Function, Opts, From, To, IQ) ->
|
||||
case Opts of
|
||||
no_queue ->
|
||||
process_iq(Host, Module, Function, From, To, IQ);
|
||||
{one_queue, Pid} ->
|
||||
Pid ! {process_iq, From, To, IQ};
|
||||
{one_queue, Pid} -> Pid ! {process_iq, From, To, IQ};
|
||||
{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};
|
||||
parallel ->
|
||||
spawn(?MODULE, process_iq, [Host, Module, Function, From, To, IQ]);
|
||||
_ ->
|
||||
todo
|
||||
spawn(?MODULE, process_iq,
|
||||
[Host, Module, Function, From, To, IQ]);
|
||||
_ -> todo
|
||||
end.
|
||||
|
||||
|
||||
process_iq(_Host, Module, Function, From, To, IQ) ->
|
||||
case catch Module:Function(From, To, IQ) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p", [Reason]);
|
||||
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
|
||||
ResIQ ->
|
||||
if
|
||||
ResIQ /= ignore ->
|
||||
ejabberd_router:route(To, From,
|
||||
jlib:iq_to_xml(ResIQ));
|
||||
true ->
|
||||
ok
|
||||
if ResIQ /= ignore ->
|
||||
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
|
||||
true -> ok
|
||||
end
|
||||
end.
|
||||
|
||||
@ -138,8 +132,8 @@ process_iq(_Host, Module, Function, From, To, IQ) ->
|
||||
%% Description: Initiates the server
|
||||
%%--------------------------------------------------------------------
|
||||
init([Host, Module, Function]) ->
|
||||
{ok, #state{host = Host,
|
||||
module = Module,
|
||||
{ok,
|
||||
#state{host = Host, module = Module,
|
||||
function = Function}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@ -152,8 +146,7 @@ init([Host, Module, Function]) ->
|
||||
%% Description: Handling call messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_call(stop, _From, State) ->
|
||||
Reply = ok,
|
||||
{stop, normal, Reply, State}.
|
||||
Reply = ok, {stop, normal, Reply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
||||
@ -161,8 +154,7 @@ handle_call(stop, _From, State) ->
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
handle_cast(_Msg, 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
|
||||
%%--------------------------------------------------------------------
|
||||
handle_info({process_iq, From, To, IQ},
|
||||
#state{host = Host,
|
||||
module = Module,
|
||||
function = Function} = State) ->
|
||||
#state{host = Host, module = Module,
|
||||
function = Function} =
|
||||
State) ->
|
||||
process_iq(Host, Module, Function, From, To, IQ),
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
handle_info(_Info, State) -> {noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: terminate(Reason, State) -> void()
|
||||
@ -186,16 +177,15 @@ handle_info(_Info, State) ->
|
||||
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
terminate(_Reason, _State) -> ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
%% Description: Convert process state when code is changed
|
||||
%%--------------------------------------------------------------------
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
|
235
src/gen_mod.erl
235
src/gen_mod.erl
@ -2,6 +2,7 @@
|
||||
%%% File : gen_mod.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose :
|
||||
%%% Purpose :
|
||||
%%% Created : 24 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
@ -25,78 +26,81 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(gen_mod).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/0,
|
||||
start_module/3,
|
||||
stop_module/2,
|
||||
stop_module_keep_config/2,
|
||||
get_opt/2,
|
||||
get_opt/3,
|
||||
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([start/0, start_module/3, stop_module/2,
|
||||
stop_module_keep_config/2, get_opt/3, get_opt/4,
|
||||
get_opt_host/3, db_type/1, db_type/2, get_module_opt/5,
|
||||
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").
|
||||
|
||||
-record(ejabberd_module, {module_host, opts}).
|
||||
-record(ejabberd_module,
|
||||
{module_host = {undefined, <<"">>} :: {atom(), binary()},
|
||||
opts = [] :: opts() | '_' | '$2'}).
|
||||
|
||||
behaviour_info(callbacks) ->
|
||||
[{start, 2},
|
||||
{stop, 1}];
|
||||
behaviour_info(_Other) ->
|
||||
undefined.
|
||||
-type opts() :: [{atom(), any()}].
|
||||
|
||||
-callback start(binary(), opts()) -> any().
|
||||
-callback stop(binary()) -> any().
|
||||
|
||||
-export_type([opts/0]).
|
||||
|
||||
%%behaviour_info(callbacks) -> [{start, 2}, {stop, 1}];
|
||||
%%behaviour_info(_Other) -> undefined.
|
||||
|
||||
start() ->
|
||||
ets:new(ejabberd_modules, [named_table,
|
||||
public,
|
||||
ets:new(ejabberd_modules,
|
||||
[named_table, public,
|
||||
{keypos, #ejabberd_module.module_host}]),
|
||||
ok.
|
||||
|
||||
-spec start_module(binary(), atom(), opts()) -> any().
|
||||
|
||||
start_module(Host, Module, Opts) ->
|
||||
set_module_opts_mnesia(Host, Module, Opts),
|
||||
ets:insert(ejabberd_modules,
|
||||
#ejabberd_module{module_host = {Module, Host},
|
||||
opts = Opts}),
|
||||
try Module:start(Host, Opts)
|
||||
catch Class:Reason ->
|
||||
try Module:start(Host, Opts) catch
|
||||
Class:Reason ->
|
||||
del_module_mnesia(Host, Module),
|
||||
ets:delete(ejabberd_modules, {Module, Host}),
|
||||
ErrorText = io_lib:format("Problem starting the module ~p for host ~p ~n options: ~p~n ~p: ~p",
|
||||
[Module, Host, Opts, Class, Reason]),
|
||||
ErrorText =
|
||||
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, []),
|
||||
case is_app_running(ejabberd) of
|
||||
true ->
|
||||
erlang:raise(Class, Reason, erlang:get_stacktrace());
|
||||
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),
|
||||
erlang:halt(string:substr(lists:flatten(ErrorText), 1, 199))
|
||||
end
|
||||
end.
|
||||
|
||||
is_app_running(AppName) ->
|
||||
%% Use a high timeout to prevent a false positive in a high load system
|
||||
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.
|
||||
stop_module(Host, Module) ->
|
||||
case stop_module_keep_config(Host, Module) of
|
||||
error ->
|
||||
error;
|
||||
ok ->
|
||||
del_module_mnesia(Host, Module)
|
||||
error -> error;
|
||||
ok -> del_module_mnesia(Host, Module)
|
||||
end.
|
||||
|
||||
%% @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.
|
||||
%% This function is useful when ejabberd is being stopped
|
||||
%% and it stops all modules.
|
||||
-spec stop_module_keep_config(binary(), atom()) -> error | ok.
|
||||
|
||||
stop_module_keep_config(Host, Module) ->
|
||||
case catch Module:stop(Host) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p", [Reason]),
|
||||
error;
|
||||
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), error;
|
||||
{wait, ProcList} when is_list(ProcList) ->
|
||||
lists:foreach(fun wait_for_process/1, ProcList),
|
||||
ets:delete(ejabberd_modules, {Module, Host}),
|
||||
@ -117,9 +121,7 @@ stop_module_keep_config(Host, Module) ->
|
||||
wait_for_process(Process),
|
||||
ets:delete(ejabberd_modules, {Module, Host}),
|
||||
ok;
|
||||
_ ->
|
||||
ets:delete(ejabberd_modules, {Module, Host}),
|
||||
ok
|
||||
_ -> ets:delete(ejabberd_modules, {Module, Host}), ok
|
||||
end.
|
||||
|
||||
wait_for_process(Process) ->
|
||||
@ -128,8 +130,7 @@ wait_for_process(Process) ->
|
||||
|
||||
wait_for_stop(Process, MonitorReference) ->
|
||||
receive
|
||||
{'DOWN', MonitorReference, _Type, _Object, _Info} ->
|
||||
ok
|
||||
{'DOWN', MonitorReference, _Type, _Object, _Info} -> ok
|
||||
after 5000 ->
|
||||
catch exit(whereis(Process), kill),
|
||||
wait_for_stop1(MonitorReference)
|
||||
@ -137,38 +138,37 @@ wait_for_stop(Process, MonitorReference) ->
|
||||
|
||||
wait_for_stop1(MonitorReference) ->
|
||||
receive
|
||||
{'DOWN', MonitorReference, _Type, _Object, _Info} ->
|
||||
ok
|
||||
after 5000 ->
|
||||
ok
|
||||
{'DOWN', MonitorReference, _Type, _Object, _Info} -> ok
|
||||
after 5000 -> ok
|
||||
end.
|
||||
|
||||
get_opt(Opt, Opts) ->
|
||||
case lists:keysearch(Opt, 1, Opts) of
|
||||
false ->
|
||||
% TODO: replace with more appropriate function
|
||||
throw({undefined_option, Opt});
|
||||
{value, {_, Val}} ->
|
||||
Val
|
||||
end.
|
||||
-type check_fun() :: fun((any()) -> any()) | {module(), atom()}.
|
||||
|
||||
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
|
||||
false ->
|
||||
Default;
|
||||
{value, {_, Val}} ->
|
||||
Val
|
||||
ejabberd_config:prepare_opt_val(Opt, Val, F, Default)
|
||||
end.
|
||||
|
||||
get_module_opt(global, Module, Opt, Default) ->
|
||||
Hosts = ?MYHOSTS,
|
||||
[Value | Values] = lists:map(
|
||||
fun(Host) ->
|
||||
get_module_opt(Host, Module, Opt, Default)
|
||||
-spec get_module_opt(global | binary(), atom(), atom(), check_fun(), any()) -> any().
|
||||
|
||||
get_module_opt(global, Module, Opt, F, Default) ->
|
||||
Hosts = (?MYHOSTS),
|
||||
[Value | Values] = lists:map(fun (Host) ->
|
||||
get_module_opt(Host, Module, Opt,
|
||||
F, Default)
|
||||
end,
|
||||
Hosts),
|
||||
Same_all = lists:all(
|
||||
fun(Other_value) ->
|
||||
Same_all = lists:all(fun (Other_value) ->
|
||||
Other_value == Value
|
||||
end,
|
||||
Values),
|
||||
@ -176,76 +176,90 @@ get_module_opt(global, Module, Opt, Default) ->
|
||||
true -> Value;
|
||||
false -> Default
|
||||
end;
|
||||
|
||||
get_module_opt(Host, Module, Opt, Default) ->
|
||||
get_module_opt(Host, Module, Opt, F, Default) ->
|
||||
OptsList = ets:lookup(ejabberd_modules, {Module, Host}),
|
||||
case OptsList of
|
||||
[] ->
|
||||
Default;
|
||||
[] -> Default;
|
||||
[#ejabberd_module{opts = Opts} | _] ->
|
||||
get_opt(Opt, Opts, Default)
|
||||
get_opt(Opt, Opts, F, Default)
|
||||
end.
|
||||
|
||||
-spec get_module_opt_host(global | binary(), atom(), binary()) -> binary().
|
||||
|
||||
get_module_opt_host(Host, Module, Default) ->
|
||||
Val = get_module_opt(Host, Module, host, Default),
|
||||
ejabberd_regexp:greplace(Val, "@HOST@", Host).
|
||||
Val = get_module_opt(Host, Module, 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) ->
|
||||
Val = get_opt(host, Opts, Default),
|
||||
ejabberd_regexp:greplace(Val, "@HOST@", Host).
|
||||
Val = get_opt(host, Opts, fun iolist_to_binary/1, Default),
|
||||
ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
|
||||
|
||||
-spec db_type(opts()) -> odbc | mnesia.
|
||||
|
||||
db_type(Opts) ->
|
||||
case get_opt(db_type, Opts, mnesia) of
|
||||
odbc -> odbc;
|
||||
_ -> mnesia
|
||||
end.
|
||||
get_opt(db_type, Opts,
|
||||
fun(odbc) -> odbc;
|
||||
(internal) -> mnesia;
|
||||
(mnesia) -> mnesia end,
|
||||
mnesia).
|
||||
|
||||
-spec db_type(binary(), atom()) -> odbc | mnesia.
|
||||
|
||||
db_type(Host, Module) ->
|
||||
case get_module_opt(Host, Module, db_type, mnesia) of
|
||||
odbc -> odbc;
|
||||
_ -> mnesia
|
||||
end.
|
||||
get_module_opt(Host, Module, db_type,
|
||||
fun(odbc) -> odbc;
|
||||
(internal) -> mnesia;
|
||||
(mnesia) -> mnesia end,
|
||||
mnesia).
|
||||
|
||||
-spec loaded_modules(binary()) -> [atom()].
|
||||
|
||||
loaded_modules(Host) ->
|
||||
ets:select(ejabberd_modules,
|
||||
[{#ejabberd_module{_ = '_', module_host = {'$1', Host}},
|
||||
[],
|
||||
['$1']}]).
|
||||
[], ['$1']}]).
|
||||
|
||||
-spec loaded_modules_with_opts(binary()) -> [{atom(), opts()}].
|
||||
|
||||
loaded_modules_with_opts(Host) ->
|
||||
ets:select(ejabberd_modules,
|
||||
[{#ejabberd_module{_ = '_', module_host = {'$1', Host},
|
||||
opts = '$2'},
|
||||
[],
|
||||
[{{'$1', '$2'}}]}]).
|
||||
[], [{{'$1', '$2'}}]}]).
|
||||
|
||||
set_module_opts_mnesia(Host, Module, Opts) ->
|
||||
Modules = case ejabberd_config:get_local_option({modules, Host}) of
|
||||
undefined ->
|
||||
[];
|
||||
Ls ->
|
||||
Ls
|
||||
end,
|
||||
Modules = ejabberd_config:get_local_option(
|
||||
{modules, Host},
|
||||
fun(Ls) when is_list(Ls) -> Ls end,
|
||||
[]),
|
||||
Modules1 = lists:keydelete(Module, 1, Modules),
|
||||
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) ->
|
||||
Modules = case ejabberd_config:get_local_option({modules, Host}) of
|
||||
undefined ->
|
||||
[];
|
||||
Ls ->
|
||||
Ls
|
||||
end,
|
||||
Modules = ejabberd_config:get_local_option(
|
||||
{modules, Host},
|
||||
fun(Ls) when is_list(Ls) -> Ls end,
|
||||
[]),
|
||||
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) ->
|
||||
case catch gen_mod:get_opt(hosts, Opts) of
|
||||
{'EXIT', _Error1} ->
|
||||
case catch gen_mod:get_opt(host, Opts) of
|
||||
{'EXIT', _Error2} ->
|
||||
[Prefix ++ Host || Host <- ?MYHOSTS];
|
||||
case get_opt(hosts, Opts,
|
||||
fun(Hs) -> [iolist_to_binary(H) || H <- Hs] end) of
|
||||
undefined ->
|
||||
case get_opt(host, Opts,
|
||||
fun iolist_to_binary/1) of
|
||||
undefined ->
|
||||
[<<Prefix/binary, Host/binary>> || Host <- ?MYHOSTS];
|
||||
Host ->
|
||||
[Host]
|
||||
end;
|
||||
@ -253,11 +267,16 @@ get_hosts(Opts, Prefix) ->
|
||||
Hosts
|
||||
end.
|
||||
|
||||
-spec get_module_proc(binary(), {frontend, atom()} | atom()) -> atom().
|
||||
|
||||
get_module_proc(Host, {frontend, Base}) ->
|
||||
get_module_proc("frontend_" ++ Host, Base);
|
||||
get_module_proc(<<"frontend_", Host/binary>>, 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) ->
|
||||
ets:member(ejabberd_modules, {Module, Host}).
|
||||
|
||||
|
184
src/idna.erl
184
src/idna.erl
@ -25,172 +25,182 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(idna).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
%%-compile(export_all).
|
||||
-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_ucs2_to_ascii(utf8_to_ucs2(Domain)).
|
||||
|
||||
utf8_to_ucs2(S) ->
|
||||
utf8_to_ucs2(S, "").
|
||||
list_to_binary(utf8_to_ucs2(binary_to_list(S), "")).
|
||||
|
||||
utf8_to_ucs2([], R) ->
|
||||
lists:reverse(R);
|
||||
utf8_to_ucs2([C | S], R) when C < 16#80 ->
|
||||
utf8_to_ucs2([], R) -> lists:reverse(R);
|
||||
utf8_to_ucs2([C | S], R) when C < 128 ->
|
||||
utf8_to_ucs2(S, [C | R]);
|
||||
utf8_to_ucs2([C1, C2 | S], R) when C1 < 16#E0 ->
|
||||
utf8_to_ucs2(S, [((C1 band 16#1F) bsl 6) bor
|
||||
(C2 band 16#3F) | R]);
|
||||
utf8_to_ucs2([C1, C2, C3 | S], R) when C1 < 16#F0 ->
|
||||
utf8_to_ucs2(S, [((C1 band 16#0F) bsl 12) bor
|
||||
((C2 band 16#3F) bsl 6) bor
|
||||
(C3 band 16#3F) | R]).
|
||||
utf8_to_ucs2([C1, C2 | S], R) when C1 < 224 ->
|
||||
utf8_to_ucs2(S, [C1 band 31 bsl 6 bor C2 band 63 | R]);
|
||||
utf8_to_ucs2([C1, C2, C3 | S], R) when C1 < 240 ->
|
||||
utf8_to_ucs2(S,
|
||||
[C1 band 15 bsl 12 bor (C2 band 63 bsl 6) bor C3 band 63
|
||||
| R]).
|
||||
|
||||
-spec domain_ucs2_to_ascii(binary()) -> false | binary().
|
||||
|
||||
domain_ucs2_to_ascii(Domain) ->
|
||||
case catch domain_ucs2_to_ascii1(Domain) of
|
||||
{'EXIT', _Reason} ->
|
||||
false;
|
||||
Res ->
|
||||
Res
|
||||
case catch domain_ucs2_to_ascii1(binary_to_list(Domain)) of
|
||||
{'EXIT', _Reason} -> false;
|
||||
Res -> iolist_to_binary(Res)
|
||||
end.
|
||||
|
||||
domain_ucs2_to_ascii1(Domain) ->
|
||||
Parts = string:tokens(Domain, [16#002E, 16#3002, 16#FF0E, 16#FF61]),
|
||||
ASCIIParts = lists:map(fun(P) ->
|
||||
to_ascii(P)
|
||||
end, Parts),
|
||||
string:strip(lists:flatmap(fun(P) -> [$. | P] end, ASCIIParts),
|
||||
Parts = string:tokens(Domain,
|
||||
[46, 12290, 65294, 65377]),
|
||||
ASCIIParts = lists:map(fun (P) -> to_ascii(P) end,
|
||||
Parts),
|
||||
string:strip(lists:flatmap(fun (P) -> [$. | P] end,
|
||||
ASCIIParts),
|
||||
left, $.).
|
||||
|
||||
%% Domain names are already nameprep'ed in ejabberd, so we skiping this step
|
||||
to_ascii(Name) ->
|
||||
false = lists:any(
|
||||
fun(C) when
|
||||
( 0 =< C) and (C =< 16#2C) or
|
||||
(16#2E =< C) and (C =< 16#2F) or
|
||||
(16#3A =< C) and (C =< 16#40) or
|
||||
(16#5B =< C) and (C =< 16#60) or
|
||||
(16#7B =< C) and (C =< 16#7F) ->
|
||||
false = lists:any(fun (C)
|
||||
when (0 =< C) and (C =< 44) or
|
||||
(46 =< C) and (C =< 47)
|
||||
or (58 =< C) and (C =< 64)
|
||||
or (91 =< C) and (C =< 96)
|
||||
or (123 =< C) and (C =< 127) ->
|
||||
true;
|
||||
(_) ->
|
||||
false
|
||||
end, Name),
|
||||
case Name of
|
||||
[H | _] when H /= $- ->
|
||||
true = lists:last(Name) /= $-
|
||||
(_) -> false
|
||||
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 = case Name of
|
||||
"xn--" ++ _ -> false;
|
||||
_ -> true
|
||||
end,
|
||||
"xn--" ++ punycode_encode(Name);
|
||||
false ->
|
||||
Name
|
||||
false -> Name
|
||||
end,
|
||||
L = length(ASCIIName),
|
||||
true = (1 =< L) and (L =< 63),
|
||||
ASCIIName.
|
||||
|
||||
|
||||
%%% PUNYCODE (RFC3492)
|
||||
|
||||
-define(BASE, 36).
|
||||
|
||||
-define(TMIN, 1).
|
||||
|
||||
-define(TMAX, 26).
|
||||
|
||||
-define(SKEW, 38).
|
||||
|
||||
-define(DAMP, 700).
|
||||
|
||||
-define(INITIAL_BIAS, 72).
|
||||
|
||||
-define(INITIAL_N, 128).
|
||||
|
||||
punycode_encode(Input) ->
|
||||
N = ?INITIAL_N,
|
||||
N = (?INITIAL_N),
|
||||
Delta = 0,
|
||||
Bias = ?INITIAL_BIAS,
|
||||
Basic = lists:filter(fun(C) -> C =< 16#7f end, Input),
|
||||
NonBasic = lists:filter(fun(C) -> C > 16#7f end, Input),
|
||||
Bias = (?INITIAL_BIAS),
|
||||
Basic = lists:filter(fun (C) -> C =< 127 end, Input),
|
||||
NonBasic = lists:filter(fun (C) -> C > 127 end, Input),
|
||||
L = length(Input),
|
||||
B = length(Basic),
|
||||
SNonBasic = lists:usort(NonBasic),
|
||||
Output1 = if
|
||||
B > 0 -> Basic ++ "-";
|
||||
Output1 = if B > 0 -> Basic ++ "-";
|
||||
true -> ""
|
||||
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.
|
||||
|
||||
|
||||
punycode_encode1(Input, [M | SNonBasic], B, H, L, N, Delta, Bias, Out)
|
||||
punycode_encode1(Input, [M | SNonBasic], B, H, L, N,
|
||||
Delta, Bias, Out)
|
||||
when H < L ->
|
||||
Delta1 = Delta + (M - N) * (H + 1),
|
||||
% let n = m
|
||||
{NewDelta, NewBias, NewH, NewOut} =
|
||||
lists:foldl(
|
||||
fun(C, {ADelta, ABias, AH, AOut}) ->
|
||||
if
|
||||
C < M ->
|
||||
{ADelta + 1, ABias, AH, AOut};
|
||||
{NewDelta, NewBias, NewH, NewOut} = lists:foldl(fun (C,
|
||||
{ADelta, ABias, AH,
|
||||
AOut}) ->
|
||||
if C < M ->
|
||||
{ADelta + 1,
|
||||
ABias, AH,
|
||||
AOut};
|
||||
C == M ->
|
||||
NewOut = punycode_encode_delta(ADelta, ABias, AOut),
|
||||
NewBias = adapt(ADelta, H + 1, H == B),
|
||||
{0, NewBias, AH + 1, NewOut};
|
||||
NewOut =
|
||||
punycode_encode_delta(ADelta,
|
||||
ABias,
|
||||
AOut),
|
||||
NewBias =
|
||||
adapt(ADelta,
|
||||
H +
|
||||
1,
|
||||
H
|
||||
==
|
||||
B),
|
||||
{0, NewBias,
|
||||
AH + 1,
|
||||
NewOut};
|
||||
true ->
|
||||
{ADelta, ABias, AH, AOut}
|
||||
{ADelta,
|
||||
ABias, AH,
|
||||
AOut}
|
||||
end
|
||||
end, {Delta1, Bias, H, Out}, Input),
|
||||
punycode_encode1(
|
||||
Input, SNonBasic, B, NewH, L, M + 1, NewDelta + 1, NewBias, NewOut);
|
||||
|
||||
punycode_encode1(_Input, _SNonBasic, _B, _H, _L, _N, _Delta, _Bias, Out) ->
|
||||
end,
|
||||
{Delta1, Bias, H, Out},
|
||||
Input),
|
||||
punycode_encode1(Input, SNonBasic, B, NewH, L, M + 1,
|
||||
NewDelta + 1, NewBias, NewOut);
|
||||
punycode_encode1(_Input, _SNonBasic, _B, _H, _L, _N,
|
||||
_Delta, _Bias, Out) ->
|
||||
lists:reverse(Out).
|
||||
|
||||
|
||||
punycode_encode_delta(Delta, Bias, Out) ->
|
||||
punycode_encode_delta(Delta, Bias, Out, ?BASE).
|
||||
|
||||
punycode_encode_delta(Delta, Bias, Out, K) ->
|
||||
T = if
|
||||
K =< Bias -> ?TMIN;
|
||||
K >= Bias + ?TMAX -> ?TMAX;
|
||||
T = if K =< Bias -> ?TMIN;
|
||||
K >= Bias + (?TMAX) -> ?TMAX;
|
||||
true -> K - Bias
|
||||
end,
|
||||
if
|
||||
Delta < T ->
|
||||
[codepoint(Delta) | Out];
|
||||
if Delta < T -> [codepoint(Delta) | Out];
|
||||
true ->
|
||||
C = T + ((Delta - T) rem (?BASE - T)),
|
||||
punycode_encode_delta((Delta - T) div (?BASE - T), Bias,
|
||||
[codepoint(C) | Out], K + ?BASE)
|
||||
C = T + (Delta - T) rem ((?BASE) - T),
|
||||
punycode_encode_delta((Delta - T) div ((?BASE) - T),
|
||||
Bias, [codepoint(C) | Out], K + (?BASE))
|
||||
end.
|
||||
|
||||
|
||||
adapt(Delta, NumPoints, FirstTime) ->
|
||||
Delta1 = if
|
||||
FirstTime -> Delta div ?DAMP;
|
||||
Delta1 = if FirstTime -> Delta div (?DAMP);
|
||||
true -> Delta div 2
|
||||
end,
|
||||
Delta2 = Delta1 + (Delta1 div NumPoints),
|
||||
Delta2 = Delta1 + Delta1 div NumPoints,
|
||||
adapt1(Delta2, 0).
|
||||
|
||||
adapt1(Delta, K) ->
|
||||
if
|
||||
Delta > ((?BASE - ?TMIN) * ?TMAX) div 2 ->
|
||||
adapt1(Delta div (?BASE - ?TMIN), K + ?BASE);
|
||||
if Delta > ((?BASE) - (?TMIN)) * (?TMAX) div 2 ->
|
||||
adapt1(Delta div ((?BASE) - (?TMIN)), K + (?BASE));
|
||||
true ->
|
||||
K + (((?BASE - ?TMIN + 1) * Delta) div (Delta + ?SKEW))
|
||||
K +
|
||||
((?BASE) - (?TMIN) + 1) * Delta div (Delta + (?SKEW))
|
||||
end.
|
||||
|
||||
|
||||
codepoint(C) ->
|
||||
if
|
||||
(0 =< C) and (C =< 25) ->
|
||||
C + 97;
|
||||
(26 =< C) and (C =< 35) ->
|
||||
C + 22
|
||||
if (0 =< C) and (C =< 25) -> C + 97;
|
||||
(26 =< C) and (C =< 35) -> C + 22
|
||||
end.
|
||||
|
152
src/jd2ejd.erl
152
src/jd2ejd.erl
@ -25,17 +25,16 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(jd2ejd).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
%% External exports
|
||||
-export([import_file/1,
|
||||
import_dir/1]).
|
||||
-export([import_file/1, import_dir/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
|
||||
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
@ -43,21 +42,21 @@
|
||||
import_file(File) ->
|
||||
User = filename:rootname(filename:basename(File)),
|
||||
Server = filename:basename(filename:dirname(File)),
|
||||
case (jlib:nodeprep(User) /= error) andalso
|
||||
(jlib:nameprep(Server) /= error) of
|
||||
case jlib:nodeprep(User) /= error andalso
|
||||
jlib:nameprep(Server) /= error
|
||||
of
|
||||
true ->
|
||||
case file:read_file(File) of
|
||||
{ok, Text} ->
|
||||
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
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG(
|
||||
"Error while processing file \"~s\": ~p~n",
|
||||
?ERROR_MSG("Error while processing file \"~s\": "
|
||||
"~p~n",
|
||||
[File, Reason]),
|
||||
{error, Reason};
|
||||
_ ->
|
||||
ok
|
||||
_ -> ok
|
||||
end;
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG("Can't parse file \"~s\": ~p~n",
|
||||
@ -65,118 +64,113 @@ import_file(File) ->
|
||||
{error, Reason}
|
||||
end;
|
||||
{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}
|
||||
end;
|
||||
false ->
|
||||
?ERROR_MSG("Illegal user/server name in file \"~s\"~n", [File]),
|
||||
{error, "illegal user/server"}
|
||||
?ERROR_MSG("Illegal user/server name in file \"~s\"~n",
|
||||
[File]),
|
||||
{error, <<"illegal user/server">>}
|
||||
end.
|
||||
|
||||
|
||||
import_dir(Dir) ->
|
||||
{ok, Files} = file:list_dir(Dir),
|
||||
MsgFiles = lists:filter(
|
||||
fun(FN) ->
|
||||
case string:len(FN) > 4 of
|
||||
MsgFiles = lists:filter(fun (FN) ->
|
||||
case length(FN) > 4 of
|
||||
true ->
|
||||
string:substr(FN,
|
||||
string:len(FN) - 3) == ".xml";
|
||||
_ ->
|
||||
false
|
||||
string:substr(FN, length(FN) - 3) ==
|
||||
".xml";
|
||||
_ -> false
|
||||
end
|
||||
end, Files),
|
||||
lists:foldl(
|
||||
fun(FN, A) ->
|
||||
end,
|
||||
Files),
|
||||
lists:foldl(fun (FN, A) ->
|
||||
Res = import_file(filename:join([Dir, FN])),
|
||||
case {A, Res} of
|
||||
{ok, ok} -> ok;
|
||||
{ok, _} -> {error, "see ejabberd log for details"};
|
||||
{ok, _} ->
|
||||
{error, <<"see ejabberd log for details">>};
|
||||
_ -> A
|
||||
end
|
||||
end, ok, MsgFiles).
|
||||
end,
|
||||
ok, MsgFiles).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
process_xdb(User, Server, {xmlelement, Name, _Attrs, Els}) ->
|
||||
process_xdb(User, Server,
|
||||
#xmlel{name = Name, children = Els}) ->
|
||||
case Name of
|
||||
"xdb" ->
|
||||
lists:foreach(
|
||||
fun(El) ->
|
||||
xdb_data(User, Server, El)
|
||||
end, Els);
|
||||
_ ->
|
||||
ok
|
||||
<<"xdb">> ->
|
||||
lists:foreach(fun (El) -> xdb_data(User, Server, El)
|
||||
end,
|
||||
Els);
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
|
||||
xdb_data(_User, _Server, {xmlcdata, _CData}) ->
|
||||
ok;
|
||||
xdb_data(User, Server, {xmlelement, _Name, Attrs, _Els} = El) ->
|
||||
From = jlib:make_jid(User, Server, ""),
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
xdb_data(_User, _Server, {xmlcdata, _CData}) -> ok;
|
||||
xdb_data(User, Server, #xmlel{attrs = Attrs} = El) ->
|
||||
From = jlib:make_jid(User, Server, <<"">>),
|
||||
case xml:get_attr_s(<<"xmlns">>, Attrs) of
|
||||
?NS_AUTH ->
|
||||
Password = xml:get_tag_cdata(El),
|
||||
ejabberd_auth:set_password(User, Server, Password),
|
||||
ok;
|
||||
?NS_ROSTER ->
|
||||
catch mod_roster:set_items(User, Server, El),
|
||||
ok;
|
||||
catch mod_roster:set_items(User, Server, El), ok;
|
||||
?NS_LAST ->
|
||||
TimeStamp = xml:get_attr_s("last", Attrs),
|
||||
TimeStamp = xml:get_attr_s(<<"last">>, Attrs),
|
||||
Status = xml:get_tag_cdata(El),
|
||||
catch mod_last:store_last_info(
|
||||
User,
|
||||
Server,
|
||||
list_to_integer(TimeStamp),
|
||||
catch mod_last:store_last_info(User, Server,
|
||||
jlib:binary_to_integer(TimeStamp),
|
||||
Status),
|
||||
ok;
|
||||
?NS_VCARD ->
|
||||
catch mod_vcard:process_sm_iq(
|
||||
From,
|
||||
jlib:make_jid("", Server, ""),
|
||||
#iq{type = set, xmlns = ?NS_VCARD, sub_el = El}),
|
||||
ok;
|
||||
"jabber:x:offline" ->
|
||||
process_offline(Server, From, El),
|
||||
catch mod_vcard:process_sm_iq(From,
|
||||
jlib:make_jid(<<"">>, Server, <<"">>),
|
||||
#iq{type = set, xmlns = ?NS_VCARD,
|
||||
sub_el = El}),
|
||||
ok;
|
||||
<<"jabber:x:offline">> ->
|
||||
process_offline(Server, From, El), ok;
|
||||
XMLNS ->
|
||||
case xml:get_attr_s("j_private_flag", Attrs) of
|
||||
"1" ->
|
||||
catch mod_private:process_sm_iq(
|
||||
From,
|
||||
jlib:make_jid("", Server, ""),
|
||||
#iq{type = set, xmlns = ?NS_PRIVATE,
|
||||
sub_el = {xmlelement, "query", [],
|
||||
[jlib:remove_attr(
|
||||
"j_private_flag",
|
||||
jlib:remove_attr("xdbns", El))]}});
|
||||
case xml:get_attr_s(<<"j_private_flag">>, Attrs) of
|
||||
<<"1">> ->
|
||||
catch mod_private:process_sm_iq(From,
|
||||
jlib:make_jid(<<"">>, Server,
|
||||
<<"">>),
|
||||
#iq{type = set,
|
||||
xmlns = ?NS_PRIVATE,
|
||||
sub_el =
|
||||
#xmlel{name =
|
||||
<<"query">>,
|
||||
attrs = [],
|
||||
children =
|
||||
[jlib:remove_attr(<<"j_private_flag">>,
|
||||
jlib:remove_attr(<<"xdbns">>,
|
||||
El))]}});
|
||||
_ ->
|
||||
?DEBUG("jd2ejd: Unknown namespace \"~s\"~n", [XMLNS])
|
||||
end,
|
||||
ok
|
||||
end.
|
||||
|
||||
|
||||
process_offline(Server, To, {xmlelement, _, _, Els}) ->
|
||||
process_offline(Server, To, #xmlel{children = Els}) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
lists:foreach(fun({xmlelement, _, Attrs, _} = El) ->
|
||||
FromS = xml:get_attr_s("from", Attrs),
|
||||
lists:foreach(fun (#xmlel{attrs = Attrs} = El) ->
|
||||
FromS = xml:get_attr_s(<<"from">>, Attrs),
|
||||
From = case FromS of
|
||||
"" ->
|
||||
jlib:make_jid("", Server, "");
|
||||
_ ->
|
||||
jlib:string_to_jid(FromS)
|
||||
<<"">> ->
|
||||
jlib:make_jid(<<"">>, Server, <<"">>);
|
||||
_ -> jlib:string_to_jid(FromS)
|
||||
end,
|
||||
case From of
|
||||
error ->
|
||||
ok;
|
||||
error -> ok;
|
||||
_ ->
|
||||
ejabberd_hooks:run(offline_message_hook,
|
||||
LServer,
|
||||
[From, To, El])
|
||||
LServer, [From, To, El])
|
||||
end
|
||||
end, Els).
|
||||
|
||||
end,
|
||||
Els).
|
||||
|
984
src/jlib.erl
984
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_EJABBERD_CONFIG, "ejabberd:config").
|
||||
-define(NS_DISCO_ITEMS,
|
||||
<<"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_STREAMS, "urn:ietf:params:xml:ns:xmpp-streams").
|
||||
-define(NS_VCARD, <<"vcard-temp">>).
|
||||
|
||||
-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_VCARD_UPDATE, <<"vcard-temp:x:update">>).
|
||||
|
||||
-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_AUTH, <<"jabber:iq:auth">>).
|
||||
|
||||
-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_SHIM, "http://jabber.org/protocol/shim").
|
||||
-define(NS_ADDRESS, "http://jabber.org/protocol/address").
|
||||
-define(NS_REGISTER, <<"jabber:iq:register">>).
|
||||
|
||||
%% CAPTCHA related NSes.
|
||||
-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").
|
||||
-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">>).
|
||||
|
||||
-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),
|
||||
{xmlelement, "error",
|
||||
[{"code", Code}, {"type", Type}],
|
||||
[{xmlelement, Condition, [{"xmlns", ?NS_STANZAS}], []}]}).
|
||||
#xmlel{name = <<"error">>,
|
||||
attrs = [{<<"code">>, Code}, {<<"type">>, Type}],
|
||||
children =
|
||||
[#xmlel{name = Condition,
|
||||
attrs = [{<<"xmlns">>, ?NS_STANZAS}],
|
||||
children = []}]}).
|
||||
|
||||
-define(ERR_BAD_FORMAT,
|
||||
?STANZA_ERROR("406", "modify", "bad-format")).
|
||||
?STANZA_ERROR(<<"406">>, <<"modify">>,
|
||||
<<"bad-format">>)).
|
||||
|
||||
-define(ERR_BAD_REQUEST,
|
||||
?STANZA_ERROR("400", "modify", "bad-request")).
|
||||
?STANZA_ERROR(<<"400">>, <<"modify">>,
|
||||
<<"bad-request">>)).
|
||||
|
||||
-define(ERR_CONFLICT,
|
||||
?STANZA_ERROR("409", "cancel", "conflict")).
|
||||
?STANZA_ERROR(<<"409">>, <<"cancel">>, <<"conflict">>)).
|
||||
|
||||
-define(ERR_FEATURE_NOT_IMPLEMENTED,
|
||||
?STANZA_ERROR("501", "cancel", "feature-not-implemented")).
|
||||
?STANZA_ERROR(<<"501">>, <<"cancel">>,
|
||||
<<"feature-not-implemented">>)).
|
||||
|
||||
-define(ERR_FORBIDDEN,
|
||||
?STANZA_ERROR("403", "auth", "forbidden")).
|
||||
?STANZA_ERROR(<<"403">>, <<"auth">>, <<"forbidden">>)).
|
||||
|
||||
-define(ERR_GONE,
|
||||
?STANZA_ERROR("302", "modify", "gone")).
|
||||
?STANZA_ERROR(<<"302">>, <<"modify">>, <<"gone">>)).
|
||||
|
||||
-define(ERR_INTERNAL_SERVER_ERROR,
|
||||
?STANZA_ERROR("500", "wait", "internal-server-error")).
|
||||
?STANZA_ERROR(<<"500">>, <<"wait">>,
|
||||
<<"internal-server-error">>)).
|
||||
|
||||
-define(ERR_ITEM_NOT_FOUND,
|
||||
?STANZA_ERROR("404", "cancel", "item-not-found")).
|
||||
?STANZA_ERROR(<<"404">>, <<"cancel">>,
|
||||
<<"item-not-found">>)).
|
||||
|
||||
-define(ERR_JID_MALFORMED,
|
||||
?STANZA_ERROR("400", "modify", "jid-malformed")).
|
||||
?STANZA_ERROR(<<"400">>, <<"modify">>,
|
||||
<<"jid-malformed">>)).
|
||||
|
||||
-define(ERR_NOT_ACCEPTABLE,
|
||||
?STANZA_ERROR("406", "modify", "not-acceptable")).
|
||||
?STANZA_ERROR(<<"406">>, <<"modify">>,
|
||||
<<"not-acceptable">>)).
|
||||
|
||||
-define(ERR_NOT_ALLOWED,
|
||||
?STANZA_ERROR("405", "cancel", "not-allowed")).
|
||||
?STANZA_ERROR(<<"405">>, <<"cancel">>,
|
||||
<<"not-allowed">>)).
|
||||
|
||||
-define(ERR_NOT_AUTHORIZED,
|
||||
?STANZA_ERROR("401", "auth", "not-authorized")).
|
||||
?STANZA_ERROR(<<"401">>, <<"auth">>,
|
||||
<<"not-authorized">>)).
|
||||
|
||||
-define(ERR_PAYMENT_REQUIRED,
|
||||
?STANZA_ERROR("402", "auth", "payment-required")).
|
||||
?STANZA_ERROR(<<"402">>, <<"auth">>,
|
||||
<<"payment-required">>)).
|
||||
|
||||
-define(ERR_RECIPIENT_UNAVAILABLE,
|
||||
?STANZA_ERROR("404", "wait", "recipient-unavailable")).
|
||||
?STANZA_ERROR(<<"404">>, <<"wait">>,
|
||||
<<"recipient-unavailable">>)).
|
||||
|
||||
-define(ERR_REDIRECT,
|
||||
?STANZA_ERROR("302", "modify", "redirect")).
|
||||
?STANZA_ERROR(<<"302">>, <<"modify">>, <<"redirect">>)).
|
||||
|
||||
-define(ERR_REGISTRATION_REQUIRED,
|
||||
?STANZA_ERROR("407", "auth", "registration-required")).
|
||||
?STANZA_ERROR(<<"407">>, <<"auth">>,
|
||||
<<"registration-required">>)).
|
||||
|
||||
-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,
|
||||
?STANZA_ERROR("504", "wait", "remote-server-timeout")).
|
||||
?STANZA_ERROR(<<"504">>, <<"wait">>,
|
||||
<<"remote-server-timeout">>)).
|
||||
|
||||
-define(ERR_RESOURCE_CONSTRAINT,
|
||||
?STANZA_ERROR("500", "wait", "resource-constraint")).
|
||||
?STANZA_ERROR(<<"500">>, <<"wait">>,
|
||||
<<"resource-constraint">>)).
|
||||
|
||||
-define(ERR_SERVICE_UNAVAILABLE,
|
||||
?STANZA_ERROR("503", "cancel", "service-unavailable")).
|
||||
?STANZA_ERROR(<<"503">>, <<"cancel">>,
|
||||
<<"service-unavailable">>)).
|
||||
|
||||
-define(ERR_SUBSCRIPTION_REQUIRED,
|
||||
?STANZA_ERROR("407", "auth", "subscription-required")).
|
||||
?STANZA_ERROR(<<"407">>, <<"auth">>,
|
||||
<<"subscription-required">>)).
|
||||
|
||||
-define(ERR_UNEXPECTED_REQUEST,
|
||||
?STANZA_ERROR("400", "wait", "unexpected-request")).
|
||||
?STANZA_ERROR(<<"400">>, <<"wait">>,
|
||||
<<"unexpected-request">>)).
|
||||
|
||||
-define(ERR_UNEXPECTED_REQUEST_CANCEL,
|
||||
?STANZA_ERROR("401", "cancel", "unexpected-request")).
|
||||
?STANZA_ERROR(<<"401">>, <<"cancel">>,
|
||||
<<"unexpected-request">>)).
|
||||
|
||||
%-define(ERR_,
|
||||
% ?STANZA_ERROR("", "", "")).
|
||||
|
||||
-define(STANZA_ERRORT(Code, Type, Condition, Lang, Text),
|
||||
{xmlelement, "error",
|
||||
[{"code", Code}, {"type", Type}],
|
||||
[{xmlelement, Condition, [{"xmlns", ?NS_STANZAS}], []},
|
||||
{xmlelement, "text", [{"xmlns", ?NS_STANZAS}],
|
||||
[{xmlcdata, translate:translate(Lang, Text)}]}]}).
|
||||
-define(STANZA_ERRORT(Code, Type, Condition, Lang,
|
||||
Text),
|
||||
#xmlel{name = <<"error">>,
|
||||
attrs = [{<<"code">>, Code}, {<<"type">>, Type}],
|
||||
children =
|
||||
[#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),
|
||||
?STANZA_ERRORT("406", "modify", "bad-format", Lang, Text)).
|
||||
?STANZA_ERRORT(<<"406">>, <<"modify">>,
|
||||
<<"bad-format">>, 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),
|
||||
?STANZA_ERRORT("409", "cancel", "conflict", Lang, Text)).
|
||||
?STANZA_ERRORT(<<"409">>, <<"cancel">>, <<"conflict">>,
|
||||
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),
|
||||
?STANZA_ERRORT("403", "auth", "forbidden", Lang, Text)).
|
||||
?STANZA_ERRORT(<<"403">>, <<"auth">>, <<"forbidden">>,
|
||||
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),
|
||||
?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),
|
||||
?STANZA_ERRORT("404", "cancel", "item-not-found", Lang, Text)).
|
||||
?STANZA_ERRORT(<<"404">>, <<"cancel">>,
|
||||
<<"item-not-found">>, 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),
|
||||
?STANZA_ERRORT("406", "modify", "not-acceptable", Lang, Text)).
|
||||
?STANZA_ERRORT(<<"406">>, <<"modify">>,
|
||||
<<"not-acceptable">>, 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),
|
||||
?STANZA_ERRORT("401", "auth", "not-authorized", Lang, Text)).
|
||||
?STANZA_ERRORT(<<"401">>, <<"auth">>,
|
||||
<<"not-authorized">>, 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),
|
||||
?STANZA_ERRORT("404", "wait", "recipient-unavailable", Lang, Text)).
|
||||
?STANZA_ERRORT(<<"404">>, <<"wait">>,
|
||||
<<"recipient-unavailable">>, 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),
|
||||
?STANZA_ERRORT("407", "auth", "registration-required", Lang, Text)).
|
||||
?STANZA_ERRORT(<<"407">>, <<"auth">>,
|
||||
<<"registration-required">>, 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),
|
||||
?STANZA_ERRORT("504", "wait", "remote-server-timeout", Lang, Text)).
|
||||
?STANZA_ERRORT(<<"504">>, <<"wait">>,
|
||||
<<"remote-server-timeout">>, 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),
|
||||
?STANZA_ERRORT("503", "cancel", "service-unavailable", Lang, Text)).
|
||||
?STANZA_ERRORT(<<"503">>, <<"cancel">>,
|
||||
<<"service-unavailable">>, 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),
|
||||
?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),
|
||||
?ERRT_NOT_ACCEPTABLE(Lang, "No resource provided")).
|
||||
?ERRT_NOT_ACCEPTABLE(Lang, <<"No resource provided">>)).
|
||||
|
||||
-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),
|
||||
?ERRT_CONFLICT(Lang, "Resource conflict")).
|
||||
?ERRT_CONFLICT(Lang, <<"Resource conflict">>)).
|
||||
|
||||
|
||||
-define(STREAM_ERROR(Condition),
|
||||
{xmlelement, "stream:error",
|
||||
[],
|
||||
[{xmlelement, Condition, [{"xmlns", ?NS_STREAMS}], []}]}).
|
||||
-define(STREAM_ERROR(Condition, Cdata),
|
||||
#xmlel{name = <<"stream:error">>, attrs = [],
|
||||
children =
|
||||
[#xmlel{name = Condition,
|
||||
attrs = [{<<"xmlns">>, ?NS_STREAMS}],
|
||||
children = [{xmlcdata, Cdata}]}]}).
|
||||
|
||||
-define(SERR_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("")).
|
||||
?STREAM_ERROR(<<"bad-format">>, <<"">>)).
|
||||
|
||||
-define(STREAM_ERRORT(Condition, Lang, Text),
|
||||
{xmlelement, "stream:error",
|
||||
[],
|
||||
[{xmlelement, Condition, [{"xmlns", ?NS_STREAMS}], []},
|
||||
{xmlelement, "text", [{"xml:lang", Lang}, {"xmlns", ?NS_STREAMS}],
|
||||
[{xmlcdata, translate:translate(Lang, Text)}]}]}).
|
||||
-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">>, <<"">>)).
|
||||
|
||||
-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),
|
||||
?STREAM_ERRORT("bad-format", Lang, Text)).
|
||||
?STREAM_ERRORT(<<"bad-format">>, <<"">>, 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),
|
||||
?STREAM_ERRORT("conflict", Lang, Text)).
|
||||
?STREAM_ERRORT(<<"conflict">>, <<"">>, 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),
|
||||
?STREAM_ERRORT("host-gone", Lang, Text)).
|
||||
?STREAM_ERRORT(<<"host-gone">>, <<"">>, 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),
|
||||
?STREAM_ERRORT("improper-addressing", Lang, Text)).
|
||||
?STREAM_ERRORT(<<"improper-addressing">>, <<"">>, 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),
|
||||
?STREAM_ERRORT("invalid-from", Lang, Text)).
|
||||
?STREAM_ERRORT(<<"invalid-from">>, <<"">>, 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),
|
||||
?STREAM_ERRORT("invalid-namespace", Lang, Text)).
|
||||
?STREAM_ERRORT(<<"invalid-namespace">>, <<"">>, 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),
|
||||
?STREAM_ERRORT("not-authorized", Lang, Text)).
|
||||
?STREAM_ERRORT(<<"not-authorized">>, <<"">>, 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),
|
||||
?STREAM_ERRORT("remote-connection-failed", Lang, Text)).
|
||||
?STREAM_ERRORT(<<"remote-connection-failed">>, <<"">>,
|
||||
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),
|
||||
?STREAM_ERRORT("restricted-xml", Lang, Text)).
|
||||
% TODO: include hostname or IP
|
||||
-define(SERRT_SEE_OTHER_HOST(Lang, Text),
|
||||
?STREAM_ERRORT("see-other-host", Lang, Text)).
|
||||
?STREAM_ERRORT(<<"restricted-xml">>, <<"">>, Lang,
|
||||
Text)).
|
||||
|
||||
-define(SERRT_SEE_OTHER_HOST(Host, Lang, Text),
|
||||
?STREAM_ERRORT(<<"see-other-host">>, Host, 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),
|
||||
?STREAM_ERRORT("unsupported-encoding", Lang, Text)).
|
||||
?STREAM_ERRORT(<<"unsupported-encoding">>, <<"">>, 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),
|
||||
?STREAM_ERRORT("unsupported-version", Lang, Text)).
|
||||
?STREAM_ERRORT(<<"unsupported-version">>, <<"">>, Lang,
|
||||
Text)).
|
||||
|
||||
-define(SERRT_XML_NOT_WELL_FORMED(Lang, Text),
|
||||
?STREAM_ERRORT("xml-not-well-formed", Lang, Text)).
|
||||
%-define(SERRT_(Lang, Text),
|
||||
% ?STREAM_ERRORT("", Lang, Text)).
|
||||
?STREAM_ERRORT(<<"xml-not-well-formed">>, <<"">>, Lang,
|
||||
Text)).
|
||||
|
||||
-record(jid, {user = <<"">> :: binary(),
|
||||
server = <<"">> :: binary(),
|
||||
resource = <<"">> :: binary(),
|
||||
luser = <<"">> :: binary(),
|
||||
lserver = <<"">> :: binary(),
|
||||
lresource = <<"">> :: binary()}).
|
||||
|
||||
-record(jid, {user, server, resource,
|
||||
luser, lserver, lresource}).
|
||||
-type(jid() :: #jid{}).
|
||||
|
||||
-record(iq, {id = "",
|
||||
type,
|
||||
xmlns = "",
|
||||
lang = "",
|
||||
sub_el}).
|
||||
-type(ljid() :: {binary(), binary(), binary()}).
|
||||
|
||||
-record(rsm_in, {max, direction, id, index}).
|
||||
-record(rsm_out, {count, index, first, last}).
|
||||
-record(xmlel,
|
||||
{
|
||||
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).
|
||||
|
||||
-author('henoch@dtek.chalmers.se').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2,
|
||||
stop/1,
|
||||
process_local_iq/3,
|
||||
process_sm_iq/3,
|
||||
get_local_commands/5,
|
||||
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]).
|
||||
-export([start/2, stop/1, process_local_iq/3,
|
||||
process_sm_iq/3, get_local_commands/5,
|
||||
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("jlib.hrl").
|
||||
|
||||
-include("adhoc.hrl").
|
||||
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_COMMANDS,
|
||||
?MODULE, process_local_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_features, Host, ?MODULE, get_local_features, 99),
|
||||
ejabberd_hooks:add(disco_local_items, Host, ?MODULE, get_local_commands, 99),
|
||||
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, 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).
|
||||
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, ?MODULE, process_local_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_features, Host, ?MODULE,
|
||||
get_local_features, 99),
|
||||
ejabberd_hooks:add(disco_local_items, Host, ?MODULE,
|
||||
get_local_commands, 99),
|
||||
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE,
|
||||
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) ->
|
||||
ejabberd_hooks:delete(adhoc_local_commands, Host, ?MODULE, ping_command, 100),
|
||||
ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE, ping_item, 100),
|
||||
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, get_sm_commands, 99),
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 99),
|
||||
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, get_sm_identity, 99),
|
||||
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).
|
||||
ejabberd_hooks:delete(adhoc_local_commands, Host,
|
||||
?MODULE, ping_command, 100),
|
||||
ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE,
|
||||
ping_item, 100),
|
||||
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE,
|
||||
get_sm_commands, 99),
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
|
||||
get_sm_features, 99),
|
||||
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE,
|
||||
get_sm_identity, 99),
|
||||
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) ->
|
||||
Display = gen_mod:get_module_opt(LServer, ?MODULE, report_commands_node, false),
|
||||
get_local_commands(Acc, _From,
|
||||
#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
|
||||
false ->
|
||||
Acc;
|
||||
false -> Acc;
|
||||
_ ->
|
||||
Items = case Acc of
|
||||
{result, I} -> I;
|
||||
_ -> []
|
||||
end,
|
||||
Nodes = [{xmlelement,
|
||||
"item",
|
||||
[{"jid", Server},
|
||||
{"node", ?NS_COMMANDS},
|
||||
{"name", translate:translate(Lang, "Commands")}],
|
||||
[]}],
|
||||
Nodes = [#xmlel{name = <<"item">>,
|
||||
attrs =
|
||||
[{<<"jid">>, Server}, {<<"node">>, ?NS_COMMANDS},
|
||||
{<<"name">>,
|
||||
translate:translate(Lang, <<"Commands">>)}],
|
||||
children = []}],
|
||||
{result, Items ++ Nodes}
|
||||
end;
|
||||
|
||||
get_local_commands(_Acc, From, #jid{lserver = LServer} = To, ?NS_COMMANDS, 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,
|
||||
#jid{lserver = LServer} = To, ?NS_COMMANDS, Lang) ->
|
||||
ejabberd_hooks:run_fold(adhoc_local_items, LServer,
|
||||
{result, []}, [From, To, Lang]);
|
||||
get_local_commands(_Acc, _From, _To, <<"ping">>,
|
||||
_Lang) ->
|
||||
{result, []};
|
||||
|
||||
get_local_commands(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
|
||||
%-------------------------------------------------------------------------
|
||||
|
||||
get_sm_commands(Acc, _From, #jid{lserver = LServer} = To, "", Lang) ->
|
||||
Display = gen_mod:get_module_opt(LServer, ?MODULE, report_commands_node, false),
|
||||
get_sm_commands(Acc, _From,
|
||||
#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
|
||||
false ->
|
||||
Acc;
|
||||
false -> Acc;
|
||||
_ ->
|
||||
Items = case Acc of
|
||||
{result, I} -> I;
|
||||
_ -> []
|
||||
end,
|
||||
Nodes = [{xmlelement,
|
||||
"item",
|
||||
[{"jid", jlib:jid_to_string(To)},
|
||||
{"node", ?NS_COMMANDS},
|
||||
{"name", translate:translate(Lang, "Commands")}],
|
||||
[]}],
|
||||
Nodes = [#xmlel{name = <<"item">>,
|
||||
attrs =
|
||||
[{<<"jid">>, jlib:jid_to_string(To)},
|
||||
{<<"node">>, ?NS_COMMANDS},
|
||||
{<<"name">>,
|
||||
translate:translate(Lang, <<"Commands">>)}],
|
||||
children = []}],
|
||||
{result, Items ++ Nodes}
|
||||
end;
|
||||
|
||||
get_sm_commands(_Acc, From, #jid{lserver = LServer} = To, ?NS_COMMANDS, Lang) ->
|
||||
ejabberd_hooks:run_fold(adhoc_sm_items, LServer, {result, []}, [From, To, Lang]);
|
||||
|
||||
get_sm_commands(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
get_sm_commands(_Acc, From,
|
||||
#jid{lserver = LServer} = To, ?NS_COMMANDS, Lang) ->
|
||||
ejabberd_hooks:run_fold(adhoc_sm_items, LServer,
|
||||
{result, []}, [From, To, Lang]);
|
||||
get_sm_commands(Acc, _From, _To, _Node, _Lang) -> Acc.
|
||||
|
||||
%-------------------------------------------------------------------------
|
||||
|
||||
%% On disco info request to the ad-hoc node, return automation/command-list.
|
||||
get_local_identity(Acc, _From, _To, ?NS_COMMANDS, Lang) ->
|
||||
[{xmlelement, "identity",
|
||||
[{"category", "automation"},
|
||||
{"type", "command-list"},
|
||||
{"name", translate:translate(Lang, "Commands")}], []} | Acc];
|
||||
|
||||
get_local_identity(Acc, _From, _To, "ping", Lang) ->
|
||||
[{xmlelement, "identity",
|
||||
[{"category", "automation"},
|
||||
{"type", "command-node"},
|
||||
{"name", translate:translate(Lang, "Ping")}], []} | Acc];
|
||||
|
||||
get_local_identity(Acc, _From, _To, ?NS_COMMANDS,
|
||||
Lang) ->
|
||||
[#xmlel{name = <<"identity">>,
|
||||
attrs =
|
||||
[{<<"category">>, <<"automation">>},
|
||||
{<<"type">>, <<"command-list">>},
|
||||
{<<"name">>,
|
||||
translate:translate(Lang, <<"Commands">>)}],
|
||||
children = []}
|
||||
| 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) ->
|
||||
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.
|
||||
get_sm_identity(Acc, _From, _To, ?NS_COMMANDS, Lang) ->
|
||||
[{xmlelement, "identity",
|
||||
[{"category", "automation"},
|
||||
{"type", "command-list"},
|
||||
{"name", translate:translate(Lang, "Commands")}], []} | Acc];
|
||||
|
||||
get_sm_identity(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
[#xmlel{name = <<"identity">>,
|
||||
attrs =
|
||||
[{<<"category">>, <<"automation">>},
|
||||
{<<"type">>, <<"command-list">>},
|
||||
{<<"name">>,
|
||||
translate:translate(Lang, <<"Commands">>)}],
|
||||
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
|
||||
{result, I} -> I;
|
||||
_ -> []
|
||||
end,
|
||||
{result, Feats ++ [?NS_COMMANDS]};
|
||||
|
||||
get_local_features(_Acc, _From, _To, ?NS_COMMANDS, _Lang) ->
|
||||
%% override all lesser features...
|
||||
get_local_features(_Acc, _From, _To, ?NS_COMMANDS,
|
||||
_Lang) ->
|
||||
{result, []};
|
||||
|
||||
get_local_features(_Acc, _From, _To, "ping", _Lang) ->
|
||||
%% override all lesser features...
|
||||
get_local_features(_Acc, _From, _To, <<"ping">>,
|
||||
_Lang) ->
|
||||
{result, [?NS_COMMANDS]};
|
||||
|
||||
get_local_features(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
|
||||
%-------------------------------------------------------------------------
|
||||
|
||||
get_sm_features(Acc, _From, _To, "", _Lang) ->
|
||||
get_sm_features(Acc, _From, _To, <<"">>, _Lang) ->
|
||||
Feats = case Acc of
|
||||
{result, I} -> I;
|
||||
_ -> []
|
||||
end,
|
||||
{result, Feats ++ [?NS_COMMANDS]};
|
||||
|
||||
get_sm_features(_Acc, _From, _To, ?NS_COMMANDS, _Lang) ->
|
||||
%% override all lesser features...
|
||||
get_sm_features(_Acc, _From, _To, ?NS_COMMANDS,
|
||||
_Lang) ->
|
||||
{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_adhoc_request(From, To, IQ, adhoc_local_commands).
|
||||
|
||||
process_adhoc_request(From, To, IQ,
|
||||
adhoc_local_commands).
|
||||
|
||||
process_sm_iq(From, To, IQ) ->
|
||||
process_adhoc_request(From, To, IQ, adhoc_sm_commands).
|
||||
|
||||
|
||||
process_adhoc_request(From, To, #iq{sub_el = SubEl} = IQ, Hook) ->
|
||||
process_adhoc_request(From, To,
|
||||
#iq{sub_el = SubEl} = IQ, Hook) ->
|
||||
?DEBUG("About to parse ~p...", [IQ]),
|
||||
case adhoc:parse_request(IQ) of
|
||||
{error, Error} ->
|
||||
@ -217,51 +239,42 @@ process_adhoc_request(From, To, #iq{sub_el = SubEl} = IQ, Hook) ->
|
||||
#adhoc_request{} = AdhocRequest ->
|
||||
Host = To#jid.lserver,
|
||||
case ejabberd_hooks:run_fold(Hook, Host, empty,
|
||||
[From, To, AdhocRequest]) of
|
||||
ignore ->
|
||||
ignore;
|
||||
[From, To, AdhocRequest])
|
||||
of
|
||||
ignore -> ignore;
|
||||
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} ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, Error]};
|
||||
Command ->
|
||||
IQ#iq{type = result, sub_el = [Command]}
|
||||
Command -> IQ#iq{type = result, sub_el = [Command]}
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
ping_item(Acc, _From, #jid{server = Server} = _To, Lang) ->
|
||||
ping_item(Acc, _From, #jid{server = Server} = _To,
|
||||
Lang) ->
|
||||
Items = case Acc of
|
||||
{result, I} ->
|
||||
I;
|
||||
_ ->
|
||||
[]
|
||||
{result, I} -> I;
|
||||
_ -> []
|
||||
end,
|
||||
Nodes = [{xmlelement, "item",
|
||||
[{"jid", Server},
|
||||
{"node", "ping"},
|
||||
{"name", translate:translate(Lang, "Ping")}],
|
||||
[]}],
|
||||
Nodes = [#xmlel{name = <<"item">>,
|
||||
attrs =
|
||||
[{<<"jid">>, Server}, {<<"node">>, <<"ping">>},
|
||||
{<<"name">>, translate:translate(Lang, <<"Ping">>)}],
|
||||
children = []}],
|
||||
{result, Items ++ Nodes}.
|
||||
|
||||
|
||||
ping_command(_Acc, _From, _To,
|
||||
#adhoc_request{lang = Lang,
|
||||
node = "ping",
|
||||
sessionid = _Sessionid,
|
||||
action = Action} = Request) ->
|
||||
if
|
||||
Action == ""; Action == "execute" ->
|
||||
adhoc:produce_response(
|
||||
Request,
|
||||
#adhoc_request{lang = Lang, node = <<"ping">>,
|
||||
sessionid = _Sessionid, action = Action} =
|
||||
Request) ->
|
||||
if Action == <<"">>; Action == <<"execute">> ->
|
||||
adhoc:produce_response(Request,
|
||||
#adhoc_response{status = completed,
|
||||
notes = [{"info", translate:translate(
|
||||
Lang,
|
||||
"Pong")}]});
|
||||
true ->
|
||||
{error, ?ERR_BAD_REQUEST}
|
||||
notes =
|
||||
[{<<"info">>,
|
||||
translate:translate(Lang,
|
||||
<<"Pong">>)}]});
|
||||
true -> {error, ?ERR_BAD_REQUEST}
|
||||
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).
|
||||
|
||||
-export([start/2, stop/1,
|
||||
process_iq/3,
|
||||
process_iq_set/4,
|
||||
process_iq_get/5]).
|
||||
-export([start/2, stop/1, process_iq/3,
|
||||
process_iq_set/4, process_iq_get/5]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
|
||||
-include("mod_privacy.hrl").
|
||||
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
ejabberd_hooks:add(privacy_iq_get, Host,
|
||||
?MODULE, process_iq_get, 40),
|
||||
ejabberd_hooks:add(privacy_iq_set, Host,
|
||||
?MODULE, process_iq_set, 40),
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
|
||||
one_queue),
|
||||
ejabberd_hooks:add(privacy_iq_get, Host, ?MODULE,
|
||||
process_iq_get, 40),
|
||||
ejabberd_hooks:add(privacy_iq_set, Host, ?MODULE,
|
||||
process_iq_set, 40),
|
||||
mod_disco:register_feature(Host, ?NS_BLOCKING),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_BLOCKING,
|
||||
?MODULE, process_iq, IQDisc).
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_BLOCKING, ?MODULE, process_iq, IQDisc).
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(privacy_iq_get, Host,
|
||||
?MODULE, process_iq_get, 40),
|
||||
ejabberd_hooks:delete(privacy_iq_set, Host,
|
||||
?MODULE, process_iq_set, 40),
|
||||
ejabberd_hooks:delete(privacy_iq_get, Host, ?MODULE,
|
||||
process_iq_get, 40),
|
||||
ejabberd_hooks:delete(privacy_iq_set, Host, ?MODULE,
|
||||
process_iq_set, 40),
|
||||
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) ->
|
||||
SubEl = IQ#iq.sub_el,
|
||||
@ -61,90 +63,73 @@ process_iq(_From, _To, IQ) ->
|
||||
|
||||
process_iq_get(_, From, _To,
|
||||
#iq{xmlns = ?NS_BLOCKING,
|
||||
sub_el = {xmlelement, "blocklist", _, _}},
|
||||
sub_el = #xmlel{name = <<"blocklist">>}},
|
||||
_) ->
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
{stop, process_blocklist_get(LUser, LServer)};
|
||||
process_iq_get(Acc, _, _, _, _) -> Acc.
|
||||
|
||||
process_iq_get(Acc, _, _, _, _) ->
|
||||
Acc.
|
||||
|
||||
process_iq_set(_, From, _To, #iq{xmlns = ?NS_BLOCKING,
|
||||
sub_el = {xmlelement, SubElName, _, SubEls}}) ->
|
||||
process_iq_set(_, From, _To,
|
||||
#iq{xmlns = ?NS_BLOCKING,
|
||||
sub_el =
|
||||
#xmlel{name = SubElName, children = SubEls}}) ->
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
Res =
|
||||
case {SubElName, xml:remove_cdata(SubEls)} of
|
||||
{"block", []} ->
|
||||
{error, ?ERR_BAD_REQUEST};
|
||||
{"block", Els} ->
|
||||
Res = case {SubElName, xml:remove_cdata(SubEls)} of
|
||||
{<<"block">>, []} -> {error, ?ERR_BAD_REQUEST};
|
||||
{<<"block">>, Els} ->
|
||||
JIDs = parse_blocklist_items(Els, []),
|
||||
process_blocklist_block(LUser, LServer, JIDs);
|
||||
{"unblock", []} ->
|
||||
{<<"unblock">>, []} ->
|
||||
process_blocklist_unblock_all(LUser, LServer);
|
||||
{"unblock", Els} ->
|
||||
{<<"unblock">>, Els} ->
|
||||
JIDs = parse_blocklist_items(Els, []),
|
||||
process_blocklist_unblock(LUser, LServer, JIDs);
|
||||
_ ->
|
||||
{error, ?ERR_BAD_REQUEST}
|
||||
_ -> {error, ?ERR_BAD_REQUEST}
|
||||
end,
|
||||
{stop, Res};
|
||||
process_iq_set(Acc, _, _, _) -> Acc.
|
||||
|
||||
process_iq_set(Acc, _, _, _) ->
|
||||
Acc.
|
||||
|
||||
list_to_blocklist_jids([], JIDs) ->
|
||||
JIDs;
|
||||
|
||||
list_to_blocklist_jids([], JIDs) -> JIDs;
|
||||
list_to_blocklist_jids([#listitem{type = jid,
|
||||
action = deny,
|
||||
value = JID} = Item | Items], JIDs) ->
|
||||
action = deny, value = JID} =
|
||||
Item
|
||||
| Items],
|
||||
JIDs) ->
|
||||
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;
|
||||
#listitem{match_iq = true,
|
||||
match_message = true,
|
||||
match_presence_in = true,
|
||||
match_presence_out = true} ->
|
||||
Match = true;
|
||||
_ ->
|
||||
Match = false
|
||||
_ -> Match = false
|
||||
end,
|
||||
if
|
||||
Match ->
|
||||
list_to_blocklist_jids(Items, [JID | JIDs]);
|
||||
true ->
|
||||
list_to_blocklist_jids(Items, JIDs)
|
||||
if Match -> list_to_blocklist_jids(Items, [JID | JIDs]);
|
||||
true -> list_to_blocklist_jids(Items, JIDs)
|
||||
end;
|
||||
|
||||
% Skip Privacy List items than cannot be mapped to Blocking items
|
||||
list_to_blocklist_jids([_ | Items], JIDs) ->
|
||||
list_to_blocklist_jids(Items, JIDs).
|
||||
|
||||
parse_blocklist_items([], JIDs) ->
|
||||
JIDs;
|
||||
|
||||
parse_blocklist_items([{xmlelement, "item", Attrs, _} | Els], JIDs) ->
|
||||
case xml:get_attr("jid", Attrs) of
|
||||
parse_blocklist_items([], JIDs) -> JIDs;
|
||||
parse_blocklist_items([#xmlel{name = <<"item">>,
|
||||
attrs = Attrs}
|
||||
| Els],
|
||||
JIDs) ->
|
||||
case xml:get_attr(<<"jid">>, Attrs) of
|
||||
{value, JID1} ->
|
||||
JID = jlib:jid_tolower(jlib:string_to_jid(JID1)),
|
||||
parse_blocklist_items(Els, [JID | JIDs]);
|
||||
false ->
|
||||
% Tolerate missing jid attribute
|
||||
parse_blocklist_items(Els, JIDs)
|
||||
false -> parse_blocklist_items(Els, JIDs)
|
||||
end;
|
||||
|
||||
parse_blocklist_items([_ | Els], JIDs) ->
|
||||
% Tolerate unknown elements
|
||||
parse_blocklist_items(Els, JIDs).
|
||||
|
||||
process_blocklist_block(LUser, LServer, JIDs) ->
|
||||
Filter = fun(List) ->
|
||||
Filter = fun (List) ->
|
||||
AlreadyBlocked = list_to_blocklist_jids(List, []),
|
||||
lists:foldr(
|
||||
fun(JID, List1) ->
|
||||
case lists:member(JID, AlreadyBlocked) of
|
||||
true ->
|
||||
List1;
|
||||
lists:foldr(fun (JID, List1) ->
|
||||
case lists:member(JID, AlreadyBlocked)
|
||||
of
|
||||
true -> List1;
|
||||
false ->
|
||||
[#listitem{type = jid,
|
||||
value = JID,
|
||||
@ -153,41 +138,38 @@ process_blocklist_block(LUser, LServer, JIDs) ->
|
||||
match_all = true}
|
||||
| List1]
|
||||
end
|
||||
end, List, JIDs)
|
||||
end,
|
||||
List, JIDs)
|
||||
end,
|
||||
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}} ->
|
||||
UserList = make_userlist(Default, List),
|
||||
broadcast_list_update(LUser, LServer, Default, UserList),
|
||||
broadcast_blocklist_event(LUser, LServer, {block, JIDs}),
|
||||
broadcast_list_update(LUser, LServer, Default,
|
||||
UserList),
|
||||
broadcast_blocklist_event(LUser, LServer,
|
||||
{block, JIDs}),
|
||||
{result, [], UserList};
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
_ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end.
|
||||
|
||||
process_blocklist_block(LUser, LServer, Filter, mnesia) ->
|
||||
F =
|
||||
fun() ->
|
||||
process_blocklist_block(LUser, LServer, Filter,
|
||||
mnesia) ->
|
||||
F = fun () ->
|
||||
case mnesia:wread({privacy, {LUser, LServer}}) of
|
||||
[] ->
|
||||
% No lists yet
|
||||
P = #privacy{us = {LUser, LServer}},
|
||||
% TODO: i18n here:
|
||||
NewDefault = "Blocked contacts",
|
||||
NewDefault = <<"Blocked contacts">>,
|
||||
NewLists1 = [],
|
||||
List = [];
|
||||
[#privacy{default = Default,
|
||||
lists = Lists} = P] ->
|
||||
[#privacy{default = Default, lists = Lists} = P] ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
% Default list exists
|
||||
NewDefault = Default,
|
||||
NewLists1 = lists:keydelete(Default, 1, Lists);
|
||||
false ->
|
||||
% No default list yet, create one
|
||||
% TODO: i18n here:
|
||||
NewDefault = "Blocked contacts",
|
||||
NewDefault = <<"Blocked contacts">>,
|
||||
NewLists1 = Lists,
|
||||
List = []
|
||||
end
|
||||
@ -200,80 +182,72 @@ process_blocklist_block(LUser, LServer, Filter, mnesia) ->
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
process_blocklist_block(LUser, LServer, Filter, odbc) ->
|
||||
F = fun() ->
|
||||
Default =
|
||||
case mod_privacy:sql_get_default_privacy_list_t(LUser) of
|
||||
{selected, ["name"], []} ->
|
||||
Name = "Blocked contacts",
|
||||
F = fun () ->
|
||||
Default = case
|
||||
mod_privacy:sql_get_default_privacy_list_t(LUser)
|
||||
of
|
||||
{selected, [<<"name">>], []} ->
|
||||
Name = <<"Blocked contacts">>,
|
||||
mod_privacy:sql_add_privacy_list(LUser, Name),
|
||||
mod_privacy:sql_set_default_privacy_list(
|
||||
LUser, Name),
|
||||
mod_privacy:sql_set_default_privacy_list(LUser,
|
||||
Name),
|
||||
Name;
|
||||
{selected, ["name"], [{Name}]} ->
|
||||
Name
|
||||
{selected, [<<"name">>], [[Name]]} -> Name
|
||||
end,
|
||||
{selected, ["id"], [{ID}]} =
|
||||
{selected, [<<"id">>], [[ID]]} =
|
||||
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,
|
||||
["t", "value", "action", "ord",
|
||||
"match_all", "match_iq", "match_message",
|
||||
"match_presence_in",
|
||||
"match_presence_out"],
|
||||
RItems = [_|_]} ->
|
||||
List = lists:map(
|
||||
fun mod_privacy:raw_to_item/1,
|
||||
RItems);
|
||||
_ ->
|
||||
List = []
|
||||
[<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
|
||||
<<"match_all">>, <<"match_iq">>, <<"match_message">>,
|
||||
<<"match_presence_in">>, <<"match_presence_out">>],
|
||||
RItems = [_ | _]} ->
|
||||
List = lists:map(fun mod_privacy:raw_to_item/1, RItems);
|
||||
_ -> List = []
|
||||
end,
|
||||
NewList = Filter(List),
|
||||
NewRItems = lists:map(
|
||||
fun mod_privacy:item_to_raw/1,
|
||||
NewRItems = lists:map(fun mod_privacy:item_to_raw/1,
|
||||
NewList),
|
||||
mod_privacy:sql_set_privacy_list(
|
||||
ID, NewRItems),
|
||||
mod_privacy:sql_set_privacy_list(ID, NewRItems),
|
||||
{ok, Default, NewList}
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
|
||||
process_blocklist_unblock_all(LUser, LServer) ->
|
||||
Filter = fun(List) ->
|
||||
lists:filter(
|
||||
fun(#listitem{action = A}) ->
|
||||
A =/= deny
|
||||
end, List)
|
||||
Filter = fun (List) ->
|
||||
lists:filter(fun (#listitem{action = A}) -> A =/= deny
|
||||
end,
|
||||
case process_blocklist_unblock_all(
|
||||
LUser, LServer, Filter, gen_mod:db_type(LServer, mod_privacy)) of
|
||||
{atomic, ok} ->
|
||||
{result, []};
|
||||
List)
|
||||
end,
|
||||
case process_blocklist_unblock_all(LUser, LServer,
|
||||
Filter,
|
||||
gen_mod:db_type(LServer, mod_privacy))
|
||||
of
|
||||
{atomic, ok} -> {result, []};
|
||||
{atomic, {ok, 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),
|
||||
{result, [], UserList};
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
_ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end.
|
||||
|
||||
process_blocklist_unblock_all(LUser, LServer, Filter, mnesia) ->
|
||||
F =
|
||||
fun() ->
|
||||
process_blocklist_unblock_all(LUser, LServer, Filter,
|
||||
mnesia) ->
|
||||
F = fun () ->
|
||||
case mnesia:read({privacy, {LUser, LServer}}) of
|
||||
[] ->
|
||||
% No lists, nothing to unblock
|
||||
ok;
|
||||
[#privacy{default = Default,
|
||||
lists = Lists} = P] ->
|
||||
[#privacy{default = Default, lists = Lists} = P] ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
% Default list, remove all deny items
|
||||
NewList = Filter(List),
|
||||
NewLists1 = lists:keydelete(Default, 1, Lists),
|
||||
NewLists = [{Default, NewList} | NewLists1],
|
||||
mnesia:write(P#privacy{lists = NewLists}),
|
||||
|
||||
{ok, Default, NewList};
|
||||
false ->
|
||||
% No default list, nothing to unblock
|
||||
@ -282,82 +256,73 @@ process_blocklist_unblock_all(LUser, LServer, Filter, mnesia) ->
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
process_blocklist_unblock_all(LUser, LServer, Filter, odbc) ->
|
||||
F = fun() ->
|
||||
case mod_privacy:sql_get_default_privacy_list_t(LUser) of
|
||||
{selected, ["name"], []} ->
|
||||
ok;
|
||||
{selected, ["name"], [{Default}]} ->
|
||||
{selected, ["id"], [{ID}]} =
|
||||
mod_privacy:sql_get_privacy_list_id_t(
|
||||
LUser, Default),
|
||||
case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of
|
||||
process_blocklist_unblock_all(LUser, LServer, Filter,
|
||||
odbc) ->
|
||||
F = fun () ->
|
||||
case mod_privacy:sql_get_default_privacy_list_t(LUser)
|
||||
of
|
||||
{selected, [<<"name">>], []} -> ok;
|
||||
{selected, [<<"name">>], [[Default]]} ->
|
||||
{selected, [<<"id">>], [[ID]]} =
|
||||
mod_privacy:sql_get_privacy_list_id_t(LUser, Default),
|
||||
case mod_privacy:sql_get_privacy_list_data_by_id_t(ID)
|
||||
of
|
||||
{selected,
|
||||
["t", "value", "action", "ord",
|
||||
"match_all", "match_iq", "match_message",
|
||||
"match_presence_in",
|
||||
"match_presence_out"],
|
||||
RItems = [_|_]} ->
|
||||
List = lists:map(
|
||||
fun mod_privacy:raw_to_item/1,
|
||||
[<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
|
||||
<<"match_all">>, <<"match_iq">>, <<"match_message">>,
|
||||
<<"match_presence_in">>, <<"match_presence_out">>],
|
||||
RItems = [_ | _]} ->
|
||||
List = lists:map(fun mod_privacy:raw_to_item/1,
|
||||
RItems),
|
||||
NewList = Filter(List),
|
||||
NewRItems = lists:map(
|
||||
fun mod_privacy:item_to_raw/1,
|
||||
NewRItems = lists:map(fun mod_privacy:item_to_raw/1,
|
||||
NewList),
|
||||
mod_privacy:sql_set_privacy_list(
|
||||
ID, NewRItems),
|
||||
mod_privacy:sql_set_privacy_list(ID, NewRItems),
|
||||
{ok, Default, NewList};
|
||||
_ ->
|
||||
ok
|
||||
_ -> ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
_ -> ok
|
||||
end
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
|
||||
process_blocklist_unblock(LUser, LServer, JIDs) ->
|
||||
Filter = fun(List) ->
|
||||
lists:filter(
|
||||
fun(#listitem{action = deny,
|
||||
type = jid,
|
||||
Filter = fun (List) ->
|
||||
lists:filter(fun (#listitem{action = deny, type = jid,
|
||||
value = JID}) ->
|
||||
not(lists:member(JID, JIDs));
|
||||
(_) ->
|
||||
true
|
||||
end, List)
|
||||
not lists:member(JID, JIDs);
|
||||
(_) -> true
|
||||
end,
|
||||
List)
|
||||
end,
|
||||
case process_blocklist_unblock(LUser, LServer, Filter,
|
||||
gen_mod:db_type(LServer, mod_privacy)) of
|
||||
{atomic, ok} ->
|
||||
{result, []};
|
||||
gen_mod:db_type(LServer, mod_privacy))
|
||||
of
|
||||
{atomic, ok} -> {result, []};
|
||||
{atomic, {ok, Default, List}} ->
|
||||
UserList = make_userlist(Default, List),
|
||||
broadcast_list_update(LUser, LServer, Default, UserList),
|
||||
broadcast_blocklist_event(LUser, LServer, {unblock, JIDs}),
|
||||
broadcast_list_update(LUser, LServer, Default,
|
||||
UserList),
|
||||
broadcast_blocklist_event(LUser, LServer,
|
||||
{unblock, JIDs}),
|
||||
{result, [], UserList};
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
_ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end.
|
||||
|
||||
process_blocklist_unblock(LUser, LServer, Filter, mnesia) ->
|
||||
F =
|
||||
fun() ->
|
||||
process_blocklist_unblock(LUser, LServer, Filter,
|
||||
mnesia) ->
|
||||
F = fun () ->
|
||||
case mnesia:read({privacy, {LUser, LServer}}) of
|
||||
[] ->
|
||||
% No lists, nothing to unblock
|
||||
ok;
|
||||
[#privacy{default = Default,
|
||||
lists = Lists} = P] ->
|
||||
[#privacy{default = Default, lists = Lists} = P] ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
% Default list, remove matching deny items
|
||||
NewList = Filter(List),
|
||||
NewLists1 = lists:keydelete(Default, 1, Lists),
|
||||
NewLists = [{Default, NewList} | NewLists1],
|
||||
mnesia:write(P#privacy{lists = NewLists}),
|
||||
|
||||
{ok, Default, NewList};
|
||||
false ->
|
||||
% No default list, nothing to unblock
|
||||
@ -366,37 +331,32 @@ process_blocklist_unblock(LUser, LServer, Filter, mnesia) ->
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
process_blocklist_unblock(LUser, LServer, Filter, odbc) ->
|
||||
F = fun() ->
|
||||
case mod_privacy:sql_get_default_privacy_list_t(LUser) of
|
||||
{selected, ["name"], []} ->
|
||||
ok;
|
||||
{selected, ["name"], [{Default}]} ->
|
||||
{selected, ["id"], [{ID}]} =
|
||||
mod_privacy:sql_get_privacy_list_id_t(
|
||||
LUser, Default),
|
||||
case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of
|
||||
process_blocklist_unblock(LUser, LServer, Filter,
|
||||
odbc) ->
|
||||
F = fun () ->
|
||||
case mod_privacy:sql_get_default_privacy_list_t(LUser)
|
||||
of
|
||||
{selected, [<<"name">>], []} -> ok;
|
||||
{selected, [<<"name">>], [[Default]]} ->
|
||||
{selected, [<<"id">>], [[ID]]} =
|
||||
mod_privacy:sql_get_privacy_list_id_t(LUser, Default),
|
||||
case mod_privacy:sql_get_privacy_list_data_by_id_t(ID)
|
||||
of
|
||||
{selected,
|
||||
["t", "value", "action", "ord",
|
||||
"match_all", "match_iq", "match_message",
|
||||
"match_presence_in",
|
||||
"match_presence_out"],
|
||||
RItems = [_|_]} ->
|
||||
List = lists:map(
|
||||
fun mod_privacy:raw_to_item/1,
|
||||
[<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
|
||||
<<"match_all">>, <<"match_iq">>, <<"match_message">>,
|
||||
<<"match_presence_in">>, <<"match_presence_out">>],
|
||||
RItems = [_ | _]} ->
|
||||
List = lists:map(fun mod_privacy:raw_to_item/1,
|
||||
RItems),
|
||||
NewList = Filter(List),
|
||||
NewRItems = lists:map(
|
||||
fun mod_privacy:item_to_raw/1,
|
||||
NewRItems = lists:map(fun mod_privacy:item_to_raw/1,
|
||||
NewList),
|
||||
mod_privacy:sql_set_privacy_list(
|
||||
ID, NewRItems),
|
||||
mod_privacy:sql_set_privacy_list(ID, NewRItems),
|
||||
{ok, Default, NewList};
|
||||
_ ->
|
||||
ok
|
||||
_ -> ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
_ -> ok
|
||||
end
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
@ -406,66 +366,65 @@ make_userlist(Name, List) ->
|
||||
#userlist{name = Name, list = List, needdb = NeedDb}.
|
||||
|
||||
broadcast_list_update(LUser, LServer, Name, UserList) ->
|
||||
ejabberd_router:route(
|
||||
jlib:make_jid(LUser, LServer, ""),
|
||||
jlib:make_jid(LUser, LServer, ""),
|
||||
{xmlelement, "broadcast", [],
|
||||
[{privacy_list, UserList, Name}]}).
|
||||
ejabberd_sm:route(jlib:make_jid(LUser, LServer,
|
||||
<<"">>),
|
||||
jlib:make_jid(LUser, LServer, <<"">>),
|
||||
{broadcast, {privacy_list, UserList, Name}}).
|
||||
|
||||
broadcast_blocklist_event(LUser, LServer, Event) ->
|
||||
JID = jlib:make_jid(LUser, LServer, ""),
|
||||
ejabberd_router:route(
|
||||
JID, JID,
|
||||
{xmlelement, "broadcast", [],
|
||||
[{blocking, Event}]}).
|
||||
JID = jlib:make_jid(LUser, LServer, <<"">>),
|
||||
ejabberd_sm:route(JID, JID,
|
||||
{broadcast, {blocking, Event}}).
|
||||
|
||||
process_blocklist_get(LUser, LServer) ->
|
||||
case process_blocklist_get(
|
||||
LUser, LServer, gen_mod:db_type(LServer, mod_privacy)) of
|
||||
error ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
case process_blocklist_get(LUser, LServer,
|
||||
gen_mod:db_type(LServer, mod_privacy))
|
||||
of
|
||||
error -> {error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
List ->
|
||||
JIDs = list_to_blocklist_jids(List, []),
|
||||
Items = lists:map(
|
||||
fun(JID) ->
|
||||
?DEBUG("JID: ~p",[JID]),
|
||||
{xmlelement, "item",
|
||||
[{"jid", jlib:jid_to_string(JID)}], []}
|
||||
end, JIDs),
|
||||
Items = lists:map(fun (JID) ->
|
||||
?DEBUG("JID: ~p", [JID]),
|
||||
#xmlel{name = <<"item">>,
|
||||
attrs =
|
||||
[{<<"jid">>,
|
||||
jlib:jid_to_string(JID)}],
|
||||
children = []}
|
||||
end,
|
||||
JIDs),
|
||||
{result,
|
||||
[{xmlelement, "blocklist", [{"xmlns", ?NS_BLOCKING}],
|
||||
Items}]}
|
||||
[#xmlel{name = <<"blocklist">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_BLOCKING}],
|
||||
children = Items}]}
|
||||
end.
|
||||
|
||||
process_blocklist_get(LUser, LServer, mnesia) ->
|
||||
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
|
||||
{'EXIT', _Reason} ->
|
||||
error;
|
||||
[] ->
|
||||
[];
|
||||
case catch mnesia:dirty_read(privacy, {LUser, LServer})
|
||||
of
|
||||
{'EXIT', _Reason} -> error;
|
||||
[] -> [];
|
||||
[#privacy{default = Default, lists = Lists}] ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
List;
|
||||
_ ->
|
||||
[]
|
||||
{value, {_, List}} -> List;
|
||||
_ -> []
|
||||
end
|
||||
end;
|
||||
process_blocklist_get(LUser, LServer, odbc) ->
|
||||
case catch mod_privacy:sql_get_default_privacy_list(LUser, LServer) of
|
||||
{selected, ["name"], []} ->
|
||||
[];
|
||||
{selected, ["name"], [{Default}]} ->
|
||||
case catch mod_privacy:sql_get_privacy_list_data(
|
||||
LUser, LServer, Default) of
|
||||
{selected, ["t", "value", "action", "ord", "match_all",
|
||||
"match_iq", "match_message",
|
||||
"match_presence_in", "match_presence_out"],
|
||||
case catch
|
||||
mod_privacy:sql_get_default_privacy_list(LUser, LServer)
|
||||
of
|
||||
{selected, [<<"name">>], []} -> [];
|
||||
{selected, [<<"name">>], [[Default]]} ->
|
||||
case catch mod_privacy:sql_get_privacy_list_data(LUser,
|
||||
LServer, Default)
|
||||
of
|
||||
{selected,
|
||||
[<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
|
||||
<<"match_all">>, <<"match_iq">>, <<"match_message">>,
|
||||
<<"match_presence_in">>, <<"match_presence_out">>],
|
||||
RItems} ->
|
||||
lists:map(fun mod_privacy:raw_to_item/1, RItems);
|
||||
{'EXIT', _} ->
|
||||
error
|
||||
{'EXIT', _} -> error
|
||||
end;
|
||||
{'EXIT', _} ->
|
||||
error
|
||||
{'EXIT', _} -> error
|
||||
end.
|
||||
|
634
src/mod_caps.erl
634
src/mod_caps.erl
@ -27,30 +27,23 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_caps).
|
||||
|
||||
-author('henoch@dtek.chalmers.se').
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([read_caps/1,
|
||||
caps_stream_features/2,
|
||||
disco_features/5,
|
||||
disco_identity/5,
|
||||
disco_info/5,
|
||||
-export([read_caps/1, caps_stream_features/2,
|
||||
disco_features/5, disco_identity/5, disco_info/5,
|
||||
get_features/1]).
|
||||
|
||||
%% gen_mod callbacks
|
||||
-export([start/2, start_link/2,
|
||||
stop/1]).
|
||||
-export([start/2, start_link/2, stop/1]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1,
|
||||
handle_info/2,
|
||||
handle_call/3,
|
||||
handle_cast/2,
|
||||
terminate/2,
|
||||
code_change/3
|
||||
]).
|
||||
-export([init/1, handle_info/2, handle_call/3,
|
||||
handle_cast/2, terminate/2, code_change/3]).
|
||||
|
||||
%% hook handlers
|
||||
-export([user_send_packet/3,
|
||||
@ -59,32 +52,45 @@
|
||||
c2s_broadcast_recipients/5]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
|
||||
-define(PROCNAME, ejabberd_mod_caps).
|
||||
-define(BAD_HASH_LIFETIME, 600). %% in seconds
|
||||
|
||||
-record(caps, {node, version, hash, exts}).
|
||||
-record(caps_features, {node_pair, features = []}).
|
||||
-define(BAD_HASH_LIFETIME, 600).
|
||||
|
||||
-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
|
||||
%%====================================================================
|
||||
start_link(Host, Opts) ->
|
||||
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) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
ChildSpec =
|
||||
{Proc,
|
||||
{?MODULE, start_link, [Host, Opts]},
|
||||
transient,
|
||||
1000,
|
||||
worker,
|
||||
[?MODULE]},
|
||||
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
|
||||
transient, 1000, worker, [?MODULE]},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||
|
||||
stop(Host) ->
|
||||
@ -96,164 +102,170 @@ stop(Host) ->
|
||||
%% get_features returns a list of features implied by the given caps
|
||||
%% record (as extracted by read_caps) or 'unknown' if features are
|
||||
%% not completely collected at the moment.
|
||||
get_features(nothing) ->
|
||||
[];
|
||||
get_features(nothing) -> [];
|
||||
get_features(#caps{node = Node, version = Version, exts = 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
|
||||
%% <presence/> stanza) and returns an opaque value representing the
|
||||
%% Entity Capabilities contained therein, or the atom nothing if no
|
||||
%% capabilities are advertised.
|
||||
read_caps(Els) ->
|
||||
read_caps(Els, nothing).
|
||||
lists:foldl(fun (SubNode, Acc) ->
|
||||
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) ->
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
read_caps(Els) -> read_caps(Els, nothing).
|
||||
|
||||
read_caps([#xmlel{name = <<"c">>, attrs = Attrs}
|
||||
| Tail],
|
||||
Result) ->
|
||||
case xml:get_attr_s(<<"xmlns">>, Attrs) of
|
||||
?NS_CAPS ->
|
||||
Node = xml:get_attr_s("node", Attrs),
|
||||
Version = xml:get_attr_s("ver", Attrs),
|
||||
Hash = xml:get_attr_s("hash", Attrs),
|
||||
Exts = string:tokens(xml:get_attr_s("ext", Attrs), " "),
|
||||
read_caps(Tail, #caps{node = Node, hash = Hash,
|
||||
version = Version, exts = Exts});
|
||||
_ ->
|
||||
read_caps(Tail, Result)
|
||||
Node = xml:get_attr_s(<<"node">>, Attrs),
|
||||
Version = xml:get_attr_s(<<"ver">>, Attrs),
|
||||
Hash = xml:get_attr_s(<<"hash">>, 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, Result)
|
||||
end;
|
||||
read_caps([{xmlelement, "x", Attrs, _Els} | Tail], Result) ->
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
?NS_MUC_USER ->
|
||||
nothing;
|
||||
_ ->
|
||||
read_caps(Tail, Result)
|
||||
read_caps([#xmlel{name = <<"x">>, attrs = Attrs}
|
||||
| Tail],
|
||||
Result) ->
|
||||
case xml:get_attr_s(<<"xmlns">>, Attrs) of
|
||||
?NS_MUC_USER -> nothing;
|
||||
_ -> read_caps(Tail, Result)
|
||||
end;
|
||||
read_caps([_ | Tail], Result) ->
|
||||
read_caps(Tail, Result);
|
||||
read_caps([], Result) ->
|
||||
Result.
|
||||
read_caps([], Result) -> Result.
|
||||
|
||||
%%====================================================================
|
||||
%% Hooks
|
||||
%%====================================================================
|
||||
user_send_packet(#jid{luser = User, lserver = Server} = From,
|
||||
#jid{luser = User, lserver = Server, lresource = ""},
|
||||
{xmlelement, "presence", Attrs, Els}) ->
|
||||
Type = xml:get_attr_s("type", Attrs),
|
||||
if Type == ""; Type == "available" ->
|
||||
user_send_packet(
|
||||
#jid{luser = User, lserver = Server} = From,
|
||||
#jid{luser = User, lserver = Server, lresource = <<"">>},
|
||||
#xmlel{name = <<"presence">>, attrs = Attrs, children = Els}) ->
|
||||
Type = xml:get_attr_s(<<"type">>, Attrs),
|
||||
if Type == <<"">>; Type == <<"available">> ->
|
||||
case read_caps(Els) of
|
||||
nothing ->
|
||||
ok;
|
||||
nothing -> ok;
|
||||
#caps{version = Version, exts = Exts} = Caps ->
|
||||
feature_request(Server, From, Caps, [Version | Exts])
|
||||
end;
|
||||
true ->
|
||||
ok
|
||||
true -> ok
|
||||
end;
|
||||
user_send_packet(_From, _To, _Packet) ->
|
||||
ok.
|
||||
user_send_packet(_From, _To, _Packet) -> ok.
|
||||
|
||||
user_receive_packet(#jid{lserver = Server}, From, _To,
|
||||
{xmlelement, "presence", Attrs, Els}) ->
|
||||
Type = xml:get_attr_s("type", Attrs),
|
||||
if Type == ""; Type == "available" ->
|
||||
#xmlel{name = <<"presence">>, attrs = Attrs,
|
||||
children = Els}) ->
|
||||
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
|
||||
nothing ->
|
||||
ok;
|
||||
nothing -> ok;
|
||||
#caps{version = Version, exts = Exts} = Caps ->
|
||||
feature_request(Server, From, Caps, [Version | Exts])
|
||||
end;
|
||||
true ->
|
||||
ok
|
||||
true -> ok
|
||||
end;
|
||||
user_receive_packet(_JID, _From, _To, _Packet) ->
|
||||
ok.
|
||||
|
||||
caps_stream_features(Acc, MyHost) ->
|
||||
case make_my_disco_hash(MyHost) of
|
||||
"" ->
|
||||
Acc;
|
||||
<<"">> -> Acc;
|
||||
Hash ->
|
||||
[{xmlelement, "c", [{"xmlns", ?NS_CAPS},
|
||||
{"hash", "sha-1"},
|
||||
{"node", ?EJABBERD_URI},
|
||||
{"ver", Hash}], []} | Acc]
|
||||
[#xmlel{name = <<"c">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_CAPS}, {<<"hash">>, <<"sha-1">>},
|
||||
{<<"node">>, ?EJABBERD_URI}, {<<"ver">>, Hash}],
|
||||
children = []}
|
||||
| Acc]
|
||||
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,
|
||||
To#jid.lserver,
|
||||
empty,
|
||||
[From, To, "", Lang]);
|
||||
disco_features(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
To#jid.lserver, empty,
|
||||
[From, To, <<"">>, Lang]);
|
||||
false ->
|
||||
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,
|
||||
To#jid.lserver,
|
||||
[],
|
||||
[From, To, "", Lang]);
|
||||
disco_identity(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
To#jid.lserver, [],
|
||||
[From, To, <<"">>, Lang]);
|
||||
false ->
|
||||
Acc
|
||||
end.
|
||||
|
||||
disco_info(_Acc, Host, Module, ?EJABBERD_URI ++ "#" ++ [_|_], Lang) ->
|
||||
ejabberd_hooks:run_fold(disco_info,
|
||||
Host,
|
||||
[],
|
||||
[Host, Module, "", Lang]);
|
||||
disco_info(Acc, _Host, _Module, _Node, _Lang) ->
|
||||
Acc.
|
||||
disco_info(Acc, Host, Module, Node, Lang) ->
|
||||
case is_valid_node(Node) of
|
||||
true ->
|
||||
ejabberd_hooks:run_fold(disco_info, Host, [],
|
||||
[Host, Module, <<"">>, Lang]);
|
||||
false ->
|
||||
Acc
|
||||
end.
|
||||
|
||||
c2s_presence_in(C2SState, {From, To, {_, _, Attrs, Els}}) ->
|
||||
Type = xml:get_attr_s("type", Attrs),
|
||||
Subscription = ejabberd_c2s:get_subscription(From, C2SState),
|
||||
Insert = ((Type == "") or (Type == "available"))
|
||||
c2s_presence_in(C2SState,
|
||||
{From, To, {_, _, Attrs, Els}}) ->
|
||||
Type = xml:get_attr_s(<<"type">>, Attrs),
|
||||
Subscription = ejabberd_c2s:get_subscription(From,
|
||||
C2SState),
|
||||
Insert = ((Type == <<"">>) or (Type == <<"available">>))
|
||||
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 ->
|
||||
LFrom = jlib:jid_tolower(From),
|
||||
Rs = case ejabberd_c2s:get_aux_field(caps_resources, C2SState) of
|
||||
{ok, Rs1} ->
|
||||
Rs1;
|
||||
error ->
|
||||
gb_trees:empty()
|
||||
Rs = case ejabberd_c2s:get_aux_field(caps_resources,
|
||||
C2SState)
|
||||
of
|
||||
{ok, Rs1} -> Rs1;
|
||||
error -> gb_trees:empty()
|
||||
end,
|
||||
Caps = read_caps(Els),
|
||||
{CapsUpdated, NewRs} =
|
||||
case Caps of
|
||||
nothing when Insert == true ->
|
||||
{false, Rs};
|
||||
{CapsUpdated, NewRs} = case Caps of
|
||||
nothing when Insert == true -> {false, Rs};
|
||||
_ when Insert == true ->
|
||||
case gb_trees:lookup(LFrom, Rs) of
|
||||
{value, Caps} ->
|
||||
{false, Rs};
|
||||
{value, Caps} -> {false, Rs};
|
||||
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;
|
||||
_ ->
|
||||
{false, gb_trees:delete_any(LFrom, Rs)}
|
||||
_ -> {false, gb_trees:delete_any(LFrom, Rs)}
|
||||
end,
|
||||
if CapsUpdated ->
|
||||
ejabberd_hooks:run(caps_update, To#jid.lserver,
|
||||
[From, To, get_features(Caps)]);
|
||||
true ->
|
||||
ok
|
||||
true -> ok
|
||||
end,
|
||||
ejabberd_c2s:set_aux_field(caps_resources, NewRs, C2SState);
|
||||
true ->
|
||||
C2SState
|
||||
ejabberd_c2s:set_aux_field(caps_resources, NewRs,
|
||||
C2SState);
|
||||
true -> C2SState
|
||||
end.
|
||||
|
||||
c2s_broadcast_recipients(InAcc, C2SState, {pep_message, Feature},
|
||||
@ -292,8 +304,8 @@ init([Host, Opts]) ->
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, caps_features)}]),
|
||||
mnesia:add_table_copy(caps_features, node(), disc_only_copies),
|
||||
MaxSize = gen_mod:get_opt(cache_size, Opts, 1000),
|
||||
LifeTime = gen_mod:get_opt(cache_life_time, Opts, timer:hours(24) div 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, fun(CL) when is_integer(CL) -> CL end, timer:hours(24) div 1000),
|
||||
cache_tab:new(caps_features, [{max_size, MaxSize}, {life_time, LifeTime}]),
|
||||
ejabberd_hooks:add(c2s_presence_in, Host,
|
||||
?MODULE, c2s_presence_in, 75),
|
||||
@ -320,20 +332,18 @@ handle_call(stop, _From, State) ->
|
||||
handle_call(_Req, _From, State) ->
|
||||
{reply, {error, badarg}, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
handle_cast(_Msg, State) -> {noreply, State}.
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
handle_info(_Info, State) -> {noreply, State}.
|
||||
|
||||
terminate(_Reason, State) ->
|
||||
Host = State#state.host,
|
||||
ejabberd_hooks:delete(c2s_presence_in, Host,
|
||||
?MODULE, c2s_presence_in, 75),
|
||||
ejabberd_hooks:delete(c2s_presence_in, Host, ?MODULE,
|
||||
c2s_presence_in, 75),
|
||||
ejabberd_hooks:delete(c2s_broadcast_recipients, Host,
|
||||
?MODULE, c2s_broadcast_recipients, 75),
|
||||
ejabberd_hooks:delete(user_send_packet, Host,
|
||||
?MODULE, user_send_packet, 75),
|
||||
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
|
||||
user_send_packet, 75),
|
||||
ejabberd_hooks:delete(user_receive_packet, Host,
|
||||
?MODULE, user_receive_packet, 75),
|
||||
ejabberd_hooks:delete(c2s_stream_features, Host,
|
||||
@ -344,267 +354,251 @@ terminate(_Reason, State) ->
|
||||
?MODULE, disco_features, 75),
|
||||
ejabberd_hooks:delete(disco_local_identity, Host,
|
||||
?MODULE, disco_identity, 75),
|
||||
ejabberd_hooks:delete(disco_info, Host,
|
||||
?MODULE, disco_info, 75),
|
||||
ejabberd_hooks:delete(disco_info, Host, ?MODULE,
|
||||
disco_info, 75),
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
%%====================================================================
|
||||
%% Aux functions
|
||||
%%====================================================================
|
||||
feature_request(Host, From, Caps, [SubNode | Tail] = SubNodes) ->
|
||||
feature_request(Host, From, Caps,
|
||||
[SubNode | Tail] = SubNodes) ->
|
||||
Node = Caps#caps.node,
|
||||
BinaryNode = node_to_binary(Node, SubNode),
|
||||
case cache_tab:lookup(caps_features, BinaryNode,
|
||||
caps_read_fun(BinaryNode)) of
|
||||
NodePair = {Node, SubNode},
|
||||
case cache_tab:lookup(caps_features, NodePair,
|
||||
caps_read_fun(NodePair))
|
||||
of
|
||||
{ok, Fs} when is_list(Fs) ->
|
||||
feature_request(Host, From, Caps, Tail);
|
||||
Other ->
|
||||
NeedRequest = case Other of
|
||||
{ok, TS} ->
|
||||
now_ts() >= TS + ?BAD_HASH_LIFETIME;
|
||||
_ ->
|
||||
true
|
||||
{ok, TS} -> now_ts() >= TS + (?BAD_HASH_LIFETIME);
|
||||
_ -> true
|
||||
end,
|
||||
if NeedRequest ->
|
||||
IQ = #iq{type = get,
|
||||
xmlns = ?NS_DISCO_INFO,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", ?NS_DISCO_INFO},
|
||||
{"node", Node ++ "#" ++ SubNode}],
|
||||
[]}]},
|
||||
%% We cache current timestamp in order to avoid
|
||||
%% caps requests flood
|
||||
cache_tab:insert(caps_features, BinaryNode, now_ts(),
|
||||
caps_write_fun(BinaryNode, now_ts())),
|
||||
F = fun(IQReply) ->
|
||||
feature_response(
|
||||
IQReply, Host, From, Caps, SubNodes)
|
||||
IQ = #iq{type = get, xmlns = ?NS_DISCO_INFO,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_DISCO_INFO},
|
||||
{<<"node">>,
|
||||
<<Node/binary, "#",
|
||||
SubNode/binary>>}],
|
||||
children = []}]},
|
||||
cache_tab:insert(caps_features, NodePair, now_ts(),
|
||||
caps_write_fun(NodePair, now_ts())),
|
||||
F = fun (IQReply) ->
|
||||
feature_response(IQReply, Host, From, Caps,
|
||||
SubNodes)
|
||||
end,
|
||||
ejabberd_local:route_iq(
|
||||
jlib:make_jid("", Host, ""), From, IQ, F);
|
||||
true ->
|
||||
feature_request(Host, From, Caps, Tail)
|
||||
ejabberd_local:route_iq(jlib:make_jid(<<"">>, Host,
|
||||
<<"">>),
|
||||
From, IQ, F);
|
||||
true -> feature_request(Host, From, Caps, Tail)
|
||||
end
|
||||
end;
|
||||
feature_request(_Host, _From, _Caps, []) ->
|
||||
ok.
|
||||
feature_request(_Host, _From, _Caps, []) -> ok.
|
||||
|
||||
feature_response(#iq{type = result,
|
||||
sub_el = [{xmlelement, _, _, Els}]},
|
||||
sub_el = [#xmlel{children = Els}]},
|
||||
Host, From, Caps, [SubNode | SubNodes]) ->
|
||||
BinaryNode = node_to_binary(Caps#caps.node, SubNode),
|
||||
NodePair = {Caps#caps.node, SubNode},
|
||||
case check_hash(Caps, Els) of
|
||||
true ->
|
||||
Features = lists:flatmap(
|
||||
fun({xmlelement, "feature", FAttrs, _}) ->
|
||||
[xml:get_attr_s("var", FAttrs)];
|
||||
(_) ->
|
||||
[]
|
||||
end, Els),
|
||||
BinaryFeatures = features_to_binary(Features),
|
||||
cache_tab:insert(
|
||||
caps_features, BinaryNode, BinaryFeatures,
|
||||
caps_write_fun(BinaryNode, BinaryFeatures));
|
||||
false ->
|
||||
ok
|
||||
Features = lists:flatmap(fun (#xmlel{name =
|
||||
<<"feature">>,
|
||||
attrs = FAttrs}) ->
|
||||
[xml:get_attr_s(<<"var">>, FAttrs)];
|
||||
(_) -> []
|
||||
end,
|
||||
Els),
|
||||
cache_tab:insert(caps_features, NodePair,
|
||||
Features,
|
||||
caps_write_fun(NodePair, Features));
|
||||
false -> ok
|
||||
end,
|
||||
feature_request(Host, From, Caps, SubNodes);
|
||||
feature_response(_IQResult, Host, From, Caps, [_SubNode | SubNodes]) ->
|
||||
%% We got type=error or invalid type=result stanza or timeout.
|
||||
feature_response(_IQResult, Host, From, Caps,
|
||||
[_SubNode | 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) ->
|
||||
fun() ->
|
||||
fun () ->
|
||||
case mnesia:dirty_read({caps_features, Node}) of
|
||||
[#caps_features{features = Features}] ->
|
||||
{ok, Features};
|
||||
_ ->
|
||||
error
|
||||
[#caps_features{features = Features}] -> {ok, Features};
|
||||
_ -> error
|
||||
end
|
||||
end.
|
||||
|
||||
caps_write_fun(Node, Features) ->
|
||||
fun() ->
|
||||
mnesia:dirty_write(
|
||||
#caps_features{node_pair = Node,
|
||||
fun () ->
|
||||
mnesia:dirty_write(#caps_features{node_pair = Node,
|
||||
features = Features})
|
||||
end.
|
||||
|
||||
make_my_disco_hash(Host) ->
|
||||
JID = jlib:make_jid("", Host, ""),
|
||||
JID = jlib:make_jid(<<"">>, Host, <<"">>),
|
||||
case {ejabberd_hooks:run_fold(disco_local_features,
|
||||
Host,
|
||||
empty,
|
||||
[JID, JID, "", ""]),
|
||||
ejabberd_hooks:run_fold(disco_local_identity,
|
||||
Host,
|
||||
[],
|
||||
[JID, JID, "", ""]),
|
||||
ejabberd_hooks:run_fold(disco_info,
|
||||
Host,
|
||||
[],
|
||||
[Host, undefined, "", ""])} of
|
||||
Host, empty, [JID, JID, <<"">>, <<"">>]),
|
||||
ejabberd_hooks:run_fold(disco_local_identity, Host, [],
|
||||
[JID, JID, <<"">>, <<"">>]),
|
||||
ejabberd_hooks:run_fold(disco_info, Host, [],
|
||||
[Host, undefined, <<"">>, <<"">>])}
|
||||
of
|
||||
{{result, Features}, Identities, Info} ->
|
||||
Feats = lists:map(
|
||||
fun({{Feat, _Host}}) ->
|
||||
{xmlelement, "feature", [{"var", Feat}], []};
|
||||
Feats = lists:map(fun ({{Feat, _Host}}) ->
|
||||
#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, Feat}],
|
||||
children = []};
|
||||
(Feat) ->
|
||||
{xmlelement, "feature", [{"var", Feat}], []}
|
||||
end, Features),
|
||||
#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, Feat}],
|
||||
children = []}
|
||||
end,
|
||||
Features),
|
||||
make_disco_hash(Identities ++ Info ++ Feats, sha1);
|
||||
_Err ->
|
||||
""
|
||||
_Err -> <<"">>
|
||||
end.
|
||||
|
||||
-ifdef(HAVE_MD2).
|
||||
|
||||
make_disco_hash(DiscoEls, Algo) ->
|
||||
Concat = [concat_identities(DiscoEls),
|
||||
concat_features(DiscoEls),
|
||||
concat_info(DiscoEls)],
|
||||
base64:encode_to_string(
|
||||
if Algo == md2 ->
|
||||
sha:md2(Concat);
|
||||
Algo == md5 ->
|
||||
crypto:md5(Concat);
|
||||
Algo == sha1 ->
|
||||
crypto:sha(Concat);
|
||||
Algo == sha224 ->
|
||||
sha:sha224(Concat);
|
||||
Algo == sha256 ->
|
||||
sha:sha256(Concat);
|
||||
Algo == sha384 ->
|
||||
sha:sha384(Concat);
|
||||
Algo == sha512 ->
|
||||
sha:sha512(Concat)
|
||||
Concat = list_to_binary([concat_identities(DiscoEls),
|
||||
concat_features(DiscoEls), concat_info(DiscoEls)]),
|
||||
jlib:encode_base64(case Algo of
|
||||
md2 -> sha:md2(Concat);
|
||||
md5 -> crypto:md5(Concat);
|
||||
sha1 -> crypto:sha(Concat);
|
||||
sha224 -> sha:sha224(Concat);
|
||||
sha256 -> sha:sha256(Concat);
|
||||
sha384 -> sha:sha384(Concat);
|
||||
sha512 -> sha:sha512(Concat)
|
||||
end).
|
||||
|
||||
check_hash(Caps, Els) ->
|
||||
case Caps#caps.hash of
|
||||
"md2" ->
|
||||
<<"md2">> ->
|
||||
Caps#caps.version == make_disco_hash(Els, md2);
|
||||
"md5" ->
|
||||
<<"md5">> ->
|
||||
Caps#caps.version == make_disco_hash(Els, md5);
|
||||
"sha-1" ->
|
||||
<<"sha-1">> ->
|
||||
Caps#caps.version == make_disco_hash(Els, sha1);
|
||||
"sha-224" ->
|
||||
<<"sha-224">> ->
|
||||
Caps#caps.version == make_disco_hash(Els, sha224);
|
||||
"sha-256" ->
|
||||
<<"sha-256">> ->
|
||||
Caps#caps.version == make_disco_hash(Els, sha256);
|
||||
"sha-384" ->
|
||||
<<"sha-384">> ->
|
||||
Caps#caps.version == make_disco_hash(Els, sha384);
|
||||
"sha-512" ->
|
||||
<<"sha-512">> ->
|
||||
Caps#caps.version == make_disco_hash(Els, sha512);
|
||||
_ ->
|
||||
true
|
||||
_ -> true
|
||||
end.
|
||||
|
||||
-else.
|
||||
|
||||
make_disco_hash(DiscoEls, Algo) ->
|
||||
Concat = [concat_identities(DiscoEls),
|
||||
concat_features(DiscoEls),
|
||||
concat_info(DiscoEls)],
|
||||
base64:encode_to_string(
|
||||
if Algo == md5 ->
|
||||
crypto:md5(Concat);
|
||||
Algo == sha1 ->
|
||||
crypto:sha(Concat);
|
||||
Algo == sha224 ->
|
||||
sha:sha224(Concat);
|
||||
Algo == sha256 ->
|
||||
sha:sha256(Concat);
|
||||
Algo == sha384 ->
|
||||
sha:sha384(Concat);
|
||||
Algo == sha512 ->
|
||||
sha:sha512(Concat)
|
||||
Concat = list_to_binary([concat_identities(DiscoEls),
|
||||
concat_features(DiscoEls), concat_info(DiscoEls)]),
|
||||
jlib:encode_base64(case Algo of
|
||||
md5 -> crypto:md5(Concat);
|
||||
sha1 -> crypto:sha(Concat);
|
||||
sha224 -> sha:sha224(Concat);
|
||||
sha256 -> sha:sha256(Concat);
|
||||
sha384 -> sha:sha384(Concat);
|
||||
sha512 -> sha:sha512(Concat)
|
||||
end).
|
||||
|
||||
check_hash(Caps, Els) ->
|
||||
case Caps#caps.hash of
|
||||
"md5" ->
|
||||
<<"md5">> ->
|
||||
Caps#caps.version == make_disco_hash(Els, md5);
|
||||
"sha-1" ->
|
||||
<<"sha-1">> ->
|
||||
Caps#caps.version == make_disco_hash(Els, sha1);
|
||||
"sha-224" ->
|
||||
<<"sha-224">> ->
|
||||
Caps#caps.version == make_disco_hash(Els, sha224);
|
||||
"sha-256" ->
|
||||
<<"sha-256">> ->
|
||||
Caps#caps.version == make_disco_hash(Els, sha256);
|
||||
"sha-384" ->
|
||||
<<"sha-384">> ->
|
||||
Caps#caps.version == make_disco_hash(Els, sha384);
|
||||
"sha-512" ->
|
||||
<<"sha-512">> ->
|
||||
Caps#caps.version == make_disco_hash(Els, sha512);
|
||||
_ ->
|
||||
true
|
||||
_ -> true
|
||||
end.
|
||||
|
||||
-endif.
|
||||
|
||||
concat_features(Els) ->
|
||||
lists:usort(
|
||||
lists:flatmap(
|
||||
fun({xmlelement, "feature", Attrs, _}) ->
|
||||
[[xml:get_attr_s("var", Attrs), $<]];
|
||||
(_) ->
|
||||
[]
|
||||
end, Els)).
|
||||
lists:usort(lists:flatmap(fun (#xmlel{name =
|
||||
<<"feature">>,
|
||||
attrs = Attrs}) ->
|
||||
[[xml:get_attr_s(<<"var">>, Attrs), $<]];
|
||||
(_) -> []
|
||||
end,
|
||||
Els)).
|
||||
|
||||
concat_identities(Els) ->
|
||||
lists:sort(
|
||||
lists:flatmap(
|
||||
fun({xmlelement, "identity", Attrs, _}) ->
|
||||
[[xml:get_attr_s("category", Attrs), $/,
|
||||
xml:get_attr_s("type", Attrs), $/,
|
||||
xml:get_attr_s("xml:lang", Attrs), $/,
|
||||
xml:get_attr_s("name", Attrs), $<]];
|
||||
(_) ->
|
||||
[]
|
||||
end, Els)).
|
||||
lists:sort(lists:flatmap(fun (#xmlel{name =
|
||||
<<"identity">>,
|
||||
attrs = Attrs}) ->
|
||||
[[xml:get_attr_s(<<"category">>, Attrs),
|
||||
$/, xml:get_attr_s(<<"type">>, Attrs),
|
||||
$/,
|
||||
xml:get_attr_s(<<"xml:lang">>, Attrs),
|
||||
$/, xml:get_attr_s(<<"name">>, Attrs),
|
||||
$<]];
|
||||
(_) -> []
|
||||
end,
|
||||
Els)).
|
||||
|
||||
concat_info(Els) ->
|
||||
lists:sort(
|
||||
lists:flatmap(
|
||||
fun({xmlelement, "x", Attrs, Fields}) ->
|
||||
case {xml:get_attr_s("xmlns", Attrs),
|
||||
xml:get_attr_s("type", Attrs)} of
|
||||
{?NS_XDATA, "result"} ->
|
||||
lists:sort(lists:flatmap(fun (#xmlel{name = <<"x">>,
|
||||
attrs = Attrs, children = Fields}) ->
|
||||
case {xml:get_attr_s(<<"xmlns">>, Attrs),
|
||||
xml:get_attr_s(<<"type">>, Attrs)}
|
||||
of
|
||||
{?NS_XDATA, <<"result">>} ->
|
||||
[concat_xdata_fields(Fields)];
|
||||
_ ->
|
||||
[]
|
||||
_ -> []
|
||||
end;
|
||||
(_) ->
|
||||
[]
|
||||
end, Els)).
|
||||
(_) -> []
|
||||
end,
|
||||
Els)).
|
||||
|
||||
concat_xdata_fields(Fields) ->
|
||||
[Form, Res] =
|
||||
lists:foldl(
|
||||
fun({xmlelement, "field", Attrs, Els} = El,
|
||||
[Form, Res] = lists:foldl(fun (#xmlel{name =
|
||||
<<"field">>,
|
||||
attrs = Attrs, children = Els} =
|
||||
El,
|
||||
[FormType, VarFields] = Acc) ->
|
||||
case xml:get_attr_s("var", Attrs) of
|
||||
"" ->
|
||||
Acc;
|
||||
"FORM_TYPE" ->
|
||||
[xml:get_subtag_cdata(El, "value"), VarFields];
|
||||
case xml:get_attr_s(<<"var">>, Attrs) of
|
||||
<<"">> -> Acc;
|
||||
<<"FORM_TYPE">> ->
|
||||
[xml:get_subtag_cdata(El,
|
||||
<<"value">>),
|
||||
VarFields];
|
||||
Var ->
|
||||
[FormType,
|
||||
[[[Var, $<],
|
||||
lists:sort(
|
||||
lists:flatmap(
|
||||
fun({xmlelement, "value", _, VEls}) ->
|
||||
[[xml:get_cdata(VEls), $<]];
|
||||
lists:sort(lists:flatmap(fun
|
||||
(#xmlel{name
|
||||
=
|
||||
<<"value">>,
|
||||
children
|
||||
=
|
||||
VEls}) ->
|
||||
[[xml:get_cdata(VEls),
|
||||
$<]];
|
||||
(_) ->
|
||||
[]
|
||||
end, Els))] | VarFields]]
|
||||
end,
|
||||
Els))]
|
||||
| VarFields]]
|
||||
end;
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, ["", []], Fields),
|
||||
(_, Acc) -> Acc
|
||||
end,
|
||||
[<<"">>, []], Fields),
|
||||
[Form, $<, lists:sort(Res)].
|
||||
|
||||
gb_trees_fold(F, Acc, Tree) ->
|
||||
@ -616,10 +610,16 @@ gb_trees_fold_iter(F, Acc, Iter) ->
|
||||
{Key, Val, NewIter} ->
|
||||
NewAcc = F(Key, Val, Acc),
|
||||
gb_trees_fold_iter(F, NewAcc, NewIter);
|
||||
_ ->
|
||||
Acc
|
||||
_ -> Acc
|
||||
end.
|
||||
|
||||
now_ts() ->
|
||||
{MegaSecs, Secs, _} = now(),
|
||||
MegaSecs*1000000 + Secs.
|
||||
{MegaSecs, Secs, _} = now(), 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).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2,
|
||||
stop/1,
|
||||
process_local_iq/3]).
|
||||
-export([start/2, stop/1, process_local_iq/3]).
|
||||
|
||||
-include("ejabberd.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) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_ECONFIGURE,
|
||||
?MODULE, process_local_iq, IQDisc),
|
||||
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_ECONFIGURE, ?MODULE, process_local_iq,
|
||||
IQDisc),
|
||||
ok.
|
||||
|
||||
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, #iq{type = Type, lang = _Lang, sub_el = SubEl} = IQ) ->
|
||||
process_local_iq(From, To,
|
||||
#iq{type = Type, lang = _Lang, sub_el = SubEl} = IQ) ->
|
||||
case acl:match_rule(To#jid.lserver, configure, From) of
|
||||
deny ->
|
||||
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;
|
||||
get ->
|
||||
case process_get(SubEl) of
|
||||
{result, Res} ->
|
||||
IQ#iq{type = result, sub_el = [Res]};
|
||||
{result, Res} -> IQ#iq{type = result, sub_el = [Res]};
|
||||
{error, Error} ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, Error]}
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
process_get({xmlelement, "info", _Attrs, _SubEls}) ->
|
||||
process_get(#xmlel{name = <<"info">>}) ->
|
||||
S2SConns = ejabberd_s2s:dirty_get_connections(),
|
||||
TConns = lists:usort([element(2, C) || C <- S2SConns]),
|
||||
Attrs = [{"registered-users",
|
||||
integer_to_list(mnesia:table_info(passwd, size))},
|
||||
{"online-users",
|
||||
integer_to_list(mnesia:table_info(presence, size))},
|
||||
{"running-nodes",
|
||||
integer_to_list(length(mnesia:system_info(running_db_nodes)))},
|
||||
{"stopped-nodes",
|
||||
integer_to_list(
|
||||
length(lists:usort(mnesia:system_info(db_nodes) ++
|
||||
mnesia:system_info(extra_db_nodes)) --
|
||||
mnesia:system_info(running_db_nodes)))},
|
||||
{"outgoing-s2s-servers", integer_to_list(length(TConns))}],
|
||||
{result, {xmlelement, "info",
|
||||
[{"xmlns", ?NS_ECONFIGURE} | Attrs], []}};
|
||||
process_get({xmlelement, "welcome-message", Attrs, _SubEls}) ->
|
||||
{Subj, Body} = case ejabberd_config:get_local_option(welcome_message) of
|
||||
{_Subj, _Body} = SB -> SB;
|
||||
_ -> {"", ""}
|
||||
Attrs = [{<<"registered-users">>,
|
||||
iolist_to_binary(integer_to_list(mnesia:table_info(passwd,
|
||||
size)))},
|
||||
{<<"online-users">>,
|
||||
iolist_to_binary(integer_to_list(mnesia:table_info(presence,
|
||||
size)))},
|
||||
{<<"running-nodes">>,
|
||||
iolist_to_binary(integer_to_list(length(mnesia:system_info(running_db_nodes))))},
|
||||
{<<"stopped-nodes">>,
|
||||
iolist_to_binary(integer_to_list(length(lists:usort(mnesia:system_info(db_nodes)
|
||||
++
|
||||
mnesia:system_info(extra_db_nodes))
|
||||
--
|
||||
mnesia:system_info(running_db_nodes))))},
|
||||
{<<"outgoing-s2s-servers">>,
|
||||
iolist_to_binary(integer_to_list(length(TConns)))}],
|
||||
{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,
|
||||
{result, {xmlelement, "welcome-message", Attrs,
|
||||
[{xmlelement, "subject", [], [{xmlcdata, Subj}]},
|
||||
{xmlelement, "body", [], [{xmlcdata, Body}]}]}};
|
||||
process_get({xmlelement, "registration-watchers", Attrs, _SubEls}) ->
|
||||
SubEls =
|
||||
case ejabberd_config:get_local_option(registration_watchers) of
|
||||
JIDs when is_list(JIDs) ->
|
||||
lists:map(fun(JID) ->
|
||||
{xmlelement, "jid", [], [{xmlcdata, JID}]}
|
||||
end, JIDs);
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
{result, {xmlelement, "registration_watchers", Attrs, SubEls}};
|
||||
process_get({xmlelement, "acls", Attrs, _SubEls}) ->
|
||||
Str = lists:flatten(io_lib:format("~p.", [ets:tab2list(acl)])),
|
||||
{result, {xmlelement, "acls", Attrs, [{xmlcdata, Str}]}};
|
||||
process_get({xmlelement, "access", Attrs, _SubEls}) ->
|
||||
Str =
|
||||
lists:flatten(
|
||||
io_lib:format(
|
||||
"~p.",
|
||||
{<<"">>, <<"">>}),
|
||||
{result,
|
||||
#xmlel{name = <<"welcome-message">>, attrs = Attrs,
|
||||
children =
|
||||
[#xmlel{name = <<"subject">>, attrs = [],
|
||||
children = [{xmlcdata, Subj}]},
|
||||
#xmlel{name = <<"body">>, attrs = [],
|
||||
children = [{xmlcdata, Body}]}]}};
|
||||
process_get(#xmlel{name = <<"registration-watchers">>,
|
||||
attrs = Attrs}) ->
|
||||
SubEls = ejabberd_config:get_local_option(
|
||||
registration_watchers,
|
||||
fun(JIDs) when is_list(JIDs) ->
|
||||
lists:map(
|
||||
fun(J) ->
|
||||
#xmlel{name = <<"jid">>, attrs = [],
|
||||
children = [{xmlcdata,
|
||||
iolist_to_binary(J)}]}
|
||||
end, JIDs)
|
||||
end, []),
|
||||
{result,
|
||||
#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,
|
||||
[{{config, {access, '$1'}, '$2'},
|
||||
[{{config, {access, '$1'},
|
||||
'$2'},
|
||||
[],
|
||||
[{{access, '$1', '$2'}}]}])
|
||||
])),
|
||||
{result, {xmlelement, "access", Attrs, [{xmlcdata, Str}]}};
|
||||
process_get({xmlelement, "last", Attrs, _SubEls}) ->
|
||||
case catch mnesia:dirty_select(
|
||||
last_activity, [{{last_activity, '_', '$1', '_'}, [], ['$1']}]) of
|
||||
[{{access, '$1',
|
||||
'$2'}}]}])])),
|
||||
{result,
|
||||
#xmlel{name = <<"access">>, attrs = Attrs,
|
||||
children = [{xmlcdata, Str}]}};
|
||||
process_get(#xmlel{name = <<"last">>, attrs = Attrs}) ->
|
||||
case catch mnesia:dirty_select(last_activity,
|
||||
[{{last_activity, '_', '$1', '_'}, [],
|
||||
['$1']}])
|
||||
of
|
||||
{'EXIT', _Reason} ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
Vals ->
|
||||
{MegaSecs, Secs, _MicroSecs} = now(),
|
||||
TimeStamp = MegaSecs * 1000000 + Secs,
|
||||
Str = lists:flatten(
|
||||
lists:append(
|
||||
[[integer_to_list(TimeStamp - V), " "] || V <- Vals])),
|
||||
{result, {xmlelement, "last", Attrs, [{xmlcdata, Str}]}}
|
||||
Str = list_to_binary(
|
||||
[[jlib:integer_to_binary(TimeStamp - V),
|
||||
<<" ">>] || V <- Vals]),
|
||||
{result,
|
||||
#xmlel{name = <<"last">>, attrs = Attrs,
|
||||
children = [{xmlcdata, Str}]}}
|
||||
end;
|
||||
%%process_get({xmlelement, Name, Attrs, SubEls}) ->
|
||||
%% {result, };
|
||||
process_get(_) ->
|
||||
{error, ?ERR_BAD_REQUEST}.
|
||||
|
||||
process_get(_) -> {error, ?ERR_BAD_REQUEST}.
|
||||
|
@ -25,247 +25,275 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_disco).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2,
|
||||
stop/1,
|
||||
process_local_iq_items/3,
|
||||
process_local_iq_info/3,
|
||||
get_local_identity/5,
|
||||
get_local_features/5,
|
||||
get_local_services/5,
|
||||
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]).
|
||||
-export([start/2, stop/1, process_local_iq_items/3,
|
||||
process_local_iq_info/3, get_local_identity/5,
|
||||
get_local_features/5, get_local_services/5,
|
||||
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("jlib.hrl").
|
||||
|
||||
-include("mod_roster.hrl").
|
||||
|
||||
start(Host, Opts) ->
|
||||
ejabberd_local:refresh_iq_handlers(),
|
||||
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
|
||||
?MODULE, process_local_iq_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
|
||||
?MODULE, process_local_iq_info, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS,
|
||||
?MODULE, process_sm_iq_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO,
|
||||
?MODULE, process_sm_iq_info, IQDisc),
|
||||
|
||||
catch ets:new(disco_features, [named_table, ordered_set, public]),
|
||||
register_feature(Host, "iq"),
|
||||
register_feature(Host, "presence"),
|
||||
register_feature(Host, "presence-invisible"),
|
||||
|
||||
catch ets:new(disco_extra_domains, [named_table, ordered_set, public]),
|
||||
ExtraDomains = gen_mod:get_opt(extra_domains, Opts, []),
|
||||
lists:foreach(fun(Domain) -> register_extra_domain(Host, Domain) end,
|
||||
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_DISCO_ITEMS, ?MODULE,
|
||||
process_local_iq_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
||||
?NS_DISCO_INFO, ?MODULE,
|
||||
process_local_iq_info, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_DISCO_ITEMS, ?MODULE, process_sm_iq_items,
|
||||
IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_DISCO_INFO, ?MODULE, process_sm_iq_info,
|
||||
IQDisc),
|
||||
catch ets:new(disco_features,
|
||||
[named_table, ordered_set, public]),
|
||||
register_feature(Host, <<"iq">>),
|
||||
register_feature(Host, <<"presence">>),
|
||||
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),
|
||||
catch ets:new(disco_sm_features, [named_table, ordered_set, public]),
|
||||
catch ets:new(disco_sm_nodes, [named_table, ordered_set, public]),
|
||||
ejabberd_hooks:add(disco_local_items, Host, ?MODULE, get_local_services, 100),
|
||||
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, get_local_features, 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),
|
||||
catch ets:new(disco_sm_features,
|
||||
[named_table, ordered_set, public]),
|
||||
catch ets:new(disco_sm_nodes,
|
||||
[named_table, ordered_set, public]),
|
||||
ejabberd_hooks:add(disco_local_items, Host, ?MODULE,
|
||||
get_local_services, 100),
|
||||
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
|
||||
get_local_features, 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.
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, get_sm_identity, 100),
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 100),
|
||||
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, get_sm_items, 100),
|
||||
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, get_local_identity, 100),
|
||||
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_local_features, 100),
|
||||
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),
|
||||
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE,
|
||||
get_sm_identity, 100),
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
|
||||
get_sm_features, 100),
|
||||
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE,
|
||||
get_sm_items, 100),
|
||||
ejabberd_hooks:delete(disco_local_identity, Host,
|
||||
?MODULE, get_local_identity, 100),
|
||||
ejabberd_hooks:delete(disco_local_features, Host,
|
||||
?MODULE, get_local_features, 100),
|
||||
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_extra_domains, {{'_', Host}}),
|
||||
catch ets:match_delete(disco_extra_domains,
|
||||
{{'_', Host}}),
|
||||
ok.
|
||||
|
||||
|
||||
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}}).
|
||||
|
||||
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}).
|
||||
|
||||
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}}).
|
||||
|
||||
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}).
|
||||
|
||||
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
|
||||
set ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
|
||||
get ->
|
||||
Node = xml:get_tag_attr_s("node", SubEl),
|
||||
Node = xml:get_tag_attr_s(<<"node">>, SubEl),
|
||||
Host = To#jid.lserver,
|
||||
|
||||
case ejabberd_hooks:run_fold(disco_local_items,
|
||||
Host,
|
||||
empty,
|
||||
[From, To, Node, Lang]) of
|
||||
case ejabberd_hooks:run_fold(disco_local_items, Host,
|
||||
empty, [From, To, Node, Lang])
|
||||
of
|
||||
{result, Items} ->
|
||||
ANode = case Node of
|
||||
"" -> [];
|
||||
_ -> [{"node", Node}]
|
||||
<<"">> -> [];
|
||||
_ -> [{<<"node">>, Node}]
|
||||
end,
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", ?NS_DISCO_ITEMS} | ANode],
|
||||
Items
|
||||
}]};
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_DISCO_ITEMS} | ANode],
|
||||
children = Items}]};
|
||||
{error, Error} ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, Error]}
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
process_local_iq_info(From, To, #iq{type = Type, lang = Lang,
|
||||
sub_el = SubEl} = IQ) ->
|
||||
process_local_iq_info(From, To,
|
||||
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
|
||||
case Type of
|
||||
set ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
|
||||
get ->
|
||||
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,
|
||||
Host,
|
||||
[],
|
||||
[From, To, Node, Lang]),
|
||||
Host, [], [From, To, Node, Lang]),
|
||||
Info = ejabberd_hooks:run_fold(disco_info, Host, [],
|
||||
[Host, ?MODULE, Node, Lang]),
|
||||
case ejabberd_hooks:run_fold(disco_local_features,
|
||||
Host,
|
||||
empty,
|
||||
[From, To, Node, Lang]) of
|
||||
case ejabberd_hooks:run_fold(disco_local_features, Host,
|
||||
empty, [From, To, Node, Lang])
|
||||
of
|
||||
{result, Features} ->
|
||||
ANode = case Node of
|
||||
"" -> [];
|
||||
_ -> [{"node", Node}]
|
||||
<<"">> -> [];
|
||||
_ -> [{<<"node">>, Node}]
|
||||
end,
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", ?NS_DISCO_INFO} | ANode],
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_DISCO_INFO} | ANode],
|
||||
children =
|
||||
Identity ++
|
||||
Info ++
|
||||
features_to_xml(Features)
|
||||
}]};
|
||||
Info ++ features_to_xml(Features)}]};
|
||||
{error, Error} ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, Error]}
|
||||
end
|
||||
end.
|
||||
|
||||
get_local_identity(Acc, _From, _To, [], _Lang) ->
|
||||
Acc ++ [{xmlelement, "identity",
|
||||
[{"category", "server"},
|
||||
{"type", "im"},
|
||||
{"name", "ejabberd"}], []}];
|
||||
|
||||
get_local_identity(Acc, _From, _To, <<>>, _Lang) ->
|
||||
Acc ++
|
||||
[#xmlel{name = <<"identity">>,
|
||||
attrs =
|
||||
[{<<"category">>, <<"server">>}, {<<"type">>, <<"im">>},
|
||||
{<<"name">>, <<"ejabberd">>}],
|
||||
children = []}];
|
||||
get_local_identity(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
|
||||
get_local_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
|
||||
get_local_features({error, _Error} = Acc, _From, _To,
|
||||
_Node, _Lang) ->
|
||||
Acc;
|
||||
|
||||
get_local_features(Acc, _From, To, [], _Lang) ->
|
||||
get_local_features(Acc, _From, To, <<>>, _Lang) ->
|
||||
Feats = case Acc of
|
||||
{result, Features} -> Features;
|
||||
empty -> []
|
||||
end,
|
||||
Host = To#jid.lserver,
|
||||
{result,
|
||||
ets:select(disco_features, [{{{'_', Host}}, [], ['$_']}]) ++ Feats};
|
||||
|
||||
ets:select(disco_features,
|
||||
[{{{'_', Host}}, [], ['$_']}])
|
||||
++ Feats};
|
||||
get_local_features(Acc, _From, _To, _Node, _Lang) ->
|
||||
case Acc of
|
||||
{result, _Features} ->
|
||||
Acc;
|
||||
empty ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND}
|
||||
{result, _Features} -> Acc;
|
||||
empty -> {error, ?ERR_ITEM_NOT_FOUND}
|
||||
end.
|
||||
|
||||
|
||||
features_to_xml(FeatureList) ->
|
||||
%% Avoid duplicating features
|
||||
[{xmlelement, "feature", [{"var", Feat}], []} ||
|
||||
Feat <- lists:usort(
|
||||
lists:map(
|
||||
fun({{Feature, _Host}}) ->
|
||||
[#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, Feat}], children = []}
|
||||
|| Feat
|
||||
<- lists:usort(lists:map(fun ({{Feature, _Host}}) ->
|
||||
Feature;
|
||||
(Feature) when is_list(Feature) ->
|
||||
(Feature) when is_binary(Feature) ->
|
||||
Feature
|
||||
end, FeatureList))].
|
||||
end,
|
||||
FeatureList))].
|
||||
|
||||
domain_to_xml({Domain}) ->
|
||||
{xmlelement, "item", [{"jid", Domain}], []};
|
||||
#xmlel{name = <<"item">>, attrs = [{<<"jid">>, Domain}],
|
||||
children = []};
|
||||
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;
|
||||
|
||||
get_local_services(Acc, _From, To, [], _Lang) ->
|
||||
get_local_services(Acc, _From, To, <<>>, _Lang) ->
|
||||
Items = case Acc of
|
||||
{result, Its} -> Its;
|
||||
empty -> []
|
||||
end,
|
||||
Host = To#jid.lserver,
|
||||
{result,
|
||||
lists:usort(
|
||||
lists:map(fun domain_to_xml/1,
|
||||
lists:usort(lists:map(fun domain_to_xml/1,
|
||||
get_vh_services(Host) ++
|
||||
ets:select(disco_extra_domains,
|
||||
[{{{'$1', Host}}, [], ['$1']}]))
|
||||
) ++ Items};
|
||||
|
||||
get_local_services({result, _} = Acc, _From, _To, _Node, _Lang) ->
|
||||
[{{{'$1', Host}}, [], ['$1']}])))
|
||||
++ Items};
|
||||
get_local_services({result, _} = Acc, _From, _To, _Node,
|
||||
_Lang) ->
|
||||
Acc;
|
||||
|
||||
get_local_services(empty, _From, _To, _Node, _Lang) ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND}.
|
||||
|
||||
get_vh_services(Host) ->
|
||||
Hosts = lists:sort(fun(H1, H2) -> length(H1) >= length(H2) end, ?MYHOSTS),
|
||||
lists:filter(fun(H) ->
|
||||
case lists:dropwhile(
|
||||
fun(VH) ->
|
||||
not lists:suffix("." ++ VH, H)
|
||||
end, Hosts) of
|
||||
[] ->
|
||||
false;
|
||||
[VH | _] ->
|
||||
VH == Host
|
||||
Hosts = lists:sort(fun (H1, H2) ->
|
||||
byte_size(H1) >= byte_size(H2)
|
||||
end,
|
||||
?MYHOSTS),
|
||||
lists:filter(fun (H) ->
|
||||
case lists:dropwhile(fun (VH) ->
|
||||
not
|
||||
str:suffix(
|
||||
<<".", VH/binary>>,
|
||||
H)
|
||||
end,
|
||||
Hosts)
|
||||
of
|
||||
[] -> false;
|
||||
[VH | _] -> VH == Host
|
||||
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
|
||||
set ->
|
||||
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
|
||||
true ->
|
||||
Host = To#jid.lserver,
|
||||
Node = xml:get_tag_attr_s("node", SubEl),
|
||||
case ejabberd_hooks:run_fold(disco_sm_items,
|
||||
Host,
|
||||
empty,
|
||||
[From, To, Node, Lang]) of
|
||||
Node = xml:get_tag_attr_s(<<"node">>, SubEl),
|
||||
case ejabberd_hooks:run_fold(disco_sm_items, Host,
|
||||
empty, [From, To, Node, Lang])
|
||||
of
|
||||
{result, Items} ->
|
||||
ANode = case Node of
|
||||
"" -> [];
|
||||
_ -> [{"node", Node}]
|
||||
<<"">> -> [];
|
||||
_ -> [{<<"node">>, Node}]
|
||||
end,
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", ?NS_DISCO_ITEMS} | ANode],
|
||||
Items
|
||||
}]};
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_DISCO_ITEMS}
|
||||
| ANode],
|
||||
children = Items}]};
|
||||
{error, Error} ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, Error]}
|
||||
end;
|
||||
false ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}
|
||||
end
|
||||
end.
|
||||
|
||||
get_sm_items({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
|
||||
get_sm_items({error, _Error} = Acc, _From, _To, _Node,
|
||||
_Lang) ->
|
||||
Acc;
|
||||
|
||||
get_sm_items(Acc, From,
|
||||
#jid{user = User, server = Server} = To,
|
||||
[], _Lang) ->
|
||||
#jid{user = User, server = Server} = To, <<>>, _Lang) ->
|
||||
Items = case Acc of
|
||||
{result, Its} -> Its;
|
||||
empty -> []
|
||||
end,
|
||||
Items1 = case is_presence_subscribed(From, To) of
|
||||
true ->
|
||||
get_user_resources(User, Server);
|
||||
_ ->
|
||||
[]
|
||||
true -> get_user_resources(User, Server);
|
||||
_ -> []
|
||||
end,
|
||||
{result, Items ++ Items1};
|
||||
|
||||
get_sm_items({result, _} = Acc, _From, _To, _Node, _Lang) ->
|
||||
get_sm_items({result, _} = Acc, _From, _To, _Node,
|
||||
_Lang) ->
|
||||
Acc;
|
||||
|
||||
get_sm_items(empty, From, To, _Node, _Lang) ->
|
||||
#jid{luser = LFrom, lserver = LSFrom} = From,
|
||||
#jid{luser = LTo, lserver = LSTo} = To,
|
||||
case {LFrom, LSFrom} of
|
||||
{LTo, LSTo} ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND};
|
||||
_ ->
|
||||
{error, ?ERR_NOT_ALLOWED}
|
||||
{LTo, LSTo} -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
_ -> {error, ?ERR_NOT_ALLOWED}
|
||||
end.
|
||||
|
||||
is_presence_subscribed(#jid{luser=User, lserver=Server}, #jid{luser=LUser, lserver=LServer}) ->
|
||||
lists:any(fun(#roster{jid = {TUser, TServer, _}, subscription = S}) ->
|
||||
if
|
||||
LUser == TUser, LServer == TServer, S/=none ->
|
||||
is_presence_subscribed(#jid{luser = User,
|
||||
lserver = Server},
|
||||
#jid{luser = LUser, lserver = LServer}) ->
|
||||
lists:any(fun (#roster{jid = {TUser, TServer, _},
|
||||
subscription = S}) ->
|
||||
if LUser == TUser, LServer == TServer, S /= none ->
|
||||
true;
|
||||
true ->
|
||||
false
|
||||
true -> false
|
||||
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.
|
||||
|
||||
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
|
||||
set ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
|
||||
@ -347,124 +374,120 @@ process_sm_iq_info(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ)
|
||||
case is_presence_subscribed(From, To) of
|
||||
true ->
|
||||
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,
|
||||
Host,
|
||||
[],
|
||||
Host, [],
|
||||
[From, To, Node, Lang]),
|
||||
case ejabberd_hooks:run_fold(disco_sm_features,
|
||||
Host,
|
||||
empty,
|
||||
[From, To, Node, Lang]) of
|
||||
case ejabberd_hooks:run_fold(disco_sm_features, Host,
|
||||
empty, [From, To, Node, Lang])
|
||||
of
|
||||
{result, Features} ->
|
||||
ANode = case Node of
|
||||
"" -> [];
|
||||
_ -> [{"node", Node}]
|
||||
<<"">> -> [];
|
||||
_ -> [{<<"node">>, Node}]
|
||||
end,
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", ?NS_DISCO_INFO} | ANode],
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_DISCO_INFO}
|
||||
| ANode],
|
||||
children =
|
||||
Identity ++
|
||||
features_to_xml(Features)
|
||||
}]};
|
||||
features_to_xml(Features)}]};
|
||||
{error, Error} ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, Error]}
|
||||
end;
|
||||
false ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}
|
||||
end
|
||||
end.
|
||||
|
||||
get_sm_identity(Acc, _From, #jid{luser = LUser, lserver=LServer}, _Node, _Lang) ->
|
||||
Acc ++ case ejabberd_auth:is_user_exists(LUser, LServer) of
|
||||
get_sm_identity(Acc, _From,
|
||||
#jid{luser = LUser, lserver = LServer}, _Node, _Lang) ->
|
||||
Acc ++
|
||||
case ejabberd_auth:is_user_exists(LUser, LServer) of
|
||||
true ->
|
||||
[{xmlelement, "identity", [{"category", "account"},
|
||||
{"type", "registered"}], []}];
|
||||
_ ->
|
||||
[]
|
||||
[#xmlel{name = <<"identity">>,
|
||||
attrs =
|
||||
[{<<"category">>, <<"account">>},
|
||||
{<<"type">>, <<"registered">>}],
|
||||
children = []}];
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
|
||||
get_sm_features(empty, From, To, _Node, _Lang) ->
|
||||
#jid{luser = LFrom, lserver = LSFrom} = From,
|
||||
#jid{luser = LTo, lserver = LSTo} = To,
|
||||
case {LFrom, LSFrom} of
|
||||
{LTo, LSTo} ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND};
|
||||
_ ->
|
||||
{error, ?ERR_NOT_ALLOWED}
|
||||
{LTo, LSTo} -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
_ -> {error, ?ERR_NOT_ALLOWED}
|
||||
end;
|
||||
|
||||
get_sm_features(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
get_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc.
|
||||
|
||||
get_user_resources(User, Server) ->
|
||||
Rs = ejabberd_sm:get_user_resources(User, Server),
|
||||
lists:map(fun(R) ->
|
||||
{xmlelement, "item",
|
||||
[{"jid", User ++ "@" ++ Server ++ "/" ++ R},
|
||||
{"name", User}], []}
|
||||
end, lists:sort(Rs)).
|
||||
lists:map(fun (R) ->
|
||||
#xmlel{name = <<"item">>,
|
||||
attrs =
|
||||
[{<<"jid">>,
|
||||
<<User/binary, "@", Server/binary, "/",
|
||||
R/binary>>},
|
||||
{<<"name">>, User}],
|
||||
children = []}
|
||||
end,
|
||||
lists:sort(Rs)).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
%%% 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
|
||||
undefined ->
|
||||
?MODULE;
|
||||
_ ->
|
||||
Mod
|
||||
undefined -> ?MODULE;
|
||||
_ -> Mod
|
||||
end,
|
||||
Serverinfo_fields = get_fields_xml(Host, Module),
|
||||
[{xmlelement, "x",
|
||||
[{"xmlns", ?NS_XDATA}, {"type", "result"}],
|
||||
[{xmlelement, "field",
|
||||
[{"var", "FORM_TYPE"}, {"type", "hidden"}],
|
||||
[{xmlelement, "value",
|
||||
[],
|
||||
[{xmlcdata, ?NS_SERVERINFO}]
|
||||
}]
|
||||
}]
|
||||
++ Serverinfo_fields
|
||||
}];
|
||||
|
||||
get_info(Acc, _, _, _Node, _) ->
|
||||
Acc.
|
||||
[#xmlel{name = <<"x">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}],
|
||||
children =
|
||||
[#xmlel{name = <<"field">>,
|
||||
attrs =
|
||||
[{<<"var">>, <<"FORM_TYPE">>},
|
||||
{<<"type">>, <<"hidden">>}],
|
||||
children =
|
||||
[#xmlel{name = <<"value">>, attrs = [],
|
||||
children = [{xmlcdata, ?NS_SERVERINFO}]}]}]
|
||||
++ Serverinfo_fields}];
|
||||
get_info(Acc, _, _, _Node, _) -> Acc.
|
||||
|
||||
get_fields_xml(Host, Module) ->
|
||||
Fields = gen_mod:get_module_opt(Host, ?MODULE, server_info, []),
|
||||
|
||||
%% filter, and get only the ones allowed for this module
|
||||
Fields_good = lists:filter(
|
||||
fun({Modules, _, _}) ->
|
||||
Fields = gen_mod:get_module_opt(Host, ?MODULE, server_info,
|
||||
fun(L) when is_list(L) -> L end,
|
||||
[]),
|
||||
Fields_good = lists:filter(fun ({Modules, _, _}) ->
|
||||
case Modules of
|
||||
all -> true;
|
||||
Modules -> lists:member(Module, Modules)
|
||||
Modules ->
|
||||
lists:member(Module, Modules)
|
||||
end
|
||||
end,
|
||||
Fields),
|
||||
|
||||
fields_to_xml(Fields_good).
|
||||
|
||||
fields_to_xml(Fields) ->
|
||||
[ field_to_xml(Field) || Field <- Fields].
|
||||
[field_to_xml(Field) || Field <- Fields].
|
||||
|
||||
field_to_xml({_, Var, Values}) ->
|
||||
Values_xml = values_to_xml(Values),
|
||||
{xmlelement, "field",
|
||||
[{"var", Var}],
|
||||
Values_xml
|
||||
}.
|
||||
#xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}],
|
||||
children = Values_xml}.
|
||||
|
||||
values_to_xml(Values) ->
|
||||
lists:map(
|
||||
fun(Value) ->
|
||||
{xmlelement, "value",
|
||||
[],
|
||||
[{xmlcdata, Value}]
|
||||
}
|
||||
lists:map(fun (Value) ->
|
||||
#xmlel{name = <<"value">>, attrs = [],
|
||||
children = [{xmlcdata, Value}]}
|
||||
end,
|
||||
Values
|
||||
).
|
||||
Values).
|
||||
|
@ -25,22 +25,26 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_echo).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
%% 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
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
|
||||
-record(state, {host}).
|
||||
-record(state, {host = <<"">> :: binary()}).
|
||||
|
||||
-define(PROCNAME, ejabberd_mod_echo).
|
||||
|
||||
@ -53,17 +57,13 @@
|
||||
%%--------------------------------------------------------------------
|
||||
start_link(Host, Opts) ->
|
||||
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) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
ChildSpec =
|
||||
{Proc,
|
||||
{?MODULE, start_link, [Host, Opts]},
|
||||
temporary,
|
||||
1000,
|
||||
worker,
|
||||
[?MODULE]},
|
||||
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
|
||||
temporary, 1000, worker, [?MODULE]},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||
|
||||
stop(Host) ->
|
||||
@ -72,7 +72,6 @@ stop(Host) ->
|
||||
supervisor:terminate_child(ejabberd_sup, Proc),
|
||||
supervisor:delete_child(ejabberd_sup, Proc).
|
||||
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
@ -85,7 +84,8 @@ stop(Host) ->
|
||||
%% Description: Initiates the server
|
||||
%%--------------------------------------------------------------------
|
||||
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),
|
||||
{ok, #state{host = MyHost}}.
|
||||
|
||||
@ -107,8 +107,7 @@ handle_call(stop, _From, State) ->
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
handle_cast(_Msg, 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) ->
|
||||
Packet2 = case From#jid.user of
|
||||
"" -> jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST);
|
||||
<<"">> ->
|
||||
jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST);
|
||||
_ -> Packet
|
||||
end,
|
||||
do_client_version(disabled, To, From), % Put 'enabled' to enable it
|
||||
do_client_version(disabled, To, From),
|
||||
ejabberd_router:route(To, From, Packet2),
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
handle_info(_Info, State) -> {noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: terminate(Reason, State) -> void()
|
||||
@ -135,15 +134,13 @@ handle_info(_Info, State) ->
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, State) ->
|
||||
ejabberd_router:unregister_route(State#state.host),
|
||||
ok.
|
||||
ejabberd_router:unregister_route(State#state.host), ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
%% Description: Convert process state when code is changed
|
||||
%%--------------------------------------------------------------------
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% 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
|
||||
%% try to guarantee that the received response matches the request sent.
|
||||
%% Finally, the received response is printed in the ejabberd log file.
|
||||
do_client_version(disabled, _From, _To) ->
|
||||
ok;
|
||||
do_client_version(disabled, _From, _To) -> ok;
|
||||
do_client_version(enabled, From, To) ->
|
||||
ToS = jlib:jid_to_string(To),
|
||||
%% It is important to identify this process and packet
|
||||
Random_resource = integer_to_list(random:uniform(100000)),
|
||||
Random_resource =
|
||||
iolist_to_binary(integer_to_list(random:uniform(100000))),
|
||||
From2 = From#jid{resource = Random_resource,
|
||||
lresource = Random_resource},
|
||||
|
||||
%% Build an iq:query request
|
||||
Packet = {xmlelement, "iq",
|
||||
[{"to", ToS}, {"type", "get"}],
|
||||
[{xmlelement, "query", [{"xmlns", ?NS_VERSION}], []}]},
|
||||
|
||||
%% Send the request
|
||||
Packet = #xmlel{name = <<"iq">>,
|
||||
attrs = [{<<"to">>, ToS}, {<<"type">>, <<"get">>}],
|
||||
children =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_VERSION}],
|
||||
children = []}]},
|
||||
ejabberd_router:route(From2, To, Packet),
|
||||
|
||||
%% Wait to receive the response
|
||||
%% It is very important to only accept a packet which is the
|
||||
%% response to the request that he sent
|
||||
Els = receive {route, To, From2, IQ} ->
|
||||
{xmlelement, "query", _, List} = xml:get_subtag(IQ, "query"),
|
||||
Els = receive
|
||||
{route, To, From2, IQ} ->
|
||||
#xmlel{name = <<"query">>, children = List} =
|
||||
xml:get_subtag(IQ, <<"query">>),
|
||||
List
|
||||
after 5000 -> % Timeout in miliseconds: 5 seconds
|
||||
[]
|
||||
end,
|
||||
Values = [{Name, Value} || {xmlelement,Name,[],[{xmlcdata,Value}]} <- Els],
|
||||
|
||||
%% Print in log
|
||||
Values_string1 = [io_lib:format("~n~s: ~p", [N, V]) || {N, V} <- Values],
|
||||
Values_string2 = lists:concat(Values_string1),
|
||||
?INFO_MSG("Information of the client: ~s~s", [ToS, Values_string2]).
|
||||
|
||||
Values = [{Name, Value}
|
||||
|| #xmlel{name = Name, attrs = [],
|
||||
children = [{xmlcdata, Value}]}
|
||||
<- Els],
|
||||
Values_string1 = [io_lib:format("~n~s: ~p", [N, V])
|
||||
|| {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).
|
||||
|
||||
-author('mremond@process-one.net').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
%% API:
|
||||
-export([start/2,
|
||||
preinit/2,
|
||||
init/1,
|
||||
stop/1]).
|
||||
-export([start/2, preinit/2, init/1, stop/1]).
|
||||
|
||||
-export([update_bl_c2s/0]).
|
||||
|
||||
%% Hooks:
|
||||
-export([is_ip_in_c2s_blacklist/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-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(bl_c2s, {ip}).
|
||||
|
||||
%% Start once for all vhost
|
||||
-record(bl_c2s, {ip = <<"">> :: binary()}).
|
||||
|
||||
start(_Host, _Opts) ->
|
||||
Pid = spawn(?MODULE, preinit, [self(), #state{}]),
|
||||
receive {ok, Pid, PreinitResult} ->
|
||||
PreinitResult
|
||||
end.
|
||||
receive {ok, Pid, PreinitResult} -> PreinitResult end.
|
||||
|
||||
preinit(Parent, State) ->
|
||||
Pid = self(),
|
||||
try register(?PROCNAME, Pid) of
|
||||
true ->
|
||||
Parent ! {ok, Pid, true},
|
||||
init(State)
|
||||
catch error:_ ->
|
||||
Parent ! {ok, Pid, true}
|
||||
true -> Parent ! {ok, Pid, true}, init(State)
|
||||
catch
|
||||
error:_ -> Parent ! {ok, Pid, true}
|
||||
end.
|
||||
|
||||
%% TODO:
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
stop(_Host) -> ok.
|
||||
|
||||
init(State)->
|
||||
init(State) ->
|
||||
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(),
|
||||
%% Register hooks for blacklist
|
||||
ejabberd_hooks:add(check_bl_c2s, ?MODULE, is_ip_in_c2s_blacklist, 50),
|
||||
%% Set timer: Download the blacklist file every 6 hours
|
||||
timer:apply_interval(timer:hours(?UPDATE_INTERVAL), ?MODULE, update_bl_c2s, []),
|
||||
ejabberd_hooks:add(check_bl_c2s, ?MODULE,
|
||||
is_ip_in_c2s_blacklist, 50),
|
||||
timer:apply_interval(timer:hours(?UPDATE_INTERVAL),
|
||||
?MODULE, update_bl_c2s, []),
|
||||
loop(State).
|
||||
|
||||
%% Remove timer when stop is received.
|
||||
loop(_State) ->
|
||||
receive
|
||||
stop ->
|
||||
ok
|
||||
end.
|
||||
loop(_State) -> receive stop -> ok end.
|
||||
|
||||
%% Download blacklist file from ProcessOne XAAI
|
||||
%% and update the table internal table
|
||||
@ -93,15 +89,17 @@ loop(_State) ->
|
||||
update_bl_c2s() ->
|
||||
?INFO_MSG("Updating C2S Blacklist", []),
|
||||
case httpc:request(?BLC2S) of
|
||||
{ok, {{_Version, 200, _Reason}, _Headers, Body}} ->
|
||||
IPs = string:tokens(Body,"\n"),
|
||||
{ok, 200, _Headers, Body} ->
|
||||
IPs = str:tokens(Body, <<"\n">>),
|
||||
ets:delete_all_objects(bl_c2s),
|
||||
lists:foreach(
|
||||
fun(IP) ->
|
||||
ets:insert(bl_c2s, #bl_c2s{ip=list_to_binary(IP)})
|
||||
end, IPs);
|
||||
lists:foreach(fun (IP) ->
|
||||
ets:insert(bl_c2s,
|
||||
#bl_c2s{ip = IP})
|
||||
end,
|
||||
IPs);
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG("Cannot download C2S blacklist file. Reason: ~p",
|
||||
?ERROR_MSG("Cannot download C2S blacklist file. "
|
||||
"Reason: ~p",
|
||||
[Reason])
|
||||
end.
|
||||
|
||||
@ -111,16 +109,15 @@ update_bl_c2s() ->
|
||||
%% true: IP is blacklisted
|
||||
%% IPV4 IP tuple:
|
||||
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
|
||||
[] -> %% Not in blacklist
|
||||
false;
|
||||
[_] -> %% Blacklisted!
|
||||
{stop, true}
|
||||
[_] -> {stop, true}
|
||||
end;
|
||||
is_ip_in_c2s_blacklist(_Val, _IP) ->
|
||||
false.
|
||||
is_ip_in_c2s_blacklist(_Val, _IP) -> false.
|
||||
|
||||
%% TODO:
|
||||
%% - For now, we do not kick user already logged on a given IP after
|
||||
%% we update the blacklist.
|
||||
|
||||
|
@ -24,7 +24,7 @@ EFLAGS += -pz ..
|
||||
|
||||
# make debug=true to compile Erlang module with debug informations.
|
||||
ifdef debug
|
||||
EFLAGS+=+debug_info +export_all
|
||||
EFLAGS+=+debug_info
|
||||
endif
|
||||
|
||||
ERLSHLIBS = ../iconv_erl.so
|
||||
|
@ -25,6 +25,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(iconv).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_server).
|
||||
@ -32,23 +33,20 @@
|
||||
-export([start/0, start_link/0, convert/3]).
|
||||
|
||||
%% Internal exports, call-back functions.
|
||||
-export([init/1,
|
||||
handle_call/3,
|
||||
handle_cast/2,
|
||||
handle_info/2,
|
||||
code_change/3,
|
||||
terminate/2]).
|
||||
|
||||
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, code_change/3, terminate/2]).
|
||||
|
||||
start() ->
|
||||
gen_server:start({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [],
|
||||
[]).
|
||||
|
||||
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;
|
||||
{error, already_loaded} -> ok
|
||||
end,
|
||||
@ -57,38 +55,28 @@ init([]) ->
|
||||
ets:insert(iconv_table, {port, Port}),
|
||||
{ok, Port}.
|
||||
|
||||
|
||||
%%% --------------------------------------------------------
|
||||
%%% The call-back functions.
|
||||
%%% --------------------------------------------------------
|
||||
|
||||
handle_call(_, _, State) ->
|
||||
{noreply, State}.
|
||||
handle_call(_, _, State) -> {noreply, State}.
|
||||
|
||||
handle_cast(_, State) ->
|
||||
{noreply, State}.
|
||||
handle_cast(_, State) -> {noreply, State}.
|
||||
|
||||
handle_info({'EXIT', Port, Reason}, Port) ->
|
||||
{stop, {port_died, Reason}, Port};
|
||||
handle_info({'EXIT', _Pid, _Reason}, Port) ->
|
||||
{noreply, Port};
|
||||
handle_info(_, State) ->
|
||||
{noreply, State}.
|
||||
handle_info(_, State) -> {noreply, State}.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
terminate(_Reason, Port) ->
|
||||
Port ! {self, close},
|
||||
ok.
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
terminate(_Reason, Port) -> Port ! {self, close}, ok.
|
||||
|
||||
-spec convert(binary(), binary(), binary()) -> binary().
|
||||
|
||||
convert(From, To, String) ->
|
||||
[{port, Port} | _] = ets:lookup(iconv_table, port),
|
||||
Bin = term_to_binary({From, To, String}),
|
||||
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
271
src/mod_last.erl
271
src/mod_last.erl
@ -25,28 +25,28 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_last).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2,
|
||||
stop/1,
|
||||
process_local_iq/3,
|
||||
process_sm_iq/3,
|
||||
on_presence_update/4,
|
||||
store_last_info/4,
|
||||
get_last_info/2,
|
||||
remove_user/2]).
|
||||
-export([start/2, stop/1, process_local_iq/3, export/1,
|
||||
process_sm_iq/3, on_presence_update/4,
|
||||
store_last_info/4, get_last_info/2, remove_user/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-include("jlib.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) ->
|
||||
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
|
||||
mnesia ->
|
||||
mnesia:create_table(last_activity,
|
||||
@ -54,63 +54,75 @@ start(Host, Opts) ->
|
||||
{attributes,
|
||||
record_info(fields, last_activity)}]),
|
||||
update_table();
|
||||
_ ->
|
||||
ok
|
||||
_ -> ok
|
||||
end,
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_LAST,
|
||||
?MODULE, process_local_iq, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_LAST,
|
||||
?MODULE, process_sm_iq, IQDisc),
|
||||
ejabberd_hooks:add(remove_user, Host,
|
||||
?MODULE, remove_user, 50),
|
||||
ejabberd_hooks:add(unset_presence_hook, Host,
|
||||
?MODULE, on_presence_update, 50).
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
||||
?NS_LAST, ?MODULE, process_local_iq, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_LAST, ?MODULE, process_sm_iq, IQDisc),
|
||||
ejabberd_hooks:add(remove_user, Host, ?MODULE,
|
||||
remove_user, 50),
|
||||
ejabberd_hooks:add(unset_presence_hook, Host, ?MODULE,
|
||||
on_presence_update, 50).
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(remove_user, Host,
|
||||
?MODULE, remove_user, 50),
|
||||
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
|
||||
remove_user, 50),
|
||||
ejabberd_hooks:delete(unset_presence_hook, Host,
|
||||
?MODULE, on_presence_update, 50),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_LAST),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_LAST).
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
|
||||
?NS_LAST),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
|
||||
?NS_LAST).
|
||||
|
||||
%%%
|
||||
%%% 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
|
||||
set ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
|
||||
get ->
|
||||
Sec = get_node_uptime(),
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", ?NS_LAST},
|
||||
{"seconds", integer_to_list(Sec)}],
|
||||
[]}]}
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_LAST},
|
||||
{<<"seconds">>,
|
||||
iolist_to_binary(integer_to_list(Sec))}],
|
||||
children = []}]}
|
||||
end.
|
||||
|
||||
%% @spec () -> integer()
|
||||
%% @doc Get the uptime of the ejabberd node, expressed in seconds.
|
||||
%% When ejabberd is starting, ejabberd_config:start/0 stores the datetime.
|
||||
get_node_uptime() ->
|
||||
case ejabberd_config:get_local_option(node_start) of
|
||||
{_, _, _} = StartNow ->
|
||||
now_to_seconds(now()) - now_to_seconds(StartNow);
|
||||
_undefined ->
|
||||
trunc(element(1, erlang:statistics(wall_clock))/1000)
|
||||
case ejabberd_config:get_local_option(
|
||||
node_start,
|
||||
fun({MegaSecs, Secs, MicroSecs} = Now)
|
||||
when is_integer(MegaSecs), MegaSecs >= 0,
|
||||
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.
|
||||
|
||||
now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
|
||||
MegaSecs * 1000000 + Secs.
|
||||
|
||||
|
||||
%%%
|
||||
%%% 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
|
||||
set ->
|
||||
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,
|
||||
Server = To#jid.lserver,
|
||||
{Subscription, _Groups} =
|
||||
ejabberd_hooks:run_fold(
|
||||
roster_get_jid_info, Server,
|
||||
ejabberd_hooks:run_fold(roster_get_jid_info, Server,
|
||||
{none, []}, [User, Server, From]),
|
||||
if
|
||||
(Subscription == both) or (Subscription == from)
|
||||
or ((From#jid.luser == To#jid.luser)
|
||||
and (From#jid.lserver == To#jid.lserver)) ->
|
||||
UserListRecord = ejabberd_hooks:run_fold(
|
||||
privacy_get_user_list, Server,
|
||||
#userlist{},
|
||||
[User, Server]),
|
||||
case ejabberd_hooks:run_fold(
|
||||
privacy_check_packet, Server,
|
||||
allow,
|
||||
if (Subscription == both) or (Subscription == from) or
|
||||
(From#jid.luser == To#jid.luser) and
|
||||
(From#jid.lserver == To#jid.lserver) ->
|
||||
UserListRecord =
|
||||
ejabberd_hooks:run_fold(privacy_get_user_list, Server,
|
||||
#userlist{}, [User, Server]),
|
||||
case ejabberd_hooks:run_fold(privacy_check_packet,
|
||||
Server, allow,
|
||||
[User, Server, UserListRecord,
|
||||
{To, From,
|
||||
{xmlelement, "presence", [], []}},
|
||||
out]) of
|
||||
allow ->
|
||||
get_last_iq(IQ, SubEl, User, Server);
|
||||
#xmlel{name = <<"presence">>,
|
||||
attrs = [],
|
||||
children = []}},
|
||||
out])
|
||||
of
|
||||
allow -> get_last_iq(IQ, SubEl, User, Server);
|
||||
deny ->
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_FORBIDDEN]}
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
|
||||
end;
|
||||
true ->
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_FORBIDDEN]}
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (LUser::string(), LServer::string()) ->
|
||||
%% {ok, TimeStamp::integer(), Status::string()} | not_found | {error, Reason}
|
||||
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) ->
|
||||
case catch mnesia:dirty_read(last_activity, {LUser, LServer}) of
|
||||
{'EXIT', Reason} ->
|
||||
{error, Reason};
|
||||
[] ->
|
||||
not_found;
|
||||
[#last_activity{timestamp = TimeStamp, status = Status}] ->
|
||||
case catch mnesia:dirty_read(last_activity,
|
||||
{LUser, LServer})
|
||||
of
|
||||
{'EXIT', Reason} -> {error, Reason};
|
||||
[] -> not_found;
|
||||
[#last_activity{timestamp = TimeStamp,
|
||||
status = Status}] ->
|
||||
{ok, TimeStamp, Status}
|
||||
end;
|
||||
get_last(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case catch odbc_queries:get_last(LServer, Username) of
|
||||
{selected, ["seconds","state"], []} ->
|
||||
{selected, [<<"seconds">>, <<"state">>], []} ->
|
||||
not_found;
|
||||
{selected, ["seconds","state"], [{STimeStamp, Status}]} ->
|
||||
case catch list_to_integer(STimeStamp) of
|
||||
{selected, [<<"seconds">>, <<"state">>],
|
||||
[[STimeStamp, Status]]} ->
|
||||
case catch jlib:binary_to_integer(STimeStamp) of
|
||||
TimeStamp when is_integer(TimeStamp) ->
|
||||
{ok, TimeStamp, Status};
|
||||
Reason ->
|
||||
{error, {invalid_timestamp, Reason}}
|
||||
Reason -> {error, {invalid_timestamp, Reason}}
|
||||
end;
|
||||
Reason ->
|
||||
{error, {invalid_result, Reason}}
|
||||
Reason -> {error, {invalid_result, Reason}}
|
||||
end.
|
||||
|
||||
get_last_iq(IQ, SubEl, LUser, LServer) ->
|
||||
@ -183,24 +192,31 @@ get_last_iq(IQ, SubEl, LUser, LServer) ->
|
||||
[] ->
|
||||
case get_last(LUser, LServer) of
|
||||
{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 ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
|
||||
{ok, TimeStamp, Status} ->
|
||||
TimeStamp2 = now_to_seconds(now()),
|
||||
Sec = TimeStamp2 - TimeStamp,
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", ?NS_LAST},
|
||||
{"seconds", integer_to_list(Sec)}],
|
||||
[{xmlcdata, Status}]}]}
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_LAST},
|
||||
{<<"seconds">>,
|
||||
iolist_to_binary(integer_to_list(Sec))}],
|
||||
children = [{xmlcdata, Status}]}]}
|
||||
end;
|
||||
_ ->
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", ?NS_LAST},
|
||||
{"seconds", "0"}],
|
||||
[]}]}
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_LAST},
|
||||
{<<"seconds">>, <<"0">>}],
|
||||
children = []}]}
|
||||
end.
|
||||
|
||||
on_presence_update(User, Server, _Resource, Status) ->
|
||||
@ -211,30 +227,33 @@ store_last_info(User, Server, TimeStamp, Status) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
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},
|
||||
F = fun() ->
|
||||
F = fun () ->
|
||||
mnesia:write(#last_activity{us = US,
|
||||
timestamp = TimeStamp,
|
||||
status = Status})
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
store_last_info(LUser, LServer, TimeStamp, Status, odbc) ->
|
||||
store_last_info(LUser, LServer, TimeStamp, Status,
|
||||
odbc) ->
|
||||
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),
|
||||
odbc_queries:set_last_t(LServer, Username, Seconds, State).
|
||||
odbc_queries:set_last_t(LServer, Username, Seconds,
|
||||
State).
|
||||
|
||||
%% @spec (LUser::string(), LServer::string()) ->
|
||||
%% {ok, TimeStamp::integer(), Status::string()} | not_found
|
||||
get_last_info(LUser, LServer) ->
|
||||
case get_last(LUser, LServer) of
|
||||
{error, _Reason} ->
|
||||
not_found;
|
||||
Res ->
|
||||
Res
|
||||
{error, _Reason} -> not_found;
|
||||
Res -> Res
|
||||
end.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
@ -245,9 +264,7 @@ remove_user(User, Server) ->
|
||||
|
||||
remove_user(LUser, LServer, mnesia) ->
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
mnesia:delete({last_activity, US})
|
||||
end,
|
||||
F = fun () -> mnesia:delete({last_activity, US}) end,
|
||||
mnesia:transaction(F);
|
||||
remove_user(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
@ -257,47 +274,33 @@ update_table() ->
|
||||
Fields = record_info(fields, last_activity),
|
||||
case mnesia:table_info(last_activity, attributes) of
|
||||
Fields ->
|
||||
ok;
|
||||
[user, timestamp, status] ->
|
||||
?INFO_MSG("Converting last_activity table from {user, timestamp, status} format", []),
|
||||
Host = ?MYNAME,
|
||||
mnesia:transform_table(last_activity, ignore, 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);
|
||||
[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);
|
||||
ejabberd_config:convert_table_to_binary(
|
||||
last_activity, Fields, set,
|
||||
fun(#last_activity{us = {U, _}}) -> U end,
|
||||
fun(#last_activity{us = {U, S}, status = Status} = R) ->
|
||||
R#last_activity{us = {iolist_to_binary(U),
|
||||
iolist_to_binary(S)},
|
||||
status = iolist_to_binary(Status)}
|
||||
end);
|
||||
_ ->
|
||||
?INFO_MSG("Recreating last_activity table", []),
|
||||
mnesia:transform_table(last_activity, ignore, Fields)
|
||||
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.
|
||||
ifdef debug
|
||||
EFLAGS+=+debug_info +export_all
|
||||
EFLAGS+=+debug_info
|
||||
endif
|
||||
|
||||
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(SETS, gb_sets).
|
||||
|
||||
-define(DICT, dict).
|
||||
|
||||
-record(lqueue, {queue, len, max}).
|
||||
-record(lqueue,
|
||||
{
|
||||
queue :: queue(),
|
||||
len :: integer(),
|
||||
max :: integer()
|
||||
}).
|
||||
|
||||
-record(config, {title = "",
|
||||
description = "",
|
||||
allow_change_subj = true,
|
||||
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()
|
||||
}).
|
||||
-type lqueue() :: #lqueue{}.
|
||||
|
||||
-record(user, {jid,
|
||||
nick,
|
||||
role,
|
||||
last_presence}).
|
||||
-record(config,
|
||||
{
|
||||
title = <<"">> :: binary(),
|
||||
description = <<"">> :: binary(),
|
||||
allow_change_subj = true :: boolean(),
|
||||
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(activity, {message_time = 0,
|
||||
presence_time = 0,
|
||||
message_shaper,
|
||||
presence_shaper,
|
||||
message,
|
||||
presence}).
|
||||
-type config() :: #config{}.
|
||||
|
||||
-record(state, {room,
|
||||
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()}).
|
||||
-type role() :: moderator | participant | visitor | none.
|
||||
|
||||
-record(muc_online_users, {us,
|
||||
resource,
|
||||
room,
|
||||
host}).
|
||||
-record(user,
|
||||
{
|
||||
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
163
src/mod_ping.erl
163
src/mod_ping.erl
@ -25,18 +25,24 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_ping).
|
||||
|
||||
-author('bjc@kublai.com').
|
||||
|
||||
-behavior(gen_mod).
|
||||
|
||||
-behavior(gen_server).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
|
||||
-define(SUPERVISOR, ejabberd_sup).
|
||||
-define(NS_PING, "urn:xmpp:ping").
|
||||
-define(DEFAULT_SEND_PINGS, false). % bool()
|
||||
-define(DEFAULT_PING_INTERVAL, 60). % seconds
|
||||
|
||||
-define(NS_PING, <<"urn:xmpp:ping">>).
|
||||
|
||||
-define(DEFAULT_SEND_PINGS, false).
|
||||
|
||||
-define(DEFAULT_PING_INTERVAL, 60).
|
||||
|
||||
-define(DICT, dict).
|
||||
|
||||
@ -47,24 +53,27 @@
|
||||
-export([start/2, stop/1]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, terminate/2, handle_call/3, handle_cast/2,
|
||||
handle_info/2, code_change/3]).
|
||||
-export([init/1, terminate/2, handle_call/3,
|
||||
handle_cast/2, handle_info/2, code_change/3]).
|
||||
|
||||
%% 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 = "",
|
||||
send_pings = ?DEFAULT_SEND_PINGS,
|
||||
ping_interval = ?DEFAULT_PING_INTERVAL,
|
||||
timeout_action = none,
|
||||
timers = ?DICT:new()}).
|
||||
-record(state,
|
||||
{host = <<"">>,
|
||||
send_pings = ?DEFAULT_SEND_PINGS :: boolean(),
|
||||
ping_interval = ?DEFAULT_PING_INTERVAL :: non_neg_integer(),
|
||||
timeout_action = none :: none | kill,
|
||||
timers = (?DICT):new() :: dict()}).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
start_link(Host, Opts) ->
|
||||
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) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
@ -92,43 +101,50 @@ stop(Host) ->
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
init([Host, Opts]) ->
|
||||
SendPings = gen_mod:get_opt(send_pings, Opts, ?DEFAULT_SEND_PINGS),
|
||||
PingInterval = gen_mod:get_opt(ping_interval, Opts, ?DEFAULT_PING_INTERVAL),
|
||||
TimeoutAction = gen_mod:get_opt(timeout_action, Opts, none),
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, no_queue),
|
||||
SendPings = gen_mod:get_opt(send_pings, Opts,
|
||||
fun(B) when is_boolean(B) -> B end,
|
||||
?DEFAULT_SEND_PINGS),
|
||||
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),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PING,
|
||||
?MODULE, iq_ping, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PING,
|
||||
?MODULE, iq_ping, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_PING, ?MODULE, iq_ping, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
||||
?NS_PING, ?MODULE, iq_ping, IQDisc),
|
||||
case SendPings of
|
||||
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,
|
||||
?MODULE, user_online, 100),
|
||||
ejabberd_hooks:add(sm_remove_connection_hook, Host,
|
||||
?MODULE, user_offline, 100),
|
||||
ejabberd_hooks:add(user_send_packet, Host,
|
||||
?MODULE, user_send, 100);
|
||||
_ ->
|
||||
ok
|
||||
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
|
||||
user_send, 100);
|
||||
_ -> ok
|
||||
end,
|
||||
{ok, #state{host = Host,
|
||||
send_pings = SendPings,
|
||||
{ok,
|
||||
#state{host = Host, send_pings = SendPings,
|
||||
ping_interval = PingInterval,
|
||||
timeout_action = TimeoutAction,
|
||||
timers = ?DICT:new()}}.
|
||||
timers = (?DICT):new()}}.
|
||||
|
||||
terminate(_Reason, #state{host = Host}) ->
|
||||
ejabberd_hooks:delete(sm_remove_connection_hook, Host,
|
||||
?MODULE, user_offline, 100),
|
||||
ejabberd_hooks:delete(sm_register_connection_hook, Host,
|
||||
?MODULE, user_online, 100),
|
||||
ejabberd_hooks:delete(user_send_packet, Host,
|
||||
?MODULE, user_send, 100),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PING),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PING),
|
||||
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
|
||||
user_send, 100),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
|
||||
?NS_PING),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
|
||||
?NS_PING),
|
||||
mod_disco:unregister_feature(Host, ?NS_PING).
|
||||
|
||||
handle_call(stop, _From, State) ->
|
||||
@ -137,56 +153,60 @@ handle_call(_Req, _From, State) ->
|
||||
{reply, {error, badarg}, 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}};
|
||||
handle_cast({stop_ping, JID}, State) ->
|
||||
Timers = del_timer(JID, State#state.timers),
|
||||
{noreply, State#state{timers = Timers}};
|
||||
handle_cast({iq_pong, JID, timeout}, State) ->
|
||||
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
|
||||
kill ->
|
||||
#jid{user = User, server = Server, resource = Resource} = JID,
|
||||
case ejabberd_sm:get_session_pid(User, Server, Resource) of
|
||||
Pid when is_pid(Pid) ->
|
||||
ejabberd_c2s:stop(Pid);
|
||||
_ ->
|
||||
ok
|
||||
#jid{user = User, server = Server,
|
||||
resource = Resource} =
|
||||
JID,
|
||||
case ejabberd_sm:get_session_pid(User, Server, Resource)
|
||||
of
|
||||
Pid when is_pid(Pid) -> ejabberd_c2s:stop(Pid);
|
||||
_ -> ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
_ -> ok
|
||||
end,
|
||||
{noreply, State#state{timers = Timers}};
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
handle_cast(_Msg, State) -> {noreply, State}.
|
||||
|
||||
handle_info({timeout, _TRef, {ping, JID}}, State) ->
|
||||
IQ = #iq{type = get,
|
||||
sub_el = [{xmlelement, "ping", [{"xmlns", ?NS_PING}], []}]},
|
||||
sub_el =
|
||||
[#xmlel{name = <<"ping">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_PING}], children = []}]},
|
||||
Pid = self(),
|
||||
F = fun(Response) ->
|
||||
F = fun (Response) ->
|
||||
gen_server:cast(Pid, {iq_pong, JID, Response})
|
||||
end,
|
||||
From = jlib:make_jid("", State#state.host, ""),
|
||||
From = jlib:make_jid(<<"">>, State#state.host, <<"">>),
|
||||
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}};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
handle_info(_Info, State) -> {noreply, State}.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
%%====================================================================
|
||||
%% 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
|
||||
{get, {xmlelement, "ping", _, _}} ->
|
||||
{get, #xmlel{name = <<"ping">>}} ->
|
||||
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.
|
||||
|
||||
user_online(_SID, JID, _Info) ->
|
||||
@ -203,35 +223,26 @@ user_send(JID, _From, _Packet) ->
|
||||
%%====================================================================
|
||||
add_timer(JID, Interval, Timers) ->
|
||||
LJID = jlib:jid_tolower(JID),
|
||||
NewTimers = case ?DICT:find(LJID, Timers) of
|
||||
NewTimers = case (?DICT):find(LJID, Timers) of
|
||||
{ok, OldTRef} ->
|
||||
cancel_timer(OldTRef),
|
||||
?DICT:erase(LJID, Timers);
|
||||
_ ->
|
||||
Timers
|
||||
cancel_timer(OldTRef), (?DICT):erase(LJID, Timers);
|
||||
_ -> Timers
|
||||
end,
|
||||
TRef = erlang:start_timer(Interval * 1000, self(), {ping, JID}),
|
||||
?DICT:store(LJID, TRef, NewTimers).
|
||||
TRef = erlang:start_timer(Interval * 1000, self(),
|
||||
{ping, JID}),
|
||||
(?DICT):store(LJID, TRef, NewTimers).
|
||||
|
||||
del_timer(JID, Timers) ->
|
||||
LJID = jlib:jid_tolower(JID),
|
||||
case ?DICT:find(LJID, Timers) of
|
||||
case (?DICT):find(LJID, Timers) of
|
||||
{ok, TRef} ->
|
||||
cancel_timer(TRef),
|
||||
?DICT:erase(LJID, Timers);
|
||||
_ ->
|
||||
Timers
|
||||
cancel_timer(TRef), (?DICT):erase(LJID, Timers);
|
||||
_ -> Timers
|
||||
end.
|
||||
|
||||
cancel_timer(TRef) ->
|
||||
case erlang:cancel_timer(TRef) of
|
||||
false ->
|
||||
receive
|
||||
{timeout, TRef, _} ->
|
||||
ok
|
||||
after 0 ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
receive {timeout, TRef, _} -> ok after 0 -> ok end;
|
||||
_ -> ok
|
||||
end.
|
||||
|
@ -28,18 +28,18 @@
|
||||
|
||||
-behavior(gen_mod).
|
||||
|
||||
-export([start/2,
|
||||
stop/1,
|
||||
check_packet/6]).
|
||||
-export([start/2, stop/1, check_packet/6]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
|
||||
-record(pres_counter, {dir, start, count, logged = false}).
|
||||
-record(pres_counter,
|
||||
{dir, start, count, logged = false}).
|
||||
|
||||
start(Host, _Opts) ->
|
||||
ejabberd_hooks:add(privacy_check_packet, Host,
|
||||
?MODULE, check_packet, 25),
|
||||
ejabberd_hooks:add(privacy_check_packet, Host, ?MODULE,
|
||||
check_packet, 25),
|
||||
ok.
|
||||
|
||||
stop(Host) ->
|
||||
@ -47,88 +47,75 @@ stop(Host) ->
|
||||
?MODULE, check_packet, 25),
|
||||
ok.
|
||||
|
||||
check_packet(_, _User, Server,
|
||||
_PrivacyList,
|
||||
{From, To, {xmlelement, Name, Attrs, _}},
|
||||
Dir) ->
|
||||
check_packet(_, _User, Server, _PrivacyList,
|
||||
{From, To, #xmlel{name = Name, attrs = Attrs}}, Dir) ->
|
||||
case Name of
|
||||
"presence" ->
|
||||
IsSubscription =
|
||||
case xml:get_attr_s("type", Attrs) of
|
||||
"subscribe" -> true;
|
||||
"subscribed" -> true;
|
||||
"unsubscribe" -> true;
|
||||
"unsubscribed" -> true;
|
||||
<<"presence">> ->
|
||||
IsSubscription = case xml:get_attr_s(<<"type">>, Attrs)
|
||||
of
|
||||
<<"subscribe">> -> true;
|
||||
<<"subscribed">> -> true;
|
||||
<<"unsubscribe">> -> true;
|
||||
<<"unsubscribed">> -> true;
|
||||
_ -> false
|
||||
end,
|
||||
if
|
||||
IsSubscription ->
|
||||
if IsSubscription ->
|
||||
JID = case Dir of
|
||||
in -> To;
|
||||
out -> From
|
||||
end,
|
||||
update(Server, JID, Dir);
|
||||
true ->
|
||||
allow
|
||||
true -> allow
|
||||
end;
|
||||
_ ->
|
||||
allow
|
||||
_ -> allow
|
||||
end.
|
||||
|
||||
update(Server, JID, Dir) ->
|
||||
%% get options
|
||||
StormCount = gen_mod:get_module_opt(Server, ?MODULE, count, 5),
|
||||
TimeInterval = gen_mod:get_module_opt(Server, ?MODULE, interval, 60),
|
||||
StormCount = gen_mod:get_module_opt(Server, ?MODULE, count,
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
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(),
|
||||
TimeStamp = MegaSecs * 1000000 + Secs,
|
||||
case read(Dir) of
|
||||
undefined ->
|
||||
write(Dir, #pres_counter{dir = Dir,
|
||||
start = TimeStamp,
|
||||
count = 1}),
|
||||
write(Dir,
|
||||
#pres_counter{dir = Dir, start = TimeStamp, count = 1}),
|
||||
allow;
|
||||
#pres_counter{start = TimeStart, count = Count, logged = Logged} = R ->
|
||||
%% record for this key exists, check if we're
|
||||
%% within TimeInterval seconds, and whether the StormCount is
|
||||
%% high enough. or else just increment the count.
|
||||
if
|
||||
TimeStamp - TimeStart > TimeInterval ->
|
||||
write(Dir, R#pres_counter{
|
||||
start = TimeStamp,
|
||||
count = 1}),
|
||||
#pres_counter{start = TimeStart, count = Count,
|
||||
logged = Logged} =
|
||||
R ->
|
||||
if TimeStamp - TimeStart > TimeInterval ->
|
||||
write(Dir,
|
||||
R#pres_counter{start = TimeStamp, count = 1}),
|
||||
allow;
|
||||
(Count =:= StormCount) and Logged ->
|
||||
{stop, deny};
|
||||
(Count =:= StormCount) and Logged -> {stop, deny};
|
||||
Count =:= StormCount ->
|
||||
write(Dir, R#pres_counter{logged = true}),
|
||||
case Dir of
|
||||
in ->
|
||||
?WARNING_MSG(
|
||||
"User ~s is being flooded, "
|
||||
"ignoring received presence subscriptions",
|
||||
?WARNING_MSG("User ~s is being flooded, ignoring received "
|
||||
"presence subscriptions",
|
||||
[jlib:jid_to_string(JID)]);
|
||||
out ->
|
||||
IP = ejabberd_sm:get_user_ip(
|
||||
JID#jid.luser,
|
||||
IP = ejabberd_sm:get_user_ip(JID#jid.luser,
|
||||
JID#jid.lserver,
|
||||
JID#jid.lresource),
|
||||
?WARNING_MSG(
|
||||
"Flooder detected: ~s, on IP: ~s "
|
||||
"ignoring sent presence subscriptions~n",
|
||||
?WARNING_MSG("Flooder detected: ~s, on IP: ~s ignoring "
|
||||
"sent presence subscriptions~n",
|
||||
[jlib:jid_to_string(JID),
|
||||
jlib:ip_to_list(IP)])
|
||||
end,
|
||||
{stop, deny};
|
||||
true ->
|
||||
write(Dir, R#pres_counter{
|
||||
start = TimeStamp,
|
||||
count = Count + 1}),
|
||||
write(Dir,
|
||||
R#pres_counter{start = TimeStamp, count = Count + 1}),
|
||||
allow
|
||||
end
|
||||
end.
|
||||
|
||||
read(K)->
|
||||
get({pres_counter, K}).
|
||||
read(K) -> get({pres_counter, K}).
|
||||
|
||||
write(K, V)->
|
||||
put({pres_counter, K}, V).
|
||||
write(K, V) -> put({pres_counter, K}, V).
|
||||
|
1145
src/mod_privacy.erl
1145
src/mod_privacy.erl
File diff suppressed because it is too large
Load Diff
@ -19,19 +19,26 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-record(privacy, {us,
|
||||
default = none,
|
||||
lists = []}).
|
||||
-record(privacy, {us = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||
default = none :: none | binary(),
|
||||
lists = [] :: [{binary(), [listitem()]}]}).
|
||||
|
||||
-record(listitem, {type = none,
|
||||
value = none,
|
||||
action,
|
||||
order,
|
||||
match_all = false,
|
||||
match_iq = false,
|
||||
match_message = false,
|
||||
match_presence_in = false,
|
||||
match_presence_out = false
|
||||
}).
|
||||
-record(listitem, {type = none :: none | jid | group | subscription,
|
||||
value = none :: none | both | from | to | ljid() | binary(),
|
||||
action = allow :: allow | deny,
|
||||
order = 0 :: integer(),
|
||||
match_all = false :: boolean(),
|
||||
match_iq = false :: boolean(),
|
||||
match_message = false :: boolean(),
|
||||
match_presence_in = false :: boolean(),
|
||||
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).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2,
|
||||
stop/1,
|
||||
process_sm_iq/3,
|
||||
remove_user/2]).
|
||||
-export([start/2, stop/1, process_sm_iq/3,
|
||||
remove_user/2, get_data/2, export/1]).
|
||||
|
||||
-include("ejabberd.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),
|
||||
(
|
||||
{xmlelement, "query", Attrs, Children}
|
||||
)).
|
||||
#xmlel{name = <<"query">>, attrs = Attrs,
|
||||
children = Children}).
|
||||
|
||||
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
|
||||
mnesia ->
|
||||
mnesia:create_table(private_storage,
|
||||
@ -53,118 +56,113 @@ start(Host, Opts) ->
|
||||
{attributes,
|
||||
record_info(fields, private_storage)}]),
|
||||
update_table();
|
||||
_ ->
|
||||
ok
|
||||
_ -> ok
|
||||
end,
|
||||
ejabberd_hooks:add(remove_user, Host,
|
||||
?MODULE, remove_user, 50),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PRIVATE,
|
||||
?MODULE, process_sm_iq, IQDisc).
|
||||
ejabberd_hooks:add(remove_user, Host, ?MODULE,
|
||||
remove_user, 50),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_PRIVATE, ?MODULE, process_sm_iq, IQDisc).
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(remove_user, Host,
|
||||
?MODULE, remove_user, 50),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PRIVATE).
|
||||
|
||||
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
|
||||
remove_user, 50),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
|
||||
?NS_PRIVATE).
|
||||
|
||||
process_sm_iq(#jid{luser = LUser, lserver = LServer},
|
||||
#jid{luser = LUser, lserver = LServer}, IQ)
|
||||
when IQ#iq.type == 'set' ->
|
||||
when IQ#iq.type == set ->
|
||||
case IQ#iq.sub_el of
|
||||
{xmlelement, "query", _, Xmlels} ->
|
||||
#xmlel{name = <<"query">>, children = Xmlels} ->
|
||||
case filter_xmlels(Xmlels) of
|
||||
[] ->
|
||||
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]};
|
||||
Data ->
|
||||
DBType = gen_mod:db_type(LServer, ?MODULE),
|
||||
F = fun() ->
|
||||
lists:foreach(
|
||||
fun
|
||||
(Datum) ->
|
||||
F = fun () ->
|
||||
lists:foreach(fun (Datum) ->
|
||||
set_data(LUser, LServer,
|
||||
Datum, DBType)
|
||||
end, Data)
|
||||
end,
|
||||
Data)
|
||||
end,
|
||||
case DBType of
|
||||
odbc ->
|
||||
ejabberd_odbc:sql_transaction(LServer, F);
|
||||
mnesia ->
|
||||
mnesia:transaction(F)
|
||||
odbc -> ejabberd_odbc:sql_transaction(LServer, F);
|
||||
mnesia -> mnesia:transaction(F)
|
||||
end,
|
||||
IQ#iq{type = result, sub_el = []}
|
||||
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;
|
||||
%%
|
||||
process_sm_iq(#jid{luser = LUser, lserver = LServer},
|
||||
#jid{luser = LUser, lserver = LServer}, IQ)
|
||||
when IQ#iq.type == 'get' ->
|
||||
when IQ#iq.type == get ->
|
||||
case IQ#iq.sub_el of
|
||||
{xmlelement, "query", Attrs, Xmlels} ->
|
||||
#xmlel{name = <<"query">>, attrs = Attrs,
|
||||
children = Xmlels} ->
|
||||
case filter_xmlels(Xmlels) of
|
||||
[] ->
|
||||
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]};
|
||||
Data ->
|
||||
case catch get_data(LUser, LServer, Data) of
|
||||
{'EXIT', _Reason} ->
|
||||
IQ#iq{
|
||||
type = error,
|
||||
sub_el = [IQ#iq.sub_el, ?ERR_INTERNAL_SERVER_ERROR]
|
||||
};
|
||||
IQ#iq{type = error,
|
||||
sub_el =
|
||||
[IQ#iq.sub_el, ?ERR_INTERNAL_SERVER_ERROR]};
|
||||
Storage_Xmlels ->
|
||||
IQ#iq{
|
||||
type = result,
|
||||
sub_el = [?Xmlel_Query(Attrs, Storage_Xmlels)]
|
||||
}
|
||||
IQ#iq{type = result,
|
||||
sub_el = [?Xmlel_Query(Attrs, Storage_Xmlels)]}
|
||||
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;
|
||||
%%
|
||||
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(Xmlels, []).
|
||||
|
||||
filter_xmlels([], Data) ->
|
||||
lists:reverse(Data);
|
||||
filter_xmlels([{xmlelement, _, Attrs, _} = Xmlel | Xmlels], Data) ->
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
"" -> [];
|
||||
filter_xmlels([], Data) -> lists:reverse(Data);
|
||||
filter_xmlels([#xmlel{attrs = Attrs} = Xmlel | Xmlels],
|
||||
Data) ->
|
||||
case xml:get_attr_s(<<"xmlns">>, Attrs) of
|
||||
<<"">> -> [];
|
||||
XmlNS -> filter_xmlels(Xmlels, [{XmlNS, Xmlel} | Data])
|
||||
end;
|
||||
filter_xmlels([_ | Xmlels], Data) ->
|
||||
filter_xmlels(Xmlels, Data).
|
||||
|
||||
|
||||
set_data(LUser, LServer, {XmlNS, Xmlel}, mnesia) ->
|
||||
mnesia:write(#private_storage{
|
||||
usns = {LUser, LServer, XmlNS},
|
||||
mnesia:write(#private_storage{usns =
|
||||
{LUser, LServer, XmlNS},
|
||||
xml = Xmlel});
|
||||
set_data(LUser, LServer, {XMLNS, El}, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LXMLNS = ejabberd_odbc:escape(XMLNS),
|
||||
SData = ejabberd_odbc:escape(
|
||||
xml:element_to_binary(El)),
|
||||
odbc_queries:set_private_data(LServer, Username, LXMLNS, SData).
|
||||
SData = ejabberd_odbc:escape(xml:element_to_binary(El)),
|
||||
odbc_queries:set_private_data(LServer, Username, LXMLNS,
|
||||
SData).
|
||||
|
||||
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);
|
||||
get_data(LUser, LServer, mnesia, [{XmlNS, Xmlel} | Data], Storage_Xmlels) ->
|
||||
case mnesia:dirty_read(private_storage, {LUser, LServer, XmlNS}) of
|
||||
get_data(LUser, LServer, mnesia,
|
||||
[{XmlNS, Xmlel} | Data], Storage_Xmlels) ->
|
||||
case mnesia:dirty_read(private_storage,
|
||||
{LUser, LServer, XmlNS})
|
||||
of
|
||||
[#private_storage{xml = Storage_Xmlel}] ->
|
||||
get_data(LUser, LServer, mnesia, Data,
|
||||
[Storage_Xmlel | Storage_Xmlels]);
|
||||
@ -172,86 +170,109 @@ get_data(LUser, LServer, mnesia, [{XmlNS, Xmlel} | Data], Storage_Xmlels) ->
|
||||
get_data(LUser, LServer, mnesia, Data,
|
||||
[Xmlel | Storage_Xmlels])
|
||||
end;
|
||||
get_data(LUser, LServer, odbc, [{XMLNS, El} | Els], Res) ->
|
||||
get_data(LUser, LServer, odbc, [{XMLNS, El} | Els],
|
||||
Res) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LXMLNS = ejabberd_odbc:escape(XMLNS),
|
||||
case catch odbc_queries:get_private_data(LServer, Username, LXMLNS) of
|
||||
{selected, ["data"], [{SData}]} ->
|
||||
case catch odbc_queries:get_private_data(LServer,
|
||||
Username, LXMLNS)
|
||||
of
|
||||
{selected, [<<"data">>], [[SData]]} ->
|
||||
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])
|
||||
end;
|
||||
%% 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])
|
||||
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.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
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) ->
|
||||
F = fun() ->
|
||||
Namespaces = mnesia:select(
|
||||
private_storage,
|
||||
[{#private_storage{usns={LUser, LServer, '$1'},
|
||||
F = fun () ->
|
||||
Namespaces = mnesia:select(private_storage,
|
||||
[{#private_storage{usns =
|
||||
{LUser,
|
||||
LServer,
|
||||
'$1'},
|
||||
_ = '_'},
|
||||
[],
|
||||
['$$']}]),
|
||||
lists:foreach(
|
||||
fun([Namespace]) ->
|
||||
[], ['$$']}]),
|
||||
lists:foreach(fun ([Namespace]) ->
|
||||
mnesia:delete({private_storage,
|
||||
{LUser, LServer, Namespace}})
|
||||
end, Namespaces)
|
||||
{LUser, LServer,
|
||||
Namespace}})
|
||||
end,
|
||||
Namespaces)
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
remove_user(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
odbc_queries:del_user_private_storage(LServer, Username).
|
||||
odbc_queries:del_user_private_storage(LServer,
|
||||
Username).
|
||||
|
||||
update_table() ->
|
||||
Fields = record_info(fields, private_storage),
|
||||
case mnesia:table_info(private_storage, attributes) of
|
||||
Fields ->
|
||||
ok;
|
||||
[userns, xml] ->
|
||||
?INFO_MSG("Converting private_storage table from "
|
||||
"{user, default, lists} format", []),
|
||||
Host = ?MYNAME,
|
||||
{atomic, ok} = mnesia:create_table(
|
||||
mod_private_tmp_table,
|
||||
[{disc_only_copies, [node()]},
|
||||
{type, bag},
|
||||
{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);
|
||||
ejabberd_config:convert_table_to_binary(
|
||||
private_storage, Fields, set,
|
||||
fun(#private_storage{usns = {U, _, _}}) -> U end,
|
||||
fun(#private_storage{usns = {U, S, NS}, xml = El} = R) ->
|
||||
R#private_storage{usns = {iolist_to_binary(U),
|
||||
iolist_to_binary(S),
|
||||
iolist_to_binary(NS)},
|
||||
xml = xml:to_xmlel(El)}
|
||||
end);
|
||||
_ ->
|
||||
?INFO_MSG("Recreating private_storage table", []),
|
||||
mnesia:transform_table(private_storage, ignore, Fields)
|
||||
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.
|
||||
ifdef debug
|
||||
EFLAGS+=+debug_info +export_all
|
||||
EFLAGS+=+debug_info
|
||||
endif
|
||||
|
||||
OUTDIR = ..
|
||||
|
@ -25,9 +25,11 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_proxy65).
|
||||
|
||||
-author('xram@jabber.ru').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
||||
%% gen_mod callbacks.
|
||||
@ -43,14 +45,11 @@
|
||||
|
||||
start(Host, Opts) ->
|
||||
case mod_proxy65_service:add_listener(Host, Opts) of
|
||||
{error, _} = Err ->
|
||||
erlang:error(Err);
|
||||
{error, _} = Err -> erlang:error(Err);
|
||||
_ ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
ChildSpec = {
|
||||
Proc, {?MODULE, start_link, [Host, Opts]},
|
||||
transient, infinity, supervisor, [?MODULE]
|
||||
},
|
||||
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
|
||||
transient, infinity, supervisor, [?MODULE]},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec)
|
||||
end.
|
||||
|
||||
@ -62,20 +61,22 @@ stop(Host) ->
|
||||
|
||||
start_link(Host, Opts) ->
|
||||
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]) ->
|
||||
Service =
|
||||
{mod_proxy65_service, {mod_proxy65_service, start_link, [Host, Opts]},
|
||||
Service = {mod_proxy65_service,
|
||||
{mod_proxy65_service, start_link, [Host, Opts]},
|
||||
transient, 5000, worker, [mod_proxy65_service]},
|
||||
StreamSupervisor =
|
||||
{ejabberd_mod_proxy65_sup,
|
||||
StreamSupervisor = {ejabberd_mod_proxy65_sup,
|
||||
{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]},
|
||||
transient, infinity, supervisor, [ejabberd_tmp_sup]},
|
||||
StreamManager =
|
||||
{mod_proxy65_sm, {mod_proxy65_sm, start_link, [Host, Opts]},
|
||||
transient, 5000, worker, [mod_proxy65_sm]},
|
||||
{ok, {{one_for_one, 10, 1},
|
||||
StreamManager = {mod_proxy65_sm,
|
||||
{mod_proxy65_sm, start_link, [Host, Opts]}, transient,
|
||||
5000, worker, [mod_proxy65_sm]},
|
||||
{ok,
|
||||
{{one_for_one, 10, 1},
|
||||
[StreamManager, StreamSupervisor, Service]}}.
|
||||
|
@ -26,36 +26,49 @@
|
||||
|
||||
%% Authentication methods
|
||||
-define(AUTH_ANONYMOUS, 0).
|
||||
|
||||
-define(AUTH_GSSAPI, 1).
|
||||
|
||||
-define(AUTH_PLAIN, 2).
|
||||
-define(AUTH_NO_METHODS, 16#FF).
|
||||
|
||||
%% Address Type
|
||||
-define(AUTH_NO_METHODS, 255).
|
||||
|
||||
-define(ATYP_IPV4, 1).
|
||||
|
||||
-define(ATYP_DOMAINNAME, 3).
|
||||
|
||||
-define(ATYP_IPV6, 4).
|
||||
|
||||
%% Commands
|
||||
-define(CMD_CONNECT, 1).
|
||||
|
||||
-define(CMD_BIND, 2).
|
||||
|
||||
-define(CMD_UDP, 3).
|
||||
|
||||
%% RFC 1928 replies
|
||||
-define(SUCCESS, 0).
|
||||
|
||||
-define(ERR_GENERAL_FAILURE, 1).
|
||||
|
||||
-define(ERR_NOT_ALLOWED, 2).
|
||||
|
||||
-define(ERR_NETWORK_UNREACHABLE, 3).
|
||||
|
||||
-define(ERR_HOST_UNREACHABLE, 4).
|
||||
|
||||
-define(ERR_CONNECTION_REFUSED, 5).
|
||||
|
||||
-define(ERR_TTL_EXPIRED, 6).
|
||||
|
||||
-define(ERR_COMMAND_NOT_SUPPORTED, 7).
|
||||
|
||||
-define(ERR_ADDRESS_TYPE_NOT_SUPPORTED, 8).
|
||||
|
||||
%% RFC 1928 defined timeout.
|
||||
-define(SOCKS5_REPLY_TIMEOUT, 10000).
|
||||
|
||||
-record(s5_request, {
|
||||
rsv = 0,
|
||||
cmd,
|
||||
sha1
|
||||
}).
|
||||
-record(s5_request, {rsv = 0 :: integer(),
|
||||
cmd = connect :: connect | udp,
|
||||
sha1 = <<"">> :: binary()}).
|
||||
|
@ -25,59 +25,49 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_proxy65_lib).
|
||||
|
||||
-author('xram@jabber.ru').
|
||||
|
||||
-include("mod_proxy65.hrl").
|
||||
|
||||
-export([
|
||||
unpack_init_message/1,
|
||||
unpack_auth_request/1,
|
||||
unpack_request/1,
|
||||
make_init_reply/1,
|
||||
make_auth_reply/1,
|
||||
make_reply/1,
|
||||
make_error_reply/1,
|
||||
make_error_reply/2
|
||||
]).
|
||||
-export([unpack_init_message/1, unpack_auth_request/1,
|
||||
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 ->
|
||||
{ok, binary_to_list(AuthMethodList)};
|
||||
unpack_init_message(_) -> error.
|
||||
|
||||
unpack_init_message(_) ->
|
||||
error.
|
||||
unpack_auth_request(<<1, ULen, User:ULen/binary, PLen,
|
||||
Pass:PLen/binary>>)
|
||||
when ULen < 256, PLen < 256 ->
|
||||
{(User), (Pass)};
|
||||
unpack_auth_request(_) -> error.
|
||||
|
||||
unpack_auth_request(<<1, ULen, User:ULen/binary,
|
||||
PLen, Pass:PLen/binary>>) when ULen < 256, PLen < 256 ->
|
||||
{binary_to_list(User), binary_to_list(Pass)};
|
||||
|
||||
unpack_auth_request(_) ->
|
||||
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
|
||||
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,
|
||||
#s5_request{cmd = Command, rsv = RSV, sha1 = binary_to_list(SHA1)};
|
||||
#s5_request{cmd = Command, rsv = RSV, sha1 = (SHA1)};
|
||||
unpack_request(_) -> error.
|
||||
|
||||
unpack_request(_) ->
|
||||
error.
|
||||
|
||||
make_init_reply(Method) ->
|
||||
[?VERSION_5, Method].
|
||||
make_init_reply(Method) -> [?VERSION_5, Method].
|
||||
|
||||
make_auth_reply(true) -> [1, ?SUCCESS];
|
||||
make_auth_reply(false) -> [1, ?ERR_NOT_ALLOWED].
|
||||
|
||||
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, ?ERR_NOT_ALLOWED).
|
||||
|
||||
make_error_reply(#s5_request{rsv = RSV, sha1 = SHA1}, Reason) ->
|
||||
[?VERSION_5, Reason, RSV, ?ATYP_DOMAINNAME, length(SHA1), SHA1, 0,0].
|
||||
make_error_reply(#s5_request{rsv = RSV, sha1 = SHA1},
|
||||
Reason) ->
|
||||
[?VERSION_5, Reason, RSV, ?ATYP_DOMAINNAME,
|
||||
byte_size(SHA1), SHA1, 0, 0].
|
||||
|
@ -25,37 +25,33 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_proxy65_service).
|
||||
|
||||
-author('xram@jabber.ru').
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% gen_server callbacks.
|
||||
-export([init/1,
|
||||
handle_info/2,
|
||||
handle_call/3,
|
||||
handle_cast/2,
|
||||
terminate/2,
|
||||
code_change/3
|
||||
]).
|
||||
-export([init/1, handle_info/2, handle_call/3,
|
||||
handle_cast/2, terminate/2, code_change/3]).
|
||||
|
||||
%% 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("jlib.hrl").
|
||||
|
||||
-define(PROCNAME, ejabberd_mod_proxy65_service).
|
||||
|
||||
-record(state, {
|
||||
myhost,
|
||||
serverhost,
|
||||
name,
|
||||
stream_addr,
|
||||
port,
|
||||
ip,
|
||||
acl
|
||||
}).
|
||||
|
||||
-record(state,
|
||||
{myhost = <<"">> :: binary(),
|
||||
serverhost = <<"">> :: binary(),
|
||||
name = <<"">> :: binary(),
|
||||
stream_addr = [] :: [attr()],
|
||||
port = 0 :: inet:port_number(),
|
||||
ip = {127,0,0,1} :: inet:ip_address(),
|
||||
acl = none :: atom()}).
|
||||
|
||||
%%%------------------------
|
||||
%%% gen_server callbacks
|
||||
@ -63,43 +59,44 @@
|
||||
|
||||
start_link(Host, Opts) ->
|
||||
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]) ->
|
||||
State = parse_options(Host, Opts),
|
||||
ejabberd_router:register_route(State#state.myhost),
|
||||
{ok, State}.
|
||||
|
||||
terminate(_Reason, #state{myhost=MyHost}) ->
|
||||
ejabberd_router:unregister_route(MyHost),
|
||||
ok.
|
||||
terminate(_Reason, #state{myhost = MyHost}) ->
|
||||
ejabberd_router:unregister_route(MyHost), 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),
|
||||
case catch process_iq(From, IQ, State) of
|
||||
Result when is_record(Result, iq) ->
|
||||
ejabberd_router:route(To, From, jlib:iq_to_xml(Result));
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("Error when processing IQ stanza: ~p", [Reason]),
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_INTERNAL_SERVER_ERROR),
|
||||
?ERROR_MSG("Error when processing IQ stanza: ~p",
|
||||
[Reason]),
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_INTERNAL_SERVER_ERROR),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
_ ->
|
||||
ok
|
||||
_ -> ok
|
||||
end,
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
handle_info(_Info, State) -> {noreply, 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) ->
|
||||
{reply, ok, State}.
|
||||
|
||||
handle_cast(_Request, State) ->
|
||||
{noreply, State}.
|
||||
handle_cast(_Request, State) -> {noreply, State}.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
%%%------------------------
|
||||
%%% Listener management
|
||||
@ -108,72 +105,106 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
add_listener(Host, Opts) ->
|
||||
State = parse_options(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) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
{port_ip, Port, IP} = gen_server:call(Proc, get_port_ip),
|
||||
catch ejabberd_listener:delete_listener({Port, IP}, mod_proxy65_stream).
|
||||
{port_ip, Port, IP} = gen_server:call(Proc,
|
||||
get_port_ip),
|
||||
catch ejabberd_listener:delete_listener({Port, IP},
|
||||
mod_proxy65_stream).
|
||||
|
||||
%%%------------------------
|
||||
%%% IQ Processing
|
||||
%%%------------------------
|
||||
|
||||
%% disco#info request
|
||||
process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} = IQ,
|
||||
#state{name=Name, serverhost=ServerHost}) ->
|
||||
Info = ejabberd_hooks:run_fold(
|
||||
disco_info, ServerHost, [], [ServerHost, ?MODULE, "", ""]),
|
||||
IQ#iq{type = result, sub_el =
|
||||
[{xmlelement, "query", [{"xmlns", ?NS_DISCO_INFO}],
|
||||
iq_disco_info(Lang, Name) ++ Info}]};
|
||||
|
||||
process_iq(_,
|
||||
#iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} =
|
||||
IQ,
|
||||
#state{name = Name, serverhost = ServerHost}) ->
|
||||
Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
|
||||
[], [ServerHost, ?MODULE, <<"">>, <<"">>]),
|
||||
IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_DISCO_INFO}],
|
||||
children = iq_disco_info(Lang, Name) ++ Info}]};
|
||||
%% disco#items request
|
||||
process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ, _) ->
|
||||
IQ#iq{type = result, sub_el =
|
||||
[{xmlelement, "query", [{"xmlns", ?NS_DISCO_ITEMS}], []}]};
|
||||
|
||||
process_iq(_,
|
||||
#iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ, _) ->
|
||||
IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}],
|
||||
children = []}]};
|
||||
%% vCard request
|
||||
process_iq(_, #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} = IQ, _) ->
|
||||
IQ#iq{type = result, sub_el =
|
||||
[{xmlelement, "vCard", [{"xmlns", ?NS_VCARD}], iq_vcard(Lang)}]};
|
||||
|
||||
process_iq(_,
|
||||
#iq{type = get, xmlns = ?NS_VCARD, lang = Lang} = IQ,
|
||||
_) ->
|
||||
IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"vCard">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_VCARD}],
|
||||
children = iq_vcard(Lang)}]};
|
||||
%% bytestreams info request
|
||||
process_iq(JID, #iq{type = get, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS} = IQ,
|
||||
#state{acl = ACL, stream_addr = StreamAddr, serverhost = ServerHost}) ->
|
||||
process_iq(JID,
|
||||
#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
|
||||
allow ->
|
||||
StreamHostEl = [{xmlelement, "streamhost", StreamAddr, []}],
|
||||
IQ#iq{type = result, sub_el =
|
||||
[{xmlelement, "query", [{"xmlns", ?NS_BYTESTREAMS}], StreamHostEl}]};
|
||||
StreamHostEl = [#xmlel{name = <<"streamhost">>,
|
||||
attrs = StreamAddr, children = []}],
|
||||
IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_BYTESTREAMS}],
|
||||
children = StreamHostEl}]};
|
||||
deny ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
|
||||
end;
|
||||
|
||||
%% 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}) ->
|
||||
case acl:match_rule(ServerHost, ACL, InitiatorJID) of
|
||||
allow ->
|
||||
ActivateEl = xml:get_path_s(SubEl, [{elem, "activate"}]),
|
||||
SID = xml:get_tag_attr_s("sid", SubEl),
|
||||
case catch jlib:string_to_jid(xml:get_tag_cdata(ActivateEl)) of
|
||||
TargetJID when is_record(TargetJID, jid), SID /= "",
|
||||
length(SID) =< 128, TargetJID /= InitiatorJID ->
|
||||
Target = jlib:jid_to_string(jlib:jid_tolower(TargetJID)),
|
||||
Initiator = jlib:jid_to_string(jlib:jid_tolower(InitiatorJID)),
|
||||
SHA1 = sha:sha(SID ++ Initiator ++ Target),
|
||||
case mod_proxy65_sm:activate_stream(SHA1, InitiatorJID, TargetJID, ServerHost) of
|
||||
ok ->
|
||||
IQ#iq{type = result, sub_el = []};
|
||||
ActivateEl = xml:get_path_s(SubEl,
|
||||
[{elem, <<"activate">>}]),
|
||||
SID = xml:get_tag_attr_s(<<"sid">>, SubEl),
|
||||
case catch
|
||||
jlib:string_to_jid(xml:get_tag_cdata(ActivateEl))
|
||||
of
|
||||
TargetJID
|
||||
when is_record(TargetJID, jid), SID /= <<"">>,
|
||||
byte_size(SID) =< 128, TargetJID /= InitiatorJID ->
|
||||
Target =
|
||||
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 ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
|
||||
limit ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_RESOURCE_CONSTRAINT]};
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_RESOURCE_CONSTRAINT]};
|
||||
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;
|
||||
_ ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
|
||||
@ -181,68 +212,74 @@ process_iq(InitiatorJID, #iq{type = set, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS
|
||||
deny ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
|
||||
end;
|
||||
|
||||
%% Unknown "set" or "get" request
|
||||
process_iq(_, #iq{type=Type, sub_el=SubEl} = IQ, _) when Type==get; Type==set ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
|
||||
|
||||
process_iq(_, #iq{type = Type, sub_el = SubEl} = IQ, _)
|
||||
when Type == get; Type == set ->
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
|
||||
%% IQ "result" or "error".
|
||||
process_iq(_, _, _) ->
|
||||
ok.
|
||||
process_iq(_, _, _) -> ok.
|
||||
|
||||
%%%-------------------------
|
||||
%%% Auxiliary functions.
|
||||
%%%-------------------------
|
||||
-define(FEATURE(Feat), {xmlelement,"feature",[{"var", Feat}],[]}).
|
||||
-define(FEATURE(Feat),
|
||||
#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, Feat}], children = []}).
|
||||
|
||||
iq_disco_info(Lang, Name) ->
|
||||
[{xmlelement, "identity",
|
||||
[{"category", "proxy"},
|
||||
{"type", "bytestreams"},
|
||||
{"name", translate:translate(Lang, Name)}], []},
|
||||
?FEATURE(?NS_DISCO_INFO),
|
||||
?FEATURE(?NS_VCARD),
|
||||
?FEATURE(?NS_BYTESTREAMS)].
|
||||
[#xmlel{name = <<"identity">>,
|
||||
attrs =
|
||||
[{<<"category">>, <<"proxy">>},
|
||||
{<<"type">>, <<"bytestreams">>},
|
||||
{<<"name">>, translate:translate(Lang, Name)}],
|
||||
children = []},
|
||||
?FEATURE((?NS_DISCO_INFO)), ?FEATURE((?NS_VCARD)),
|
||||
?FEATURE((?NS_BYTESTREAMS))].
|
||||
|
||||
iq_vcard(Lang) ->
|
||||
[{xmlelement, "FN", [],
|
||||
[{xmlcdata, "ejabberd/mod_proxy65"}]},
|
||||
{xmlelement, "URL", [],
|
||||
[{xmlcdata, ?EJABBERD_URI}]},
|
||||
{xmlelement, "DESC", [],
|
||||
[{xmlcdata, translate:translate(Lang, "ejabberd SOCKS5 Bytestreams module") ++
|
||||
"\nCopyright (c) 2003-2013 ProcessOne"}]}].
|
||||
[#xmlel{name = <<"FN">>, attrs = [],
|
||||
children = [{xmlcdata, <<"ejabberd/mod_proxy65">>}]},
|
||||
#xmlel{name = <<"URL">>, attrs = [],
|
||||
children = [{xmlcdata, ?EJABBERD_URI}]},
|
||||
#xmlel{name = <<"DESC">>, attrs = [],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
<<(translate:translate(Lang,
|
||||
<<"ejabberd SOCKS5 Bytestreams module">>))/binary,
|
||||
"\nCopyright (c) 2003-2013 ProcessOne">>}]}].
|
||||
|
||||
parse_options(ServerHost, Opts) ->
|
||||
MyHost = gen_mod:get_opt_host(ServerHost, Opts, "proxy.@HOST@"),
|
||||
Port = gen_mod:get_opt(port, Opts, 7777),
|
||||
ACL = gen_mod:get_opt(access, Opts, all),
|
||||
Name = gen_mod:get_opt(name, Opts, "SOCKS5 Bytestreams"),
|
||||
IP = case gen_mod:get_opt(ip, Opts, none) of
|
||||
none -> get_my_ip();
|
||||
Addr -> Addr
|
||||
end,
|
||||
HostName = case gen_mod:get_opt(hostname, Opts, none) of
|
||||
none ->
|
||||
inet_parse:ntoa(IP);
|
||||
HostAddr when is_tuple(HostAddr) ->
|
||||
inet_parse:ntoa(HostAddr);
|
||||
HostNameStr ->
|
||||
HostNameStr
|
||||
end,
|
||||
StreamAddr = [{"jid", MyHost}, {"host", HostName},
|
||||
{"port", integer_to_list(Port)}],
|
||||
#state{myhost = MyHost,
|
||||
serverhost = ServerHost,
|
||||
name = Name,
|
||||
port = Port,
|
||||
ip = IP,
|
||||
stream_addr = StreamAddr,
|
||||
acl = ACL}.
|
||||
MyHost = gen_mod:get_opt_host(ServerHost, Opts,
|
||||
<<"proxy.@HOST@">>),
|
||||
Port = gen_mod:get_opt(port, Opts,
|
||||
fun(P) when is_integer(P), P>0, P<65536 -> P end,
|
||||
7777),
|
||||
ACL = gen_mod:get_opt(access, Opts, fun(A) when is_atom(A) -> A end,
|
||||
all),
|
||||
Name = gen_mod:get_opt(name, Opts, fun iolist_to_binary/1,
|
||||
<<"SOCKS5 Bytestreams">>),
|
||||
IP = gen_mod:get_opt(ip, Opts,
|
||||
fun(Addr) ->
|
||||
jlib:ip_to_list(Addr),
|
||||
Addr
|
||||
end, get_my_ip()),
|
||||
HostName = gen_mod:get_opt(hostname, Opts,
|
||||
fun(Addr) when is_tuple(Addr) ->
|
||||
jlib:ip_to_list(Addr);
|
||||
(S) ->
|
||||
iolist_to_binary(S)
|
||||
end, jlib:ip_to_list(IP)),
|
||||
StreamAddr = [{<<"jid">>, MyHost},
|
||||
{<<"host">>, HostName},
|
||||
{<<"port">>, jlib:integer_to_binary(Port)}],
|
||||
#state{myhost = MyHost, serverhost = ServerHost,
|
||||
name = Name, port = Port, ip = IP,
|
||||
stream_addr = StreamAddr, acl = ACL}.
|
||||
|
||||
get_my_ip() ->
|
||||
{ok, MyHostName} = inet:gethostname(),
|
||||
case inet:getaddr(MyHostName, inet) of
|
||||
{ok, Addr} -> Addr;
|
||||
{error, _} -> {127,0,0,1}
|
||||
{error, _} -> {127, 0, 0, 1}
|
||||
end.
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user