25
1
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:
Badlop 2013-03-14 10:33:02 +01:00
parent 9c41abde10
commit 9deb294328
185 changed files with 49979 additions and 42974 deletions

View File

@ -35,7 +35,7 @@ ERLANG_CFLAGS += @ERLANG_SSLVER@
# make debug=true to compile Erlang module with debug informations.
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))

View File

@ -25,30 +25,58 @@
%%%----------------------------------------------------------------------
-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 () ->
@ -57,102 +85,100 @@ add(Host, ACLName, 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
if Clear ->
Ks = mnesia:select(acl,
[{{acl, {'$1', Host}, '$2'}, [],
['$1']}]),
lists:foreach(fun (K) -> mnesia:delete({acl, {K, Host}})
end,
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;
@ -161,24 +187,19 @@ match_acl(ACL, JID, Host) ->
{User, Server, Resource} = jlib:jid_tolower(JID),
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.

View File

@ -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
}.

View File

@ -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{}.

View File

@ -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) ->

View File

@ -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'

View File

@ -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}];
external -> [{'==', '$2', plain}];
scram -> [{'/=', '$2', digest}];
{'EXIT', {undef, [{Module, store_type, []} | _]}} ->
?WARNING_MSG("~p doesn't implement the function store_type/0", [Module]),
?WARNING_MSG("~p doesn't implement the function store_type/0",
[Module]),
[];
_Else ->
[]
_Else -> []
end,
['$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}.
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.

View File

@ -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.

View File

@ -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"}.
{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))).

View File

@ -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]).

View File

@ -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.

View File

@ -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)).
-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)).

View File

@ -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

View File

@ -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.

View 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};
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,40 +361,48 @@ 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),
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).

View File

@ -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.

View File

@ -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).

View File

@ -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()]},
@ -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
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 () ->
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
@ -318,8 +314,8 @@ remove_user(User, Server) ->
US = {LUser, LServer},
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.
@ -332,77 +328,63 @@ remove_user(User, Server, Password) ->
US = {LUser, LServer},
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,7 +426,9 @@ maybe_scram_passwords() ->
end.
scram_passwords() ->
?INFO_MSG("Converting the stored passwords into SCRAM bits", []),
?INFO_MSG("Converting the stored passwords into "
"SCRAM bits",
[]),
Fun = fun (#passwd{password = Password} = P) ->
Scram = password_to_scram(Password),
P#passwd{password = Scram}
@ -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}].

View File

@ -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).
@ -362,93 +347,74 @@ local_filter(equal, Attrs, FilterMatch) ->
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.

View File

@ -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),
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.

View File

@ -24,28 +24,24 @@
%%%
%%%-------------------------------------------------------------------
-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;
@ -56,16 +52,19 @@ start(_Host) ->
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).

File diff suppressed because it is too large Load Diff

View File

@ -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([{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).

View File

@ -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
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 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
case ets:lookup(captcha, Id) of
[#captcha{args = Args, pid = Pid}] ->
if is_pid(Pid) ->
Pid ! {captcha_failed, Args};
true ->
ok
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).
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,28 +643,50 @@ 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.

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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]).

View File

@ -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()}).

View File

@ -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 ->

View File

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

View File

@ -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

View File

@ -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}] ->

View File

@ -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)).

View File

@ -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.

View File

@ -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

View File

@ -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()
?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).

View File

@ -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.

View File

@ -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.

View File

@ -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,29 +81,31 @@ 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,
mnesia:write(#route{domain = LDomain, pid = Pid,
local_hint = LocalHint})
end,
mnesia:transaction(F);
@ -105,89 +113,99 @@ register_route(Domain, LocalHint) ->
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
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
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) ->
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.

View File

@ -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]),
?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()})
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] ->
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,
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,6 +178,8 @@ 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),
@ -162,26 +187,23 @@ try_register(FromTo) ->
max_s2s_connections_number_per_node(FromTo),
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,6 +574,7 @@ 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;
@ -566,15 +585,19 @@ get_info_s2s_connections(Type) ->
get_s2s_info(Connections, Type) ->
complete_s2s_info(Connections, Type, []).
complete_s2s_info([],_,Result)->
Result;
complete_s2s_info([], _, Result) -> Result;
complete_s2s_info([Connection | T], Type, Result) ->
{_, PID, _, _} = Connection,
State = get_s2s_state(PID),
complete_s2s_info(T, Type, [State | Result]).
-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
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}]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -25,6 +25,7 @@
%%%----------------------------------------------------------------------
-module(ejabberd_service).
-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.

View File

@ -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
@ -370,11 +420,8 @@ set_session(SID, User, Server, Resource, Priority, Info) ->
US = {LUser, LServer},
USR = {LUser, LServer, LResource},
F = fun () ->
mnesia:write(#session{sid = SID,
usr = USR,
us = US,
priority = Priority,
info = Info})
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.
-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;
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,
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 ->

View File

@ -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
%%====================================================================
%====================================================================

View File

@ -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,

View File

@ -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]),
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} |
@ -154,8 +155,7 @@ handle_info({monitor, Pid, large_heap, Info}, State) ->
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).

View File

@ -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]}]}}.

View File

@ -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() ->

View File

@ -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

View File

@ -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).

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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{}.

View File

@ -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))).

View File

@ -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).

View File

@ -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>>).

View File

@ -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)]).

View File

@ -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);

View File

@ -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]).
@ -64,15 +58,15 @@ init(ProcessName, ExtPrg) ->
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,16 +89,15 @@ 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(),
@ -110,10 +105,11 @@ random_instance(MaxNum) ->
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;
(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.

View File

@ -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) ->
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
%%--------------------------------------------------------------------

View File

@ -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}).

View File

@ -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.

View File

@ -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).

File diff suppressed because it is too large Load Diff

View File

@ -19,318 +19,687 @@
%%%
%%%----------------------------------------------------------------------
-define(NS_DISCO_ITEMS, "http://jabber.org/protocol/disco#items").
-define(NS_DISCO_INFO, "http://jabber.org/protocol/disco#info").
-define(NS_VCARD, "vcard-temp").
-define(NS_VCARD_UPDATE, "vcard-temp:x:update").
-define(NS_AUTH, "jabber:iq:auth").
-define(NS_AUTH_ERROR, "jabber:iq:auth:error").
-define(NS_REGISTER, "jabber:iq:register").
-define(NS_SEARCH, "jabber:iq:search").
-define(NS_ROSTER, "jabber:iq:roster").
-define(NS_ROSTER_VER, "urn:xmpp:features:rosterver").
-define(NS_PRIVACY, "jabber:iq:privacy").
-define(NS_BLOCKING, "urn:xmpp:blocking").
-define(NS_PRIVATE, "jabber:iq:private").
-define(NS_VERSION, "jabber:iq:version").
-define(NS_TIME90, "jabber:iq:time"). % TODO: Remove once XEP-0090 is Obsolete
-define(NS_TIME, "urn:xmpp:time").
-define(NS_LAST, "jabber:iq:last").
-define(NS_XDATA, "jabber:x:data").
-define(NS_IQDATA, "jabber:iq:data").
-define(NS_DELAY91, "jabber:x:delay"). % TODO: Remove once XEP-0091 is Obsolete
-define(NS_DELAY, "urn:xmpp:delay").
-define(NS_EXPIRE, "jabber:x:expire").
-define(NS_EVENT, "jabber:x:event").
-define(NS_CHATSTATES, "http://jabber.org/protocol/chatstates").
-define(NS_XCONFERENCE, "jabber:x:conference").
-define(NS_STATS, "http://jabber.org/protocol/stats").
-define(NS_MUC, "http://jabber.org/protocol/muc").
-define(NS_MUC_USER, "http://jabber.org/protocol/muc#user").
-define(NS_MUC_ADMIN, "http://jabber.org/protocol/muc#admin").
-define(NS_MUC_OWNER, "http://jabber.org/protocol/muc#owner").
-define(NS_MUC_UNIQUE, "http://jabber.org/protocol/muc#unique").
-define(NS_PUBSUB, "http://jabber.org/protocol/pubsub").
-define(NS_PUBSUB_EVENT, "http://jabber.org/protocol/pubsub#event").
-define(NS_PUBSUB_OWNER, "http://jabber.org/protocol/pubsub#owner").
-define(NS_PUBSUB_NMI, "http://jabber.org/protocol/pubsub#node-meta-info").
-define(NS_PUBSUB_ERRORS,"http://jabber.org/protocol/pubsub#errors").
-define(NS_PUBSUB_NODE_CONFIG, "http://jabber.org/protocol/pubsub#node_config").
-define(NS_PUBSUB_SUB_OPTIONS, "http://jabber.org/protocol/pubsub#subscribe_options").
-define(NS_PUBSUB_SUB_AUTH, "http://jabber.org/protocol/pubsub#subscribe_authorization").
-define(NS_PUBSUB_GET_PENDING, "http://jabber.org/protocol/pubsub#get-pending").
-define(NS_COMMANDS, "http://jabber.org/protocol/commands").
-define(NS_BYTESTREAMS, "http://jabber.org/protocol/bytestreams").
-define(NS_ADMIN, "http://jabber.org/protocol/admin").
-define(NS_SERVERINFO, "http://jabber.org/network/serverinfo").
-define(NS_RSM, "http://jabber.org/protocol/rsm").
-define(NS_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{}.

View File

@ -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

View File

@ -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) ->
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
@ -201,79 +183,71 @@ process_blocklist_block(LUser, LServer, Filter, mnesia) ->
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",
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"],
[<<"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 = []
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)
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) ->
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
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"],
[<<"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,
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,
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) ->
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
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"],
[<<"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,
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) ->
Items = lists:map(fun (JID) ->
?DEBUG("JID: ~p", [JID]),
{xmlelement, "item",
[{"jid", jlib:jid_to_string(JID)}], []}
end, JIDs),
#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.

View File

@ -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())),
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)
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 () ->
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,
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

View File

@ -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}.

View File

@ -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),
Hosts = lists:sort(fun (H1, H2) ->
byte_size(H1) >= byte_size(H2)
end,
?MYHOSTS),
lists:filter(fun (H) ->
case lists:dropwhile(
fun(VH) ->
not lists:suffix("." ++ VH, H)
end, Hosts) of
[] ->
false;
[VH | _] ->
VH == Host
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,105 +374,107 @@ process_sm_iq_info(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ)
case is_presence_subscribed(From, To) of
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)).
#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) ->
@ -453,18 +482,12 @@ fields_to_xml(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).

View File

@ -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]).

View File

@ -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) ->
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.

View File

@ -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

View File

@ -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

View File

@ -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,9 +227,11 @@ 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 () ->
mnesia:write(#last_activity{us = US,
@ -221,20 +239,21 @@ store_last_info(LUser, LServer, TimeStamp, Status, mnesia) ->
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}].

View File

@ -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

View File

@ -22,69 +22,95 @@
-define(MAX_USERS_DEFAULT, 200).
-define(SETS, gb_sets).
-define(DICT, dict).
-record(lqueue, {queue, len, max}).
-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()
-record(lqueue,
{
queue :: queue(),
len :: integer(),
max :: integer()
}).
-record(user, {jid,
nick,
role,
last_presence}).
-type lqueue() :: #lqueue{}.
-record(activity, {message_time = 0,
presence_time = 0,
message_shaper,
presence_shaper,
message,
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(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 config() :: #config{}.
-record(muc_online_users, {us,
resource,
room,
host}).
-type role() :: moderator | participant | visitor | none.
-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

View File

@ -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) ->
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.

View File

@ -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).

File diff suppressed because it is too large Load Diff

View File

@ -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]).

View File

@ -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) ->
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'},
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}].

View File

@ -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 = ..

View File

@ -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]}}.

View File

@ -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()}).

View File

@ -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].

View File

@ -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,7 +59,8 @@
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),
@ -71,35 +68,35 @@ init([Host, Opts]) ->
{ok, State}.
terminate(_Reason, #state{myhost = MyHost}) ->
ejabberd_router:unregister_route(MyHost),
ok.
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,
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}]};
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,64 +212,70 @@ 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(),

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