diff --git a/src/Makefile.in b/src/Makefile.in index 42af5b2f2..a70e25c10 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -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)) diff --git a/src/acl.erl b/src/acl.erl index b5f64141b..77c55e79d 100644 --- a/src/acl.erl +++ b/src/acl.erl @@ -25,225 +25,264 @@ %%%---------------------------------------------------------------------- -module(acl). + -author('alexey@process-one.net'). --export([start/0, - to_record/3, - add/3, - add_list/3, - match_rule/3, - % for debugging only - match_acl/3]). +-export([start/0, to_record/3, add/3, add_list/3, + match_rule/3, match_acl/3]). -include("ejabberd.hrl"). +-include("jlib.hrl"). -record(acl, {aclname, aclspec}). +-type regexp() :: binary(). +-type glob() :: binary(). +-type aclname() :: {atom(), binary() | global}. +-type aclspec() :: all | none | + {user, binary()} | + {user, binary(), binary()} | + {server, binary()} | + {resource, binary()} | + {user_regexp, regexp()} | + {shared_group, binary()} | + {shared_group, binary(), binary()} | + {user_regexp, regexp(), binary()} | + {server_regexp, regexp()} | + {resource_regexp, regexp()} | + {node_regexp, regexp(), regexp()} | + {user_glob, glob()} | + {user_glob, glob(), binary()} | + {server_glob, glob()} | + {resource_glob, glob()} | + {node_glob, glob(), glob()}. + +-type acl() :: #acl{aclname :: aclname(), + aclspec :: aclspec()}. + +-export_type([acl/0]). + start() -> mnesia:create_table(acl, - [{disc_copies, [node()]}, - {type, bag}, + [{disc_copies, [node()]}, {type, bag}, {attributes, record_info(fields, acl)}]), mnesia:add_table_copy(acl, node(), ram_copies), + update_table(), ok. +-spec to_record(binary(), atom(), aclspec()) -> acl(). + to_record(Host, ACLName, ACLSpec) -> - #acl{aclname = {ACLName, Host}, aclspec = normalize_spec(ACLSpec)}. + #acl{aclname = {ACLName, Host}, + aclspec = normalize_spec(ACLSpec)}. + +-spec add(binary(), aclname(), aclspec()) -> {atomic, ok} | {aborted, any()}. add(Host, ACLName, ACLSpec) -> - F = fun() -> + F = fun () -> mnesia:write(#acl{aclname = {ACLName, Host}, aclspec = normalize_spec(ACLSpec)}) end, mnesia:transaction(F). +-spec add_list(binary(), [acl()], boolean()) -> false | ok. + add_list(Host, ACLs, Clear) -> - F = fun() -> - if - Clear -> - Ks = mnesia:select( - acl, [{{acl, {'$1', Host}, '$2'}, [], ['$1']}]), - lists:foreach(fun(K) -> - mnesia:delete({acl, {K, Host}}) - end, Ks); - true -> - ok + F = fun () -> + if Clear -> + Ks = mnesia:select(acl, + [{{acl, {'$1', Host}, '$2'}, [], + ['$1']}]), + lists:foreach(fun (K) -> mnesia:delete({acl, {K, Host}}) + end, + Ks); + true -> ok end, - lists:foreach(fun(ACL) -> + lists:foreach(fun (ACL) -> case ACL of - #acl{aclname = ACLName, - aclspec = ACLSpec} -> - mnesia:write( - #acl{aclname = {ACLName, Host}, - aclspec = normalize_spec(ACLSpec)}) + #acl{aclname = ACLName, + aclspec = 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) - end + all -> allow; + none -> deny; + _ -> + 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 - undefined -> - case ejabberd_config:get_global_option({access, Rule, Host}) 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); - ACLs -> - case lists:reverse(GACLs) of - [{allow, all} | Rest] -> - match_acls( - lists:reverse(Rest) ++ ACLs ++ - [{allow, all}], - JID, Host); - _ -> - match_acls(GACLs ++ ACLs, JID, Host) - end - end - end + all -> allow; + none -> deny; + _ -> + case ejabberd_config:get_global_option( + {access, Rule, global}, fun(V) -> V end) + of + undefined -> + 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}, 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}], + JID, Host); + _ -> match_acls(GACLs ++ ACLs, JID, Host) + end + end + end end. -match_acls([], _, _Host) -> - deny; +match_acls([], _, _Host) -> deny; match_acls([{Access, ACL} | ACLs], JID, Host) -> case match_acl(ACL, JID, Host) of - true -> - Access; - _ -> - match_acls(ACLs, JID, Host) + true -> Access; + _ -> match_acls(ACLs, JID, Host) end. +-spec match_acl(atom(), jid() | ljid(), binary()) -> boolean(). + match_acl(ACL, JID, Host) -> case ACL of - all -> true; - none -> false; - _ -> - {User, Server, Resource} = jlib:jid_tolower(JID), - lists:any(fun(#acl{aclspec = Spec}) -> - case Spec of - 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; - {user_regexp, UR} -> - ((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), - Mod:is_user_in_group({User, Server}, G, Host); - {shared_group, G, H} -> - 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); - {server_regexp, SR} -> - is_regexp_match(Server, SR); - {resource_regexp, RR} -> - is_regexp_match(Resource, RR); - {node_regexp, UR, SR} -> - 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); - {user_glob, UR, S} -> - (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", - [WrongSpec]), - false - end - end, - ets:lookup(acl, {ACL, global}) ++ + all -> true; + none -> false; + _ -> + {User, Server, Resource} = jlib:jid_tolower(JID), + lists:any(fun (#acl{aclspec = Spec}) -> + case Spec of + 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; + {user_regexp, UR} -> + (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), + Mod:is_user_in_group({User, Server}, G, Host); + {shared_group, G, H} -> + 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); + {server_regexp, SR} -> + is_regexp_match(Server, SR); + {resource_regexp, RR} -> + is_regexp_match(Resource, RR); + {node_regexp, UR, SR} -> + 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); + {user_glob, UR, S} -> + 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~nCheck your " + "config file and reload it with the override_a" + "cls option enabled", + [WrongSpec]), + false + end + end, + ets:lookup(acl, {ACL, global}) ++ ets:lookup(acl, {ACL, Host})) end. is_regexp_match(String, RegExp) -> case ejabberd_regexp:run(String, RegExp) of - nomatch -> - false; - match -> - true; - {error, ErrDesc} -> - ?ERROR_MSG( - "Wrong regexp ~p in ACL: ~p", - [RegExp, ErrDesc]), - false + nomatch -> false; + match -> true; + {error, ErrDesc} -> + ?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. diff --git a/src/adhoc.erl b/src/adhoc.erl index 94353aead..6af65e5d1 100644 --- a/src/adhoc.erl +++ b/src/adhoc.erl @@ -25,11 +25,14 @@ %%%---------------------------------------------------------------------- -module(adhoc). + -author('henoch@dtek.chalmers.se'). --export([parse_request/1, - produce_response/2, - produce_response/1]). +-export([ + parse_request/1, + produce_response/2, + 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, - node = Node, - sessionid = SessionID, - action = Action, - xdata = XData, - others = Others}; -parse_request(_) -> - {error, ?ERR_BAD_REQUEST}. + #adhoc_request{ + lang = Lang, + node = Node, + sessionid = SessionID, + action = Action, + xdata = XData, + 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 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 node to use as response from an adhoc_response %% record. -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()) - end, +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_binary(ProvidedSessionID), + ProvidedSessionID /= <<"">> -> ProvidedSessionID; + true -> jlib:now_to_utc_string(now()) + end, case Actions of - [] -> - ActionsEls = []; - _ -> - case DefaultAction of - "" -> - ActionsElAttrs = []; - _ -> - ActionsElAttrs = [{"execute", DefaultAction}] - end, - ActionsEls = [{xmlelement, "actions", - ActionsElAttrs, - [{xmlelement, Action, [], []} || Action <- Actions]}] + [] -> + ActionsEls = []; + _ -> + case DefaultAction of + <<"">> -> ActionsElAttrs = []; + _ -> ActionsElAttrs = [{<<"execute">>, DefaultAction}] + end, + 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}]} - end, Notes), - {xmlelement, "command", - [{"xmlns", ?NS_COMMANDS}, - {"sessionid", SessionID}, - {"node", Node}, - {"status", atom_to_list(Status)}], - ActionsEls ++ NotesEls ++ Elements}. + #xmlel{ + name = <<"note">>, + attrs = [{<<"type">>, Type}], + children = [{xmlcdata, Text}] + } + end, Notes), + #xmlel{ + name = <<"command">>, + attrs = [ + {<<"xmlns">>, ?NS_COMMANDS}, + {<<"sessionid">>, SessionID}, + {<<"node">>, Node}, + {<<"status">>, iolist_to_binary(atom_to_list(Status))} + ], + children = ActionsEls ++ NotesEls ++ Elements + }. diff --git a/src/adhoc.hrl b/src/adhoc.hrl index b294f84fb..0910dc621 100644 --- a/src/adhoc.hrl +++ b/src/adhoc.hrl @@ -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{}. diff --git a/src/cache_tab.erl b/src/cache_tab.erl index 74f47db6a..95343e4f5 100644 --- a/src/cache_tab.erl +++ b/src/cache_tab.erl @@ -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) -> diff --git a/src/configure.erl b/src/configure.erl index 34246fd30..87b7bc208 100644 --- a/src/configure.erl +++ b/src/configure.erl @@ -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' diff --git a/src/cyrsasl.erl b/src/cyrsasl.erl index 9d1377ffc..0672267b2 100644 --- a/src/cyrsasl.erl +++ b/src/cyrsasl.erl @@ -25,43 +25,76 @@ %%%---------------------------------------------------------------------- -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, - {keypos, #sasl_mechanism.mechanism}]), + ets:new(sasl_mechanism, + [named_table, public, + {keypos, #sasl_mechanism.mechanism}]), cyrsasl_plain:start([]), cyrsasl_digest:start([]), cyrsasl_scram: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,95 +122,96 @@ register_mechanism(Mechanism, Module, PasswordType) -> %% end. check_credentials(_State, Props) -> - User = xml:get_attr_s(username, Props), + User = proplists:get_value(username, Props, <<>>), case jlib:nodeprep(User) of - error -> - {error, "not-authorized"}; - "" -> - {error, "not-authorized"}; - _LUser -> - ok + error -> {error, <<"not-authorized">>}; + <<"">> -> {error, <<"not-authorized">>}; + _LUser -> ok end. +-spec(listmech/1 :: +( + Host ::binary()) + -> Mechanisms::mechanisms() +). + listmech(Host) -> Mechs = ets:select(sasl_mechanism, [{#sasl_mechanism{mechanism = '$1', - password_type = '$2', - _ = '_'}, + password_type = '$2', _ = '_'}, case catch ejabberd_auth:store_type(Host) of - external -> - [{'==', '$2', plain}]; - scram -> - [{'/=', '$2', digest}]; - {'EXIT',{undef,[{Module,store_type,[]} | _]}} -> - ?WARNING_MSG("~p doesn't implement the function store_type/0", [Module]), - []; - _Else -> - [] + external -> [{'==', '$2', plain}]; + scram -> [{'/=', '$2', digest}]; + {'EXIT', {undef, [{Module, store_type, []} | _]}} -> + ?WARNING_MSG("~p doesn't implement the function store_type/0", + [Module]), + []; + _Else -> [] end, ['$1']}]), filter_anonymous(Host, Mechs). server_new(Service, ServerFQDN, UserRealm, _SecFlags, GetPassword, CheckPassword, CheckPasswordDigest) -> - #sasl_state{service = Service, - myname = ServerFQDN, - realm = UserRealm, - get_password = GetPassword, + #sasl_state{service = Service, myname = ServerFQDN, + realm = UserRealm, get_password = GetPassword, check_password = CheckPassword, - check_password_digest= CheckPasswordDigest}. + check_password_digest = CheckPasswordDigest}. server_start(State, Mech, ClientIn) -> - case lists:member(Mech, listmech(State#sasl_state.myname)) of - true -> - case ets:lookup(sasl_mechanism, Mech) of - [#sasl_mechanism{module = Module}] -> - {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"} - end; - false -> - {error, "no-mechanism"} + 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, + 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">>} + end; + false -> {error, <<"no-mechanism">>} end. server_step(State, ClientIn) -> Module = State#sasl_state.mech_mod, MechState = State#sasl_state.mech_state, case Module:mech_step(MechState, ClientIn) of - {ok, Props} -> - case check_credentials(State, Props) of - 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} - end; - {continue, ServerOut, NewMechState} -> - {continue, ServerOut, - State#sasl_state{mech_state = NewMechState}}; - {error, Error, Username} -> - {error, Error, Username}; - {error, Error} -> - {error, Error} + {ok, Props} -> + case check_credentials(State, Props) of + 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} + end; + {continue, ServerOut, NewMechState} -> + {continue, ServerOut, State#sasl_state{mech_state = NewMechState}}; + {error, Error, Username} -> + {error, Error, Username}; + {error, Error} -> + {error, Error} end. %% 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"] + true -> Mechs; + false -> Mechs -- [<<"ANONYMOUS">>] end. diff --git a/src/cyrsasl_anonymous.erl b/src/cyrsasl_anonymous.erl index cb0b1e3ff..3090cfe9d 100644 --- a/src/cyrsasl_anonymous.erl +++ b/src/cyrsasl_anonymous.erl @@ -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. diff --git a/src/cyrsasl_digest.erl b/src/cyrsasl_digest.erl index 557e498cd..3bb88431b 100644 --- a/src/cyrsasl_digest.erl +++ b/src/cyrsasl_digest.erl @@ -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(), - get_password = GetPassword, - check_password = CheckPasswordDigest}}. +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"}; - 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 - 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}; - true -> - AuthzId = xml:get_attr_s("authzid", KeyVals), - case (State#state.get_password)(UserName) of - {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 - {true, _} -> - RspAuth = response(KeyVals, - UserName, Passwd, - Nonce, AuthzId, ""), - {continue, - "rspauth=" ++ RspAuth, - State#state{step = 5, - auth_module = AuthModule, - username = UserName, - authzid = AuthzId}}; - false -> - {error, "not-authorized", UserName}; - {false, _} -> - {error, "not-authorized", UserName} - end - end - end + bad -> {error, <<"bad-protocol">>}; + KeyVals -> + 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}; + true -> + 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}; + {Passwd, AuthModule} -> + 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/binary>>, + State#state{step = 5, auth_module = AuthModule, + username = UserName, + authzid = AuthzId}}; + 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}, - {auth_module, AuthModule}]}; +mech_step(#state{step = 5, auth_module = AuthModule, + username = UserName, authzid = AuthzId}, + <<"">>) -> + {ok, + [{username, UserName}, {authzid, AuthzId}, + {auth_module, AuthModule}]}; mech_step(A, B) -> - ?DEBUG("SASL DIGEST: A ~p B ~p", [A,B]), - {error, "bad-protocol"}. + ?DEBUG("SASL DIGEST: A ~p B ~p", [A, B]), + {error, <<"bad-protocol">>}. -parse(S) -> - parse1(S, "", []). +parse(S) -> parse1(binary_to_list(S), "", []). parse1([$= | Cs], S, Ts) -> parse2(Cs, lists:reverse(S), "", Ts); -parse1([$, | Cs], [], Ts) -> - parse1(Cs, [], Ts); -parse1([$\s | Cs], [], Ts) -> - parse1(Cs, [], Ts); -parse1([C | Cs], S, Ts) -> - parse1(Cs, [C | S], Ts); -parse1([], [], T) -> - lists:reverse(T); -parse1([], _S, _T) -> - bad. +parse1([$, | Cs], [], Ts) -> parse1(Cs, [], Ts); +parse1([$\s | Cs], [], Ts) -> parse1(Cs, [], Ts); +parse1([C | Cs], S, Ts) -> parse1(Cs, [C | S], Ts); +parse1([], [], T) -> lists:reverse(T); +parse1([], _S, _T) -> bad. -parse2([$\" | Cs], Key, Val, Ts) -> +parse2([$" | Cs], Key, Val, Ts) -> parse3(Cs, Key, Val, Ts); parse2([C | Cs], Key, Val, Ts) -> parse4(Cs, Key, [C | Val], Ts); -parse2([], _, _, _) -> - bad. +parse2([], _, _, _) -> bad. -parse3([$\" | Cs], Key, Val, Ts) -> +parse3([$" | Cs], Key, Val, Ts) -> parse4(Cs, Key, Val, Ts); parse3([$\\, C | Cs], Key, Val, Ts) -> parse3(Cs, Key, [C | Val], Ts); parse3([C | Cs], Key, Val, Ts) -> parse3(Cs, Key, [C | Val], Ts); -parse3([], _, _, _) -> - bad. +parse3([], _, _, _) -> bad. parse4([$, | Cs], Key, Val, Ts) -> - parse1(Cs, "", [{Key, lists:reverse(Val)} | Ts]); + parse1(Cs, "", [{list_to_binary(Key), list_to_binary(lists:reverse(Val))} | Ts]); parse4([$\s | Cs], Key, Val, Ts) -> parse4(Cs, Key, Val, Ts); parse4([C | Cs], Key, Val, Ts) -> parse4(Cs, Key, [C | Val], Ts); parse4([], Key, Val, Ts) -> - parse1([], "", [{Key, lists:reverse(Val)} | Ts]). - - %% @doc Check if the digest-uri is valid. %% RFC-2831 allows to provide the IP address in Host, %% however ejabberd doesn't allow that. @@ -162,14 +173,17 @@ parse4([], Key, Val, Ts) -> %% xmpp/server3.example.org/jabber.example.org, xmpp/server3.example.org and %% xmpp/jabber.example.org %% The last version is not actually allowed by the RFC, but implemented by popular clients -is_digesturi_valid(DigestURICase, JabberDomain, JabberFQDN) -> + parse1([], "", [{list_to_binary(Key), list_to_binary(lists:reverse(Val))} | Ts]). + +is_digesturi_valid(DigestURICase, JabberDomain, + JabberFQDN) -> DigestURI = stringprep:tolower(DigestURICase), - case catch string:tokens(DigestURI, "/") of - ["xmpp", Host] -> - IsHostFqdn = is_host_fqdn(Host, JabberFQDN), + case catch str:tokens(DigestURI, <<"/">>) of + [<<"xmpp">>, Host] -> + IsHostFqdn = is_host_fqdn(binary_to_list(Host), binary_to_list(JabberFQDN)), (Host == JabberDomain) or IsHostFqdn; - ["xmpp", Host, ServName] -> - IsHostFqdn = is_host_fqdn(Host, JabberFQDN), + [<<"xmpp">>, Host, ServName] -> + IsHostFqdn = is_host_fqdn(binary_to_list(Host), binary_to_list(JabberFQDN)), (ServName == JabberDomain) and IsHostFqdn; _ -> false @@ -185,62 +199,60 @@ is_host_fqdn(Host, [Fqdn | FqdnTail]) when Host /= Fqdn -> is_host_fqdn(Host, FqdnTail). get_local_fqdn() -> - case (catch get_local_fqdn2()) of - Str when is_list(Str) -> Str; - _ -> "unknown-fqdn, please configure fqdn option in ejabberd.cfg!" - end. -get_local_fqdn2() -> - case ejabberd_config:get_local_option(fqdn) of - ConfiguredFqdn when is_list(ConfiguredFqdn) -> - ConfiguredFqdn; - _undefined -> - {ok, Hostname} = inet:gethostname(), - {ok, {hostent, Fqdn, _, _, _, _}} = inet:gethostbyname(Hostname), - Fqdn + case catch get_local_fqdn2() of + Str when is_binary(Str) -> Str; + _ -> + <<"unknown-fqdn, please configure fqdn " + "option in ejabberd.cfg!">> end. -digit_to_xchar(D) when (D >= 0) and (D < 10) -> - D + 48; -digit_to_xchar(D) -> - D + 87. +get_local_fqdn2() -> + case ejabberd_config:get_local_option( + fqdn, fun iolist_to_binary/1) of + ConfiguredFqdn when is_binary(ConfiguredFqdn) -> + ConfiguredFqdn; + undefined -> + {ok, Hostname} = inet:gethostname(), + {ok, {hostent, Fqdn, _, _, _, _}} = + inet:gethostbyname(Hostname), + list_to_binary(Fqdn) + end. hex(S) -> - hex(S, []). + sha:to_hexlist(S). -hex([], Res) -> - lists:reverse(Res); -hex([N | Ns], Res) -> - hex(Ns, [digit_to_xchar(N rem 16), - digit_to_xchar(N div 16) | Res]). +proplists_get_bin_value(Key, Pairs, Default) -> + case proplists:get_value(Key, Pairs, Default) of + L when is_list(L) -> + list_to_binary(L); + L2 -> + L2 + end. - -response(KeyVals, User, Passwd, Nonce, AuthzId, A2Prefix) -> - Realm = xml:get_attr_s("realm", KeyVals), - CNonce = xml:get_attr_s("cnonce", KeyVals), - DigestURI = xml:get_attr_s("digest-uri", KeyVals), - NC = xml:get_attr_s("nc", KeyVals), - QOP = xml:get_attr_s("qop", KeyVals), +response(KeyVals, User, Passwd, Nonce, AuthzId, + A2Prefix) -> + Realm = proplists_get_bin_value(<<"realm">>, KeyVals, <<>>), + CNonce = proplists_get_bin_value(<<"cnonce">>, KeyVals, <<>>), + DigestURI = proplists_get_bin_value(<<"digest-uri">>, KeyVals, <<>>), + NC = proplists_get_bin_value(<<"nc">>, KeyVals, <<>>), + QOP = proplists_get_bin_value(<<"qop">>, KeyVals, <<>>), + MD5Hash = crypto:md5(<>), A1 = case AuthzId of - "" -> - binary_to_list( - crypto:md5(User ++ ":" ++ Realm ++ ":" ++ Passwd)) ++ - ":" ++ Nonce ++ ":" ++ CNonce; - _ -> - binary_to_list( - crypto:md5(User ++ ":" ++ Realm ++ ":" ++ Passwd)) ++ - ":" ++ Nonce ++ ":" ++ CNonce ++ ":" ++ AuthzId + <<"">> -> + <>; + _ -> + <> end, A2 = case QOP of - "auth" -> - A2Prefix ++ ":" ++ DigestURI; - _ -> - A2Prefix ++ ":" ++ DigestURI ++ - ":00000000000000000000000000000000" + <<"auth">> -> + <>; + _ -> + <> 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))). diff --git a/src/cyrsasl_plain.erl b/src/cyrsasl_plain.erl index 7192cd161..c5c5f2e02 100644 --- a/src/cyrsasl_plain.erl +++ b/src/cyrsasl_plain.erl @@ -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,67 +35,56 @@ -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}}. mech_step(State, ClientIn) -> case prepare(ClientIn) of - [AuthzId, User, Password] -> - case (State#state.check_password)(User, Password) of - {true, AuthModule} -> - {ok, [{username, User}, {authzid, AuthzId}, - {auth_module, AuthModule}]}; - _ -> - {error, "not-authorized", User} - end; - _ -> - {error, "bad-protocol"} + [AuthzId, User, Password] -> + case (State#state.check_password)(User, Password) of + {true, AuthModule} -> + {ok, + [{username, User}, {authzid, AuthzId}, + {auth_module, AuthModule}]}; + _ -> {error, <<"not-authorized">>, User} + end; + _ -> {error, <<"bad-protocol">>} end. prepare(ClientIn) -> case parse(ClientIn) of - [[], UserMaybeDomain, Password] -> - case parse_domain(UserMaybeDomain) of - %% login@domainpwd - [User, _Domain] -> - [UserMaybeDomain, User, Password]; - %% loginpwd - [User] -> - ["", User, Password] - end; - %% login@domainloginpwd - [AuthzId, User, Password] -> - [AuthzId, User, Password]; - _ -> - error + [<<"">>, UserMaybeDomain, Password] -> + case parse_domain(UserMaybeDomain) of + %% login@domainpwd + [User, _Domain] -> [UserMaybeDomain, User, Password]; + %% loginpwd + [User] -> [<<"">>, User, Password] + end; + %% login@domainloginpwd + [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]). diff --git a/src/cyrsasl_scram.erl b/src/cyrsasl_scram.erl index dc671b243..33d18cd1a 100644 --- a/src/cyrsasl_scram.erl +++ b/src/cyrsasl_scram.erl @@ -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 parse_attribute(UserNameAttribute) of - {error, Reason} -> - {error, Reason}; - {_, EscapedUserName} -> - case unescape_username(EscapedUserName) of - 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}; - {Ret, _AuthModule} -> - {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), - {scram:stored_key(scram:client_key(SaltedPassword)), - 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}} - end; - _Else -> - {error, "not-supported"} - end - end - end; - _Else -> - {error, "bad-protocol"} - end; + case str:tokens(ClientIn, <<",">>) of + [CBind, UserNameAttribute, ClientNonceAttribute] + when (CBind == <<"y">>) or (CBind == <<"n">>) -> + case parse_attribute(UserNameAttribute) of + {error, Reason} -> {error, Reason}; + {_, EscapedUserName} -> + case unescape_username(EscapedUserName) of + 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}; + {Ret, _AuthModule} -> + {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), + {scram:stored_key(scram:client_key(SaltedPassword)), + scram:server_key(SaltedPassword), + TempSalt, + ?SCRAM_DEFAULT_ITERATION_COUNT} + end, + 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 = + <>, + client_nonce = ClientNonce, + server_nonce = ServerNonce, + username = UserName}} + end; + _Else -> {error, <<"not-supported">>} + end + end + end; + _Else -> {error, <<"bad-protocol">>} + end; mech_step(#state{step = 4} = State, ClientIn) -> - case string:tokens(ClientIn, ",") of - [GS2ChannelBindingAttribute, NonceAttribute, ClientProofAttribute] -> - case parse_attribute(GS2ChannelBindingAttribute) of - {$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, - 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), - 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"} - end; - _Else -> - {error, "bad-protocol"} - end; - {$r, _} -> - {error, "bad-nonce"}; - _Else -> - {error, "bad-protocol"} - end; - _Else -> - {error, "bad-protocol"} + case str:tokens(ClientIn, <<",">>) of + [GS2ChannelBindingAttribute, NonceAttribute, + ClientProofAttribute] -> + case parse_attribute(GS2ChannelBindingAttribute) of + {$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)/binary, + (State#state.server_nonce)/binary>>, + case parse_attribute(NonceAttribute) of + {$r, CompareNonce} when CompareNonce == Nonce -> + case parse_attribute(ClientProofAttribute) of + {$p, ClientProofB64} -> + 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=", + (jlib:encode_base64(ServerSignature))/binary>>}; + true -> {error, <<"bad-auth">>} + end; + _Else -> {error, <<"bad-protocol">>} + end; + {$r, _} -> {error, <<"bad-nonce">>}; + _Else -> {error, <<"bad-protocol">>} end; - _Else -> - {error, "bad-protocol"} - end. + _Else -> {error, <<"bad-protocol">>} + end; + _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 - true -> - if - SecondChar == $= -> - String = string:substr(Attribute, 3), - {lists:nth(1, Attribute), String}; - true -> - {error, "bad-format second char not equal sign"} - end; - _Else -> - {error, "bad-format first char not a letter"} - end; - true -> - {error, "bad-format attribute too short"} - end. + 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 = 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">>} + end; + true -> {error, <<"bad-format attribute too short">>} + end. -unescape_username("") -> - ""; +unescape_username(<<"">>) -> <<"">>; unescape_username(EscapedUsername) -> - Pos = string: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; - 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 - end - end - end. - -is_alpha(Char) when Char >= $a, Char =< $z -> - true; -is_alpha(Char) when Char >= $A, Char =< $Z -> - true; -is_alpha(_) -> - false. + Pos = str:str(EscapedUsername, <<"=">>), + if Pos == 0 -> EscapedUsername; + true -> + Start = str:substr(EscapedUsername, 1, Pos - 1), + End = str:substr(EscapedUsername, Pos), + EndLen = byte_size(End), + if EndLen < 3 -> error; + true -> + case str:substr(End, 1, 3) of + <<"=2C">> -> + <>; + <<"=3D">> -> + <>; + _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. diff --git a/src/ejabberd.hrl b/src/ejabberd.hrl index 6edd5b8de..d2832ee4d 100644 --- a/src/ejabberd.hrl +++ b/src/ejabberd.hrl @@ -21,44 +21,57 @@ %% This macro returns a string of the ejabberd version running, e.g. "2.3.4" %% If the ejabberd application description isn't loaded, returns atom: undefined --define(VERSION, element(2, application:get_key(ejabberd,vsn))). +-define(VERSION, ejabberd_config:get_version()). --define(MYHOSTS, ejabberd_config:get_global_option(hosts)). --define(MYNAME, hd(ejabberd_config:get_global_option(hosts))). --define(MYLANG, ejabberd_config:get_global_option(language)). +-define(MYHOSTS, ejabberd_config:get_myhosts()). --define(MSGS_DIR, "msgs"). --define(CONFIG_PATH, "ejabberd.cfg"). --define(LOG_PATH, "ejabberd.log"). +-define(MYNAME, hd(ejabberd_config:get_myhosts())). --define(EJABBERD_URI, "http://www.process-one.net/en/ejabberd/"). +-define(MYLANG, ejabberd_config:get_mylang()). + +-define(MSGS_DIR, <<"msgs">>). + +-define(CONFIG_PATH, <<"ejabberd.cfg">>). + +-define(LOG_PATH, <<"ejabberd.log">>). + +-define(EJABBERD_URI, <<"http://www.process-one.net/en/ejabberd/">>). -define(S2STIMEOUT, 600000). %%-define(DBGFSM, true). --record(scram, {storedkey, serverkey, salt, iterationcount}). +-record(scram, + {storedkey = <<"">>, + serverkey = <<"">>, + salt = <<"">>, + iterationcount = 0 :: integer()}). + +-type scram() :: #scram{}. + -define(SCRAM_DEFAULT_ITERATION_COUNT, 4096). %% --------------------------------- %% Logging mechanism %% Print in standard output --define(PRINT(Format, Args), - io:format(Format, Args)). +-define(PRINT(Format, Args), io:format(Format, Args)). -define(DEBUG(Format, Args), - ejabberd_logger:debug_msg(?MODULE,?LINE,Format, Args)). + ejabberd_logger:debug_msg(?MODULE, ?LINE, Format, + Args)). -define(INFO_MSG(Format, Args), - ejabberd_logger:info_msg(?MODULE,?LINE,Format, Args)). - + ejabberd_logger:info_msg(?MODULE, ?LINE, Format, Args)). + -define(WARNING_MSG(Format, Args), - ejabberd_logger:warning_msg(?MODULE,?LINE,Format, Args)). - + ejabberd_logger:warning_msg(?MODULE, ?LINE, Format, + Args)). + -define(ERROR_MSG(Format, Args), - ejabberd_logger:error_msg(?MODULE,?LINE,Format, Args)). + ejabberd_logger:error_msg(?MODULE, ?LINE, Format, + Args)). -define(CRITICAL_MSG(Format, Args), - ejabberd_logger:critical_msg(?MODULE,?LINE,Format, Args)). - + ejabberd_logger:critical_msg(?MODULE, ?LINE, Format, + Args)). diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index 031470c04..9fa95abf6 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -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 diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl index 562c4870f..393d7afb2 100644 --- a/src/ejabberd_app.erl +++ b/src/ejabberd_app.erl @@ -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 -> - lists:foreach( - fun({Module, Args}) -> - gen_mod:start_module(Host, Module, Args) - end, Modules) - end + 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, ?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 -> - lists:foreach( - fun({Module, _Args}) -> - gen_mod:stop_module_keep_config(Host, Module) - end, Modules) - end + 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, ?MYHOSTS). connect_nodes() -> - case ejabberd_config:get_local_option(cluster_nodes) of - undefined -> - ok; - Nodes when is_list(Nodes) -> - lists:foreach(fun(Node) -> - net_kernel:connect_node(Node) - end, Nodes) - end. + 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). %% @spec () -> string() %% @doc Returns the full path to the ejabberd log file. diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index 7485f8234..298cdf1eb 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -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,55 +50,80 @@ %%%---------------------------------------------------------------------- %%% 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 - {true, _AuthModule} -> true; - false -> false + case check_password_with_authmodule(User, Server, + Password) + of + {true, _AuthModule} -> true; + false -> false end. %% @doc Check if the user and password can login in server. %% @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 - {true, _AuthModule} -> true; - false -> false +-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. %% @doc Check if the user and password can login in server. @@ -122,199 +136,224 @@ 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 - {error, not_allowed}; + 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}; - false -> - case lists:member(jlib:nameprep(Server), ?MYHOSTS) of - true -> - Res = lists:foldl( - fun(_M, {atomic, ok} = Res) -> - Res; - (M, _) -> - M:try_register(User, Server, Password) - end, {error, not_allowed}, auth_modules(Server)), - case Res of - {atomic, ok} -> - ejabberd_hooks:run(register_user, Server, - [User, Server]), - {atomic, ok}; - _ -> Res - end; - false -> - {error, not_allowed} - end + case is_user_exists(User, Server) of + true -> {atomic, exists}; + false -> + case lists:member(jlib:nameprep(Server), ?MYHOSTS) of + true -> + Res = lists:foldl(fun (_M, {atomic, ok} = Res) -> Res; + (M, _) -> + M:try_register(User, Server, Password) + end, + {error, not_allowed}, auth_modules(Server)), + case Res of + {atomic, ok} -> + ejabberd_hooks:run(register_user, Server, + [User, Server]), + {atomic, ok}; + _ -> Res + end; + 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) -> - M:get_vh_registered_users(Server) - end, auth_modules(Server)). + lists:flatmap(fun (M) -> + M:get_vh_registered_users(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) - end - end, auth_modules(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)). get_vh_registered_users_number(Server) -> - 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))). + 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))). + +-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 - true -> - M:get_vh_registered_users_number(Server, Opts); - false -> - length(M:get_vh_registered_users(Server)) - end - end, auth_modules(Server))). - %% @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); + false -> + length(M:get_vh_registered_users(Server)) + end + end, + auth_modules(Server))). + +-spec get_password(binary(), binary()) -> false | binary(). + get_password(User, Server) -> - lists:foldl( - fun(M, false) -> - M:get_password(User, Server); - (_M, Password) -> - Password - end, false, auth_modules(Server)). + lists:foldl(fun (M, false) -> + M:get_password(User, 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 + 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~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) -> + 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) -> +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 + true -> true; + false -> + is_user_exists_in_other_modules_loop(AuthModules, User, + Server); + {error, Error} -> + ?DEBUG("The authentication module ~p returned " + "an error~nwhen checking user ~p in server " + "~p~nError message: ~p", + [AuthModule, User, Server, Error]), + maybe end. +-spec remove_user(binary(), binary()) -> ok. %% @spec (User, Server) -> ok %% @doc Remove user. %% Note: it may return ok even if there was some problem removing the user. remove_user(User, Server) -> - lists:foreach( - fun(M) -> - M:remove_user(User, Server) - end, auth_modules(Server)), - ejabberd_hooks:run(remove_user, jlib:nameprep(Server), [User, Server]), + lists:foreach(fun (M) -> M:remove_user(User, Server) + end, + auth_modules(Server)), + ejabberd_hooks:run(remove_user, jlib:nameprep(Server), + [User, Server]), ok. %% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request | error @@ -322,41 +361,49 @@ remove_user(User, Server) -> %% The removal is attempted in each auth method provided: %% when one returns 'ok' the loop stops; %% if no method returns 'ok' then it returns the error message indicated by the last method attempted. +-spec remove_user(binary(), binary(), binary()) -> any(). + remove_user(User, Server, Password) -> - R = lists:foldl( - fun(_M, ok = Res) -> - Res; - (M, _) -> - M:remove_user(User, Server, Password) - end, error, auth_modules(Server)), + R = lists:foldl(fun (_M, ok = Res) -> Res; + (M, _) -> M:remove_user(User, Server, Password) + end, + error, auth_modules(Server)), case R of - ok -> ejabberd_hooks:run(remove_user, jlib:nameprep(Server), [User, Server]); - _ -> none + 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; - S -> - Set = lists:foldl( - fun(C, [Digit, Printable, LowLetter, HiLetter, Other]) -> - if C >= $a, C =< $z -> - [Digit, Printable, 26, HiLetter, Other]; - C >= $0, C =< $9 -> - [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]; - true -> - [Digit, Printable, LowLetter, HiLetter, 128] - end - end, [0, 0, 0, 0, 0], S), - length(S) * math:log(lists:sum(Set))/math:log(2) +entropy(B) -> + case binary_to_list(B) of + "" -> 0.0; + S -> + Set = lists:foldl(fun (C, + [Digit, Printable, LowLetter, HiLetter, + Other]) -> + if C >= $a, C =< $z -> + [Digit, Printable, 26, HiLetter, + Other]; + C >= $0, C =< $9 -> + [9, Printable, LowLetter, HiLetter, + Other]; + C >= $A, C =< $Z -> + [Digit, Printable, LowLetter, 26, + Other]; + C >= 33, C =< 126 -> + [Digit, 33, LowLetter, HiLetter, + Other]; + true -> + [Digit, Printable, LowLetter, + HiLetter, 128] + end + 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) -> - auth_modules(Server) - end, ?MYHOSTS)). + lists:usort(lists:flatmap(fun (Server) -> + auth_modules(Server) + 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). diff --git a/src/ejabberd_auth_anonymous.erl b/src/ejabberd_auth_anonymous.erl index ebdbf9680..c19effabe 100644 --- a/src/ejabberd_auth_anonymous.erl +++ b/src/ejabberd_auth_anonymous.erl @@ -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()]}, @@ -80,13 +77,13 @@ allow_anonymous(Host) -> %% anonymous protocol can be: sasl_anon|login_anon|both is_sasl_anonymous_enabled(Host) -> case allow_anonymous(Host) of - false -> false; - true -> - case anonymous_protocol(Host) of - sasl_anon -> true; - both -> true; - _Other -> false - end + false -> false; + true -> + case anonymous_protocol(Host) of + sasl_anon -> true; + both -> true; + _Other -> false + end end. %% Return true if anonymous login is enabled on the server @@ -94,30 +91,33 @@ is_sasl_anonymous_enabled(Host) -> %% clients that do not support anonymous login) is_login_anonymous_enabled(Host) -> case allow_anonymous(Host) of - false -> false; - true -> - case anonymous_protocol(Host) of - login_anon -> true; - both -> true; - _Other -> false - end + false -> false; + true -> + case anonymous_protocol(Host) of + login_anon -> true; + both -> true; + _Other -> false + end end. %% 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}) - end, + 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 - true -> - ejabberd_hooks:run(register_user, LServer, [LUser, LServer]), - US = {LUser, LServer}, - mnesia:sync_dirty( - fun() -> mnesia:write(#anonymous{us = US, sid=SID}) - end); - false -> - ok +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]), + US = {LUser, LServer}, + mnesia:sync_dirty(fun () -> + mnesia:write(#anonymous{us = US, + sid = SID}) + end); + 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,41 +175,42 @@ 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 - %% 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 - maybe -> false; - false -> login(User, Server) + 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 + maybe -> false; + false -> login(User, Server) end. login(User, Server) -> case is_login_anonymous_enabled(Server) of - false -> false; - true -> - case anonymous_user_exist(User, Server) of - %% Reject the login if an anonymous user with the same login - %% is already logged and if multiple login has not been enable - %% in the config file. - true -> allow_multiple_connections(Server); - %% Accept login and add user to the anonymous table - false -> true - end + false -> false; + true -> + case anonymous_user_exist(User, Server) of + %% Reject the login if an anonymous user with the same login + %% is already logged and if multiple login has not been enable + %% in the config file. + true -> allow_multiple_connections(Server); + %% Accept login and add user to the anonymous table + false -> true + end end. %% When anonymous login is enabled, check that the user is permanent before %% 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 - %% We return the default value if the user is anonymous - true -> - DefaultValue; - %% We return the permanent user password otherwise - false -> - false + case anonymous_user_exist(User, Server) or + login(User, Server) + of + %% We return the default value if the user is anonymous + true -> DefaultValue; + %% We return the permanent user password otherwise + false -> false + end. + +get_password_s(User, Server) -> + case get_password(User, Server) of + 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. diff --git a/src/ejabberd_auth_external.erl b/src/ejabberd_auth_external.erl index 08f2856ac..8ae6a1df5 100644 --- a/src/ejabberd_auth_external.erl +++ b/src/ejabberd_auth_external.erl @@ -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]), - no_cache; - _ -> cache - end + 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]), + 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) + false -> check_password_extauth(User, Server, Password); + {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; - _ -> {error, unknown_problem} + 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) + false -> try_register_extauth(User, Server, Password); + {true, _CacheTime} -> + try_register_external_cache(User, Server, Password) end. dirty_get_registered_users() -> @@ -110,56 +108,60 @@ dirty_get_registered_users() -> 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). +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) + false -> false; + {true, CacheTime} -> + get_password_cache(User, Server, CacheTime) end. get_password_s(User, Server) -> case get_password(User, Server) of - false -> []; - Other -> Other + false -> <<"">>; + Other -> Other end. %% @spec (User, Server) -> true | false | {error, Error} is_user_exists(User, Server) -> try extauth:is_user_exists(User, Server) of - Res -> Res + Res -> Res catch - _:Error -> {error, Error} + _:Error -> {error, Error} end. remove_user(User, Server) -> case extauth:remove_user(User, Server) of - false -> false; - true -> - case get_cache_option(Server) of - false -> false; - {true, _CacheTime} -> - ejabberd_auth_internal:remove_user(User, Server) - end + false -> false; + true -> + case get_cache_option(Server) of + false -> false; + {true, _CacheTime} -> + ejabberd_auth_internal:remove_user(User, Server) + end end. remove_user(User, Server, Password) -> case extauth:remove_user(User, Server, Password) of - false -> false; - true -> - case get_cache_option(Server) of - false -> false; - {true, _CacheTime} -> - ejabberd_auth_internal:remove_user(User, Server, Password) - end + false -> false; + true -> + case get_cache_option(Server) of + false -> false; + {true, _CacheTime} -> + ejabberd_auth_internal:remove_user(User, Server, + Password) + end end. %%% @@ -168,45 +170,50 @@ 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", []), - 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; - %% Else (password nonvalid in Mnesia), check in extauth and cache result - false -> - check_password_external_cache(User, Server, Password) - end; - %% Else (need to refresh), check in extauth and cache result - false -> - check_password_external_cache(User, Server, Password) - end + 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", + []), + check_password_external_cache(User, Server, Password); + TimeStamp -> + 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; + %% Else (password nonvalid in Mnesia), check in extauth and cache result + false -> + check_password_external_cache(User, Server, Password) + end; + %% Else (need to refresh), check in extauth and cache result + false -> + check_password_external_cache(User, Server, Password) + end end. get_password_internal(User, Server) -> @@ -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; - mod_last_required -> - ?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 - end + 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", + []), + false; + TimeStamp -> + case is_fresh_enough(TimeStamp, CacheTime) of + 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 + true -> + set_password_internal(User, Server, Password), true; + 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; - _ -> {error, not_allowed} + {atomic, ok} = 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 @@ -276,38 +277,35 @@ is_fresh_enough(TimeStampLast, CacheTime) -> %% TODO: Update time format to XEP-0202: Entity Time get_last_access(User, Server) -> case ejabberd_sm:get_user_resources(User, Server) of - [] -> - _US = {User, Server}, - case get_last_info(User, Server) of - mod_last_required -> - mod_last_required; - not_found -> - never; - {ok, Timestamp, _Status} -> - Timestamp - end; - _ -> - online + [] -> + _US = {User, Server}, + case get_last_info(User, Server) of + mod_last_required -> mod_last_required; + not_found -> never; + {ok, Timestamp, _Status} -> Timestamp + end; + _ -> 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); - no_mod_last -> mod_last_required + mod_last -> mod_last:get_last_info(User, Server); + no_mod_last -> mod_last_required end. %% @spec (Server) -> mod_last | no_mod_last get_mod_last_enabled(Server) -> case gen_mod:is_loaded(Server, mod_last) of - true -> mod_last; - false -> no_mod_last + true -> mod_last; + false -> no_mod_last end. get_mod_last_configured(Server) -> case is_configured(Server, mod_last) of - true -> mod_last; - false -> no_mod_last + true -> mod_last; + false -> no_mod_last end. is_configured(Host, Module) -> - lists:keymember(Module, 1, ejabberd_config:get_local_option({modules, Host})). + gen_mod:is_loaded(Host, Module). diff --git a/src/ejabberd_auth_internal.erl b/src/ejabberd_auth_internal.erl index 4b5bcd327..b3587e211 100644 --- a/src/ejabberd_auth_internal.erl +++ b/src/ejabberd_auth_internal.erl @@ -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,8 +55,9 @@ %%% API %%%---------------------------------------------------------------------- start(Host) -> - mnesia:create_table(passwd, [{disc_copies, [node()]}, - {attributes, record_info(fields, passwd)}]), + mnesia:create_table(passwd, + [{disc_copies, [node()]}, + {attributes, record_info(fields, passwd)}]), mnesia:create_table(reg_users_counter, [{ram_copies, [node()]}, {attributes, record_info(fields, reg_users_counter)}]), @@ -72,22 +70,22 @@ update_reg_users_counter_table(Server) -> Set = get_vh_registered_users(Server), Size = length(Set), LServer = jlib:nameprep(Server), - F = fun() -> - mnesia:write(#reg_users_counter{vhost = LServer, - count = Size}) + F = fun () -> + mnesia:write(#reg_users_counter{vhost = LServer, + count = Size}) end, mnesia:sync_dirty(F). plain_password_required() -> case is_scrammed() of - false -> false; - true -> true + false -> false; + true -> true end. store_type() -> case is_scrammed() of - false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM - true -> scram %% allows: PLAIN SCRAM + false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM + true -> scram %% allows: PLAIN SCRAM end. check_password(User, Server, Password) -> @@ -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) -> - is_password_scram_valid(Password, Scram); - _ -> - false + [#passwd{password = Password}] + when is_binary(Password) -> + Password /= <<"">>; + [#passwd{password = Scram}] + when is_record(Scram, scram) -> + is_password_scram_valid(Password, Scram); + _ -> 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 /= "" -> - Digest == DigestGen(Passwd); - true -> - false - end, - 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 /= "" -> - Digest == DigestGen(Passwd); - true -> - false - end, - if DigRes -> - true; - true -> - (Passwd == Password) and (Password /= "") - end; - _ -> - false + [#passwd{password = Passwd}] when is_binary(Passwd) -> + DigRes = if Digest /= <<"">> -> + Digest == DigestGen(Passwd); + true -> false + end, + if DigRes -> true; + true -> (Passwd == Password) and (Password /= <<"">>) + end; + [#passwd{password = Scram}] + when is_record(Scram, scram) -> + Passwd = jlib:decode_base64(Scram#scram.storedkey), + DigRes = if Digest /= <<"">> -> + Digest == DigestGen(Passwd); + true -> false + end, + if DigRes -> true; + true -> (Passwd == Password) and (Password /= <<"">>) + end; + _ -> false end. %% @spec (User::string(), Server::string(), Password::string()) -> @@ -143,49 +135,48 @@ set_password(User, Server, Password) -> LUser = jlib:nodeprep(User), LServer = jlib:nameprep(Server), US = {LUser, LServer}, - if - (LUser == error) or (LServer == error) -> - {error, invalid_jid}; - true -> - F = fun() -> - Password2 = case is_scrammed() and is_list(Password) of - true -> password_to_scram(Password); - false -> Password - end, - mnesia:write(#passwd{us = US, - password = Password2}) - end, - {atomic, ok} = mnesia:transaction(F), - ok + if (LUser == error) or (LServer == error) -> + {error, invalid_jid}; + true -> + F = fun () -> + Password2 = case is_scrammed() and is_binary(Password) + of + true -> password_to_scram(Password); + false -> Password + end, + mnesia:write(#passwd{us = US, password = Password2}) + 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) -> - {error, invalid_jid}; - true -> - F = fun() -> - case mnesia:read({passwd, US}) of - [] -> - Password2 = case is_scrammed() and is_list(Password) of - true -> password_to_scram(Password); - false -> Password - end, - mnesia:write(#passwd{us = US, - password = Password2}), - mnesia:dirty_update_counter( - reg_users_counter, - LServer, 1), - ok; - [_E] -> - exists - end - end, - mnesia:transaction(F) + if (LUser == error) or (LServer == error) -> + {error, invalid_jid}; + true -> + F = fun () -> + case mnesia:read({passwd, US}) 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, + LServer, 1), + ok; + [_E] -> exists + end + end, + mnesia:transaction(F) end. %% Get all registered users in Mnesia @@ -194,75 +185,81 @@ dirty_get_registered_users() -> get_vh_registered_users(Server) -> LServer = jlib:nameprep(Server), - mnesia:dirty_select( - passwd, - [{#passwd{us = '$1', _ = '_'}, - [{'==', {element, 2, '$1'}, LServer}], - ['$1']}]). + mnesia:dirty_select(passwd, + [{#passwd{us = '$1', _ = '_'}, + [{'==', {element, 2, '$1'}, LServer}], ['$1']}]). -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}]) - when is_integer(Limit) and is_integer(Offset) -> +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}]) + when is_integer(Limit) and is_integer(Offset) -> case get_vh_registered_users(Server) of - [] -> - []; - Users -> - Set = lists:keysort(1, Users), - L = length(Set), - Start = if Offset < 1 -> 1; - Offset > L -> L; - true -> Offset - end, - lists:sublist(Set, Start, Limit) + [] -> []; + Users -> + Set = lists:keysort(1, Users), + L = length(Set), + Start = if Offset < 1 -> 1; + Offset > L -> L; + true -> 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)], +get_vh_registered_users(Server, [{prefix, Prefix}]) + 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 - [] -> - []; - Users -> - Set = lists:keysort(1, Users), - L = length(Set), - Start = if Offset < 1 -> 1; - Offset > L -> L; - true -> Offset - end, - lists:sublist(Set, Start, Limit) +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), + Start = if Offset < 1 -> 1; + Offset > L -> L; + true -> Offset + 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; - _ -> 0 + [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) -> - Password; - [#passwd{password = Scram}] when is_record(Scram, scram) -> - {base64:decode(Scram#scram.storedkey), - base64:decode(Scram#scram.serverkey), - base64:decode(Scram#scram.salt), - Scram#scram.iterationcount}; - _ -> - false + [#passwd{password = Password}] + when is_binary(Password) -> + Password; + [#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 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) -> - 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) -> + <<"">>; + _ -> <<"">> end. %% @spec (User, Server) -> true | false | {error, Error} @@ -301,12 +300,9 @@ is_user_exists(User, Server) -> LServer = jlib:nameprep(Server), US = {LUser, LServer}, case catch mnesia:dirty_read({passwd, US}) of - [] -> - false; - [_] -> - true; - Other -> - {error, Other} + [] -> false; + [_] -> true; + Other -> {error, Other} end. %% @spec (User, Server) -> ok @@ -316,13 +312,13 @@ remove_user(User, Server) -> LUser = jlib:nodeprep(User), LServer = jlib:nameprep(Server), US = {LUser, LServer}, - F = fun() -> + F = fun () -> mnesia:delete({passwd, US}), - mnesia:dirty_update_counter(reg_users_counter, - LServer, -1) - end, + mnesia:dirty_update_counter(reg_users_counter, LServer, + -1) + end, mnesia:transaction(F), - ok. + ok. %% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request %% @doc Remove user if the provided password is correct. @@ -330,79 +326,65 @@ remove_user(User, Server, Password) -> LUser = jlib:nodeprep(User), LServer = jlib:nameprep(Server), US = {LUser, LServer}, - F = fun() -> + F = fun () -> case mnesia:read({passwd, US}) of - [#passwd{password = Password}] when is_list(Password) -> - mnesia:delete({passwd, US}), - mnesia:dirty_update_counter(reg_users_counter, - LServer, -1), - ok; - [#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 - end; - _ -> - not_exists + [#passwd{password = Password}] + when is_binary(Password) -> + mnesia:delete({passwd, US}), + mnesia:dirty_update_counter(reg_users_counter, LServer, + -1), + ok; + [#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 + end; + _ -> not_exists end - 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 -> - 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) + Fields -> + convert_to_binary(Fields), + maybe_scram_passwords(), + ok; + _ -> + ?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,38 +393,43 @@ 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; - {false, [#passwd{password = Scram}]} when is_record(Scram, scram) -> - true; - _ -> - false + {true, _} -> true; + {false, [#passwd{password = Scram}]} + when is_record(Scram, scram) -> + true; + _ -> 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 + 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 end. maybe_scram_passwords() -> case is_scrammed() of - true -> scram_passwords(); - false -> ok + true -> scram_passwords(); + false -> ok end. scram_passwords() -> - ?INFO_MSG("Converting the stored passwords into SCRAM bits", []), - Fun = fun(#passwd{password = Password} = P) -> + ?INFO_MSG("Converting the stored passwords into " + "SCRAM bits", + []), + Fun = fun (#passwd{password = Password} = P) -> Scram = password_to_scram(Password), P#passwd{password = Scram} end, @@ -450,21 +437,39 @@ scram_passwords() -> mnesia:transform_table(passwd, Fun, Fields). password_to_scram(Password) -> - password_to_scram(Password, ?SCRAM_DEFAULT_ITERATION_COUNT). + password_to_scram(Password, + ?SCRAM_DEFAULT_ITERATION_COUNT). password_to_scram(Password, IterationCount) -> Salt = crypto:rand_bytes(?SALT_LENGTH), - SaltedPassword = scram:salted_password(Password, Salt, IterationCount), - StoredKey = scram:stored_key(scram:client_key(SaltedPassword)), + SaltedPassword = scram:salted_password(Password, Salt, + IterationCount), + StoredKey = + scram:stored_key(scram:client_key(SaltedPassword)), ServerKey = scram:server_key(SaltedPassword), - #scram{storedkey = base64:encode(StoredKey), - serverkey = base64:encode(ServerKey), - salt = base64:encode(Salt), + #scram{storedkey = jlib:encode_base64(StoredKey), + serverkey = jlib:encode_base64(ServerKey), + salt = jlib:encode_base64(Salt), iterationcount = IterationCount}. is_password_scram_valid(Password, Scram) -> IterationCount = Scram#scram.iterationcount, - Salt = base64:decode(Scram#scram.salt), - SaltedPassword = scram:salted_password(Password, Salt, IterationCount), - StoredKey = scram:stored_key(scram:client_key(SaltedPassword)), - (base64:decode(Scram#scram.storedkey) == StoredKey). + Salt = jlib:decode_base64(Scram#scram.salt), + SaltedPassword = scram:salted_password(Password, Salt, + IterationCount), + StoredKey = + scram:stored_key(scram:client_key(SaltedPassword)), + jlib:decode_base64(Scram#scram.storedkey) == StoredKey. + +export(_Server) -> + [{passwd, + fun(Host, #passwd{us = {LUser, LServer}, password = Password}) + when LServer == Host -> + Username = ejabberd_odbc:escape(LUser), + Pass = ejabberd_odbc:escape(Password), + [[<<"delete from users where username='">>, Username, <<"';">>], + [<<"insert into users(username, password) " + "values ('">>, Username, <<"', '">>, Pass, <<"');">>]]; + (_Host, _R) -> + [] + end}]. diff --git a/src/ejabberd_auth_ldap.erl b/src/ejabberd_auth_ldap.erl index 5e5ca2422..998f21215 100644 --- a/src/ejabberd_auth_ldap.erl +++ b/src/ejabberd_auth_ldap.erl @@ -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, - 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 - ]). +-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_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; - true -> - case catch check_password_ldap(User, Server, Password) of - {'EXIT', _} -> false; - Result -> Result - end + if Password == <<"">> -> false; + true -> + 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}; - DN -> - eldap_pool:modify_passwd(State#state.eldap_id, DN, Password) + false -> {error, user_not_found}; + DN -> + eldap_pool:modify_passwd(State#state.eldap_id, DN, + Password) end. %% @spec (User, Server, Password) -> {error, not_allowed} @@ -173,55 +146,56 @@ try_register(_User, _Server, _Password) -> dirty_get_registered_users() -> Servers = ejabberd_config:get_vh_by_auth_method(ldap), - lists:flatmap( - fun(Server) -> - get_vh_registered_users(Server) - end, Servers). + lists:flatmap(fun (Server) -> + get_vh_registered_users(Server) + end, + Servers). get_vh_registered_users(Server) -> case catch get_vh_registered_users_ldap(Server) of - {'EXIT', _} -> []; - Result -> Result - end. + {'EXIT', _} -> []; + 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 %%%---------------------------------------------------------------------- check_password_ldap(User, Server, Password) -> - {ok, State} = eldap_utils:get_state(Server, ?MODULE), - case find_user_dn(User, State) of - false -> - false; - DN -> - case eldap_pool:bind(State#state.bind_eldap_id, DN, Password) of - ok -> true; - _ -> false - end - end. + {ok, State} = eldap_utils:get_state(Server, ?MODULE), + case find_user_dn(User, State) of + false -> false; + DN -> + case eldap_pool:bind(State#state.bind_eldap_id, DN, + Password) + of + ok -> true; + _ -> false + end + end. get_vh_registered_users_ldap(Server) -> {ok, State} = eldap_utils:get_state(Server, ?MODULE), @@ -230,114 +204,123 @@ get_vh_registered_users_ldap(Server) -> Server = State#state.host, ResAttrs = result_attrs(State), case eldap_filter:parse(State#state.sfilter) of - {ok, EldapFilter} -> - case eldap_pool:search(Eldap_ID, - [{base, State#state.base}, - {filter, EldapFilter}, - {timeout, ?LDAP_SEARCH_TIMEOUT}, - {deref_aliases, State#state.deref_aliases}, - {attributes, ResAttrs}]) of - #eldap_search_result{entries = Entries} -> - lists:flatmap( - fun(#eldap_entry{attributes = Attrs, - object_name = DN}) -> + {ok, EldapFilter} -> + case eldap_pool:search(Eldap_ID, + [{base, State#state.base}, + {filter, EldapFilter}, + {timeout, ?LDAP_SEARCH_TIMEOUT}, + {deref_aliases, State#state.deref_aliases}, + {attributes, ResAttrs}]) + of + #eldap_search_result{entries = Entries} -> + 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 - "" -> []; - {User, UIDFormat} -> - case eldap_utils:get_user_part(User, UIDFormat) of - {ok, U} -> - case jlib:nodeprep(U) of - error -> []; - LU -> [{LU, jlib:nameprep(Server)}] - end; - _ -> [] - end - end + false -> []; + _ -> + case + eldap_utils:find_ldap_attrs(UIDs, + Attrs) + of + <<"">> -> []; + {User, UIDFormat} -> + case + eldap_utils:get_user_part(User, + UIDFormat) + of + {ok, U} -> + case jlib:nodeprep(U) of + error -> []; + LU -> + [{LU, + jlib:nameprep(Server)}] + end; + _ -> [] + end + end end - end, Entries); - _ -> - [] - end; - _ -> - [] - end. + end, + Entries); + _ -> [] + end; + _ -> [] + end. is_user_exists_ldap(User, Server) -> {ok, State} = eldap_utils:get_state(Server, ?MODULE), case find_user_dn(User, State) of - false -> false; - _DN -> true - end. + false -> false; + _DN -> true + end. handle_call(get_state, _From, State) -> - {reply, {ok, State}, 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 - {ok, Filter} -> - case eldap_pool:search(State#state.eldap_id, - [{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} | _]} -> - dn_filter(DN, Attrs, State); - _ -> - false - end; - _ -> - false + 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}, + {deref_aliases, State#state.deref_aliases}, + {attributes, ResAttrs}]) + of + #eldap_search_result{entries = + [#eldap_entry{attributes = Attrs, + object_name = DN} + | _]} -> + dn_filter(DN, Attrs, State); + _ -> false + end; + _ -> 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) + 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; - {S, UAF} -> - case eldap_utils:get_user_part(S, UAF) of - {ok, U} -> [{"%u", U} | Values]; - _ -> Values - end - 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 - end; - _ -> - false + 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]; + _ -> Values + end + 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 + end; + _ -> false end. %% The local filter is used to check an attribute in ejabberd @@ -346,109 +329,92 @@ 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). - + local_filter(equal, Attrs, FilterMatch) -> {Attr, Value} = FilterMatch, case lists:keysearch(Attr, 1, Attrs) of - false -> false; - {value,{Attr,Value}} -> true; - _ -> false + false -> false; + {value, {Attr, Value}} -> true; + _ -> false end; local_filter(notequal, Attrs, FilterMatch) -> not local_filter(equal, Attrs, FilterMatch). -result_attrs(#state{uids = UIDs, dn_filter_attrs = DNFilterAttrs}) -> - lists:foldl( - fun({UID}, Acc) -> - [UID | Acc]; - ({UID, _}, Acc) -> - [UID | Acc] - end, DNFilterAttrs, UIDs). +result_attrs(#state{uids = UIDs, + dn_filter_attrs = DNFilterAttrs}) -> + lists:foldl(fun ({UID}, Acc) -> [UID | Acc]; + ({UID, _}, Acc) -> [UID | Acc] + end, + DNFilterAttrs, UIDs). %%%---------------------------------------------------------------------- %%% Auxiliary functions %%%---------------------------------------------------------------------- parse_options(Host) -> - Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, ?MODULE)), - Bind_Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, bind_ejabberd_auth_ldap)), - LDAPServers = ejabberd_config:get_local_option({ldap_servers, Host}), - LDAPBackups = case ejabberd_config:get_local_option({ldap_backups, Host}) of - undefined -> []; - Backups -> Backups - end, - LDAPEncrypt = ejabberd_config:get_local_option({ldap_encrypt, Host}), - LDAPTLSVerify = ejabberd_config:get_local_option({ldap_tls_verify, Host}), - LDAPTLSCAFile = ejabberd_config:get_local_option({ldap_tls_cacertfile, Host}), - LDAPTLSDepth = ejabberd_config:get_local_option({ldap_tls_depth, Host}), - LDAPPort = case ejabberd_config:get_local_option({ldap_port, Host}) of - undefined -> case LDAPEncrypt of - tls -> ?LDAPS_PORT; - starttls -> ?LDAP_PORT; - _ -> ?LDAP_PORT - end; - P -> P - end, - RootDN = case ejabberd_config:get_local_option({ldap_rootdn, Host}) of - undefined -> ""; - RDN -> RDN - end, - Password = case ejabberd_config:get_local_option({ldap_password, Host}) of - undefined -> ""; - Pass -> Pass - end, - UIDs = case ejabberd_config:get_local_option({ldap_uids, Host}) of - undefined -> [{"uid", "%u"}]; - UI -> eldap_utils:uids_domain_subst(Host, UI) - end, - SubFilter = lists:flatten(eldap_utils:generate_subfilter(UIDs)), - UserFilter = case ejabberd_config:get_local_option({ldap_filter, Host}) of - undefined -> SubFilter; - "" -> SubFilter; - F -> - eldap_utils:check_filter(F), - "(&" ++ SubFilter ++ F ++ ")" - end, - SearchFilter = eldap_filter:do_sub(UserFilter, [{"%u", "*"}]), - LDAPBase = ejabberd_config:get_local_option({ldap_base, Host}), + 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 -> + <<"(&", SubFilter/binary, F/binary, ")">> + end, + SearchFilter = eldap_filter:do_sub(UserFilter, + [{<<"%u">>, <<"*">>}]), {DNFilter, DNFilterAttrs} = - case ejabberd_config:get_local_option({ldap_dn_filter, Host}) of - undefined -> - {undefined, []}; - {DNF, undefined} -> - {DNF, []}; - {DNF, DNFA} -> - {DNF, 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, - 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 - }. + eldap_utils:get_opt({ldap_dn_filter, Host}, [], + fun({DNF, DNFA}) -> + NewDNFA = case DNFA of + undefined -> + []; + _ -> + [iolist_to_binary(A) + || A <- DNFA] + end, + 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 = 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. diff --git a/src/ejabberd_auth_odbc.erl b/src/ejabberd_auth_odbc.erl index 3f648d666..7a2e90e02 100644 --- a/src/ejabberd_auth_odbc.erl +++ b/src/ejabberd_auth_odbc.erl @@ -25,223 +25,197 @@ %%%---------------------------------------------------------------------- -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; - 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}]} -> - false; %% Password is not correct - {selected, ["password"], []} -> - false; %% Account does not exist - {error, _Error} -> - false %% Typical error is that table doesn't exist - catch - _:_ -> - false %% Typical error is database not accessible - end + error -> false; + LUser -> + Username = ejabberd_odbc:escape(LUser), + LServer = jlib:nameprep(Server), + try odbc_queries:get_password(LServer, Username) of + {selected, [<<"password">>], [[Password]]} -> + Password /= <<"">>; + {selected, [<<"password">>], [[_Password2]]} -> + false; %% Password is not correct + {selected, [<<"password">>], []} -> + false; %% Account does not exist + {error, _Error} -> + false %% Typical error is that table doesn't exist + catch + _:_ -> + false %% Typical error is database not accessible + end 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; - 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 /= "" -> - Digest == DigestGen(Passwd); - true -> - false - end, - if DigRes -> - true; - true -> - (Passwd == Password) and (Password /= "") - end; - {selected, ["password"], []} -> - false; %% Account does not exist - {error, _Error} -> - false %% Typical error is that table doesn't exist - catch - _:_ -> - false %% Typical error is database not accessible - end + 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 /= <<"">> -> + Digest == DigestGen(Passwd); + true -> false + end, + if DigRes -> true; + true -> (Passwd == Password) and (Password /= <<"">>) + end; + {selected, [<<"password">>], []} -> + false; %% Account does not exist + {error, _Error} -> + false %% Typical error is that table doesn't exist + catch + _:_ -> + false %% Typical error is database not accessible + end end. %% @spec (User::string(), Server::string(), Password::string()) -> %% ok | {error, invalid_jid} set_password(User, Server, Password) -> case jlib:nodeprep(User) of - 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 - {atomic, ok} -> ok; - Other -> {error, Other} - end + 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 + {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}; - 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} - end + 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} + end end. dirty_get_registered_users() -> Servers = ejabberd_config:get_vh_by_auth_method(odbc), - lists:flatmap( - fun(Server) -> - get_vh_registered_users(Server) - end, Servers). + lists:flatmap(fun (Server) -> + get_vh_registered_users(Server) + 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; - LUser -> - Username = ejabberd_odbc:escape(LUser), - LServer = jlib:nameprep(Server), - case catch odbc_queries:get_password(LServer, Username) of - {selected, ["password"], [{Password}]} -> - Password; - _ -> - false - end + 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 + end end. get_password_s(User, Server) -> case jlib:nodeprep(User) of - error -> - ""; - LUser -> - Username = ejabberd_odbc:escape(LUser), - LServer = jlib:nameprep(Server), - case catch odbc_queries:get_password(LServer, Username) of - {selected, ["password"], [{Password}]} -> - Password; - _ -> - "" - end + error -> <<"">>; + LUser -> + Username = ejabberd_odbc:escape(LUser), + LServer = jlib:nameprep(Server), + 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; - LUser -> - Username = ejabberd_odbc:escape(LUser), - LServer = jlib:nameprep(Server), - try odbc_queries:get_password(LServer, Username) of - {selected, ["password"], [{_Password}]} -> - true; %% Account exists - {selected, ["password"], []} -> - false; %% Account does not exist - {error, Error} -> - {error, Error} %% Typical error is that table doesn't exist - catch - _:B -> - {error, B} %% Typical error is database not accessible - end + error -> false; + LUser -> + Username = ejabberd_odbc:escape(LUser), + LServer = jlib:nameprep(Server), + try odbc_queries:get_password(LServer, Username) of + {selected, [<<"password">>], [[_Password]]} -> + true; %% Account exists + {selected, [<<"password">>], []} -> + false; %% Account does not exist + {error, Error} -> {error, Error} + catch + _:B -> {error, B} + end end. %% @spec (User, Server) -> ok | error @@ -249,37 +223,34 @@ 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; - LUser -> - Username = ejabberd_odbc:escape(LUser), - LServer = jlib:nameprep(Server), - catch odbc_queries:del_user(LServer, Username), - ok + error -> error; + LUser -> + Username = ejabberd_odbc:escape(LUser), + LServer = jlib:nameprep(Server), + catch odbc_queries:del_user(LServer, Username), + ok end. %% @spec (User, Server, Password) -> ok | error | not_exists | not_allowed %% @doc Remove user if the provided password is correct. remove_user(User, Server, Password) -> case jlib:nodeprep(User) of - 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), - case Result of - {selected, ["password"], [{Password}]} -> - ok; - {selected, ["password"], []} -> - not_exists; - _ -> - not_allowed - end - end, - {atomic, Result} = odbc_queries:sql_transaction(LServer, F), - Result + 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), + case Result of + {selected, [<<"password">>], [[Password]]} -> ok; + {selected, [<<"password">>], []} -> not_exists; + _ -> not_allowed + end + end, + {atomic, Result} = odbc_queries:sql_transaction(LServer, + F), + Result end. diff --git a/src/ejabberd_auth_pam.erl b/src/ejabberd_auth_pam.erl index 29752ba8d..a1400fe8e 100644 --- a/src/ejabberd_auth_pam.erl +++ b/src/ejabberd_auth_pam.erl @@ -24,102 +24,102 @@ %%% %%%------------------------------------------------------------------- -module(ejabberd_auth_pam). + -author('xram@jabber.ru'). -%% External exports --export([start/1, - set_password/3, - check_password/3, - check_password/5, - try_register/3, - dirty_get_registered_users/0, - get_vh_registered_users/1, - get_password/2, - get_password_s/2, - is_user_exists/2, - remove_user/2, - remove_user/3, - store_type/0, - plain_password_required/0 - ]). +-behaviour(ejabberd_auth). +%% External exports %%==================================================================== %% API %%==================================================================== +-export([start/1, set_password/3, check_password/3, + check_password/5, try_register/3, + dirty_get_registered_users/0, get_vh_registered_users/1, + get_vh_registered_users/2, get_vh_registered_users_number/1, + get_vh_registered_users_number/2, + get_password/2, get_password_s/2, is_user_exists/2, + remove_user/2, remove_user/3, store_type/0, + plain_password_required/0]). + start(_Host) -> case epam:start() of - {ok, _} -> ok; - {error,{already_started, _}} -> ok; - Err -> Err + {ok, _} -> ok; + {error, {already_started, _}} -> ok; + Err -> Err end. set_password(_User, _Server, _Password) -> {error, not_allowed}. -check_password(User, Server, Password, _Digest, _DigestGen) -> +check_password(User, Server, Password, _Digest, + _DigestGen) -> check_password(User, Server, Password). check_password(User, Host, Password) -> Service = get_pam_service(Host), UserInfo = case get_pam_userinfotype(Host) of - username -> User; - jid -> User++"@"++Host - end, - case catch epam:authenticate(Service, UserInfo, Password) of - true -> true; - _ -> false + username -> User; + jid -> <> + end, + case catch epam:authenticate(Service, UserInfo, + Password) + of + true -> true; + _ -> false end. 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 is_user_exists(User, Host) -> Service = get_pam_service(Host), UserInfo = case get_pam_userinfotype(Host) of - username -> User; - jid -> User++"@"++Host - end, + username -> User; + jid -> <> + end, case catch epam:acct_mgmt(Service, UserInfo) of - true -> true; - _ -> false + 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). diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index ed26400f0..f1cde0e0f 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -25,7 +25,9 @@ %%%---------------------------------------------------------------------- -module(ejabberd_c2s). + -author('alexey@process-one.net'). + -update_info({update, 0}). -define(GEN_FSM, p1_fsm). @@ -61,11 +63,13 @@ code_change/4, handle_info/3, terminate/3, - print_state/1 + print_state/1 ]). -include("ejabberd.hrl"). + -include("jlib.hrl"). + -include("mod_privacy.hrl"). -define(SETS, gb_sets). @@ -88,7 +92,7 @@ tls_options = [], authenticated = false, jid, - user = "", server = ?MYNAME, resource = "", + user = "", server = ?MYNAME, resource = <<"">>, sid, pres_t = ?SETS:new(), pres_f = ?SETS:new(), @@ -107,9 +111,13 @@ %-define(DBGFSM, true). -ifdef(DBGFSM). + -define(FSMOPTS, [{debug, [trace]}]). + -else. + -define(FSMOPTS, []). + -endif. %% Module start with or without supervisor: @@ -124,22 +132,26 @@ %% This is the timeout to apply between event when starting a new %% session: -define(C2S_OPEN_TIMEOUT, 60000). + -define(C2S_HIBERNATE_TIMEOUT, 90000). -define(STREAM_HEADER, - "" - "" - ). + <<"">>). --define(STREAM_TRAILER, ""). +-define(STREAM_TRAILER, <<"">>). -define(INVALID_NS_ERR, ?SERR_INVALID_NAMESPACE). + -define(INVALID_XML_ERR, ?SERR_XML_NOT_WELL_FORMED). + -define(HOST_UNKNOWN_ERR, ?SERR_HOST_UNKNOWN). + -define(POLICY_VIOLATION_ERR(Lang, Text), ?SERRT_POLICY_VIOLATION(Lang, Text)). + -define(INVALID_FROM, ?SERR_INVALID_FROM). @@ -153,24 +165,23 @@ start_link(SockData, Opts) -> ?GEN_FSM:start_link(ejabberd_c2s, [SockData, Opts], fsm_limit_opts(Opts) ++ ?FSMOPTS). -socket_type() -> - xml_stream. +socket_type() -> xml_stream. %% Return Username, Resource and presence information get_presence(FsmRef) -> - ?GEN_FSM:sync_send_all_state_event(FsmRef, {get_presence}, 1000). + (?GEN_FSM):sync_send_all_state_event(FsmRef, + {get_presence}, 1000). get_aux_field(Key, #state{aux_fields = Opts}) -> case lists:keysearch(Key, 1, Opts) of - {value, {_, Val}} -> - {ok, Val}; - _ -> - error + {value, {_, Val}} -> {ok, Val}; + _ -> error end. -set_aux_field(Key, Val, #state{aux_fields = Opts} = State) -> +set_aux_field(Key, Val, + #state{aux_fields = Opts} = State) -> Opts1 = lists:keydelete(Key, 1, Opts), - State#state{aux_fields = [{Key, Val}|Opts1]}. + State#state{aux_fields = [{Key, Val} | Opts1]}. del_aux_field(Key, #state{aux_fields = Opts} = State) -> Opts1 = lists:keydelete(Key, 1, Opts), @@ -179,11 +190,13 @@ del_aux_field(Key, #state{aux_fields = Opts} = State) -> get_subscription(From = #jid{}, StateData) -> get_subscription(jlib:jid_tolower(From), StateData); get_subscription(LFrom, StateData) -> - LBFrom = setelement(3, LFrom, ""), - F = ?SETS:is_element(LFrom, StateData#state.pres_f) orelse - ?SETS:is_element(LBFrom, StateData#state.pres_f), - T = ?SETS:is_element(LFrom, StateData#state.pres_t) orelse - ?SETS:is_element(LBFrom, StateData#state.pres_t), + LBFrom = setelement(3, LFrom, <<"">>), + F = (?SETS):is_element(LFrom, StateData#state.pres_f) + orelse + (?SETS):is_element(LBFrom, StateData#state.pres_f), + T = (?SETS):is_element(LFrom, StateData#state.pres_t) + orelse + (?SETS):is_element(LBFrom, StateData#state.pres_t), if F and T -> both; F -> from; T -> to; @@ -193,8 +206,7 @@ get_subscription(LFrom, StateData) -> broadcast(FsmRef, Type, From, Packet) -> FsmRef ! {broadcast, Type, From, Packet}. -stop(FsmRef) -> - ?GEN_FSM:send_event(FsmRef, closed). +stop(FsmRef) -> (?GEN_FSM):send_event(FsmRef, closed). %%%---------------------------------------------------------------------- %%% Callback functions from gen_fsm @@ -209,63 +221,58 @@ stop(FsmRef) -> %%---------------------------------------------------------------------- init([{SockMod, Socket}, Opts]) -> Access = case lists:keysearch(access, 1, Opts) of - {value, {_, A}} -> A; - _ -> all + {value, {_, A}} -> A; + _ -> all end, Shaper = case lists:keysearch(shaper, 1, Opts) of - {value, {_, S}} -> S; - _ -> none + {value, {_, S}} -> S; + _ -> none end, - XMLSocket = - case lists:keysearch(xml_socket, 1, Opts) of - {value, {_, XS}} -> XS; - _ -> false - end, + XMLSocket = case lists:keysearch(xml_socket, 1, Opts) of + {value, {_, XS}} -> XS; + _ -> false + end, Zlib = lists:member(zlib, Opts), StartTLS = lists:member(starttls, Opts), - StartTLSRequired = lists:member(starttls_required, Opts), + StartTLSRequired = lists:member(starttls_required, + Opts), TLSEnabled = lists:member(tls, Opts), - TLS = StartTLS orelse StartTLSRequired orelse TLSEnabled, - TLSOpts1 = - lists:filter(fun({certfile, _}) -> true; - (_) -> false - end, Opts), + TLS = StartTLS orelse + StartTLSRequired orelse TLSEnabled, + TLSOpts1 = lists:filter(fun ({certfile, _}) -> true; + (_) -> false + end, + Opts), TLSOpts = [verify_none | TLSOpts1], IP = peerip(SockMod, Socket), %% Check if IP is blacklisted: case is_ip_blacklisted(IP) of - true -> - ?INFO_MSG("Connection attempt from blacklisted IP: ~s (~w)", - [jlib:ip_to_list(IP), IP]), - {stop, normal}; - false -> - Socket1 = - if - TLSEnabled -> - SockMod:starttls(Socket, TLSOpts); - true -> - Socket - end, - SocketMonitor = SockMod:monitor(Socket1), - {ok, wait_for_stream, #state{socket = Socket1, - sockmod = SockMod, - socket_monitor = SocketMonitor, - xml_socket = XMLSocket, - zlib = Zlib, - tls = TLS, - tls_required = StartTLSRequired, - tls_enabled = TLSEnabled, - tls_options = TLSOpts, - streamid = new_id(), - access = Access, - shaper = Shaper, - ip = IP}, - ?C2S_OPEN_TIMEOUT} + true -> + ?INFO_MSG("Connection attempt from blacklisted " + "IP: ~s (~w)", + [jlib:ip_to_list(IP), IP]), + {stop, normal}; + false -> + Socket1 = if TLSEnabled andalso + SockMod /= ejabberd_frontend_socket -> + SockMod:starttls(Socket, TLSOpts); + true -> Socket + end, + SocketMonitor = SockMod:monitor(Socket1), + StateData = #state{socket = Socket1, sockmod = SockMod, + socket_monitor = SocketMonitor, + xml_socket = XMLSocket, zlib = Zlib, tls = TLS, + tls_required = StartTLSRequired, + tls_enabled = TLSEnabled, tls_options = TLSOpts, + streamid = new_id(), access = Access, + shaper = Shaper, ip = IP}, + {ok, wait_for_stream, StateData, ?C2S_OPEN_TIMEOUT} end. %% Return list of all available resources of contacts, get_subscribed(FsmRef) -> - ?GEN_FSM:sync_send_all_state_event(FsmRef, get_subscribed, 1000). + (?GEN_FSM):sync_send_all_state_event(FsmRef, + get_subscribed, 1000). %%---------------------------------------------------------------------- %% Func: StateName/2 @@ -276,17 +283,15 @@ get_subscribed(FsmRef) -> wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> DefaultLang = case ?MYLANG of - undefined -> - "en"; - DL -> - DL - end, - case xml:get_attr_s("xmlns:stream", Attrs) of + undefined -> <<"en">>; + DL -> DL + end, + case xml:get_attr_s(<<"xmlns:stream">>, Attrs) of ?NS_STREAM -> - Server = jlib:nameprep(xml:get_attr_s("to", Attrs)), + Server = jlib:nameprep(xml:get_attr_s(<<"to">>, Attrs)), case lists:member(Server, ?MYHOSTS) of true -> - Lang = case xml:get_attr_s("xml:lang", Attrs) of + Lang = case xml:get_attr_s(<<"xml:lang">>, Attrs) of Lang1 when length(Lang1) =< 35 -> %% As stated in BCP47, 4.4.1: %% Protocols or specifications that @@ -297,17 +302,17 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> _ -> %% Do not store long language tag to %% avoid possible DoS/flood attacks - "" + <<"">> end, - change_shaper(StateData, jlib:make_jid("", Server, "")), - case xml:get_attr_s("version", Attrs) of - "1.0" -> - send_header(StateData, Server, "1.0", DefaultLang), + change_shaper(StateData, jlib:make_jid(<<"">>, Server, <<"">>)), + case xml:get_attr_s(<<"version">>, Attrs) of + <<"1.0">> -> + send_header(StateData, Server, <<"1.0">>, DefaultLang), case StateData#state.authenticated of false -> SASLState = cyrsasl:server_new( - "jabber", Server, "", [], + <<"jabber">>, Server, <<"">>, [], fun(U) -> ejabberd_auth:get_password_with_authmodule( U, Server) @@ -320,11 +325,12 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> ejabberd_auth:check_password_with_authmodule( U, Server, P, D, DG) end), - Mechs = lists:map( - fun(S) -> - {xmlelement, "mechanism", [], - [{xmlcdata, S}]} - end, cyrsasl:listmech(Server)), + Mechs = lists:map(fun (S) -> + #xmlel{name = <<"mechanism">>, + attrs = [], + children = [{xmlcdata, S}]} + end, + cyrsasl:listmech(Server)), SockMod = (StateData#state.sockmod):get_sockmod( StateData#state.socket), @@ -334,10 +340,11 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> ((SockMod == gen_tcp) orelse (SockMod == tls)) of true -> - [{xmlelement, "compression", - [{"xmlns", ?NS_FEATURE_COMPRESS}], - [{xmlelement, "method", - [], [{xmlcdata, "zlib"}]}]}]; + [#xmlel{name = <<"compression">>, + attrs = [{<<"xmlns">>, ?NS_FEATURE_COMPRESS}], + children = [#xmlel{name = <<"method">>, + attrs = [], + children = [{xmlcdata, <<"zlib">>}]}]}]; _ -> [] end, @@ -351,27 +358,30 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> true -> case TLSRequired of true -> - [{xmlelement, "starttls", - [{"xmlns", ?NS_TLS}], - [{xmlelement, "required", - [], []}]}]; + [#xmlel{name = <<"starttls">>, + attrs = [{<<"xmlns">>, ?NS_TLS}], + children = [#xmlel{name = <<"required">>, + attrs = [], + children = []}]}]; _ -> - [{xmlelement, "starttls", - [{"xmlns", ?NS_TLS}], []}] + [#xmlel{name = <<"starttls">>, + attrs = [{<<"xmlns">>, ?NS_TLS}], + children = []}] end; false -> [] end, send_element(StateData, - {xmlelement, "stream:features", [], - TLSFeature ++ CompressFeature ++ - [{xmlelement, "mechanisms", - [{"xmlns", ?NS_SASL}], - Mechs}] ++ - ejabberd_hooks:run_fold( - c2s_stream_features, - Server, - [], [Server])}), + #xmlel{name = <<"stream:features">>, + attrs = [], + children = + TLSFeature ++ CompressFeature ++ + [#xmlel{name = <<"mechanisms">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = Mechs}] + ++ + ejabberd_hooks:run_fold(c2s_stream_features, + Server, [], [Server])}), fsm_next_state(wait_for_feature_request, StateData#state{ server = Server, @@ -379,556 +389,578 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> lang = Lang}); _ -> case StateData#state.resource of - "" -> - RosterVersioningFeature = - ejabberd_hooks:run_fold( - roster_get_versioning_feature, - Server, [], [Server]), - StreamFeatures = - [{xmlelement, "bind", - [{"xmlns", ?NS_BIND}], []}, - {xmlelement, "session", - [{"xmlns", ?NS_SESSION}], []}] - ++ RosterVersioningFeature - ++ ejabberd_hooks:run_fold( - c2s_stream_features, - Server, - [], [Server]), - send_element( - StateData, - {xmlelement, "stream:features", [], - StreamFeatures}), - fsm_next_state(wait_for_bind, - StateData#state{ - server = Server, - lang = Lang}); - _ -> - send_element( - StateData, - {xmlelement, "stream:features", [], []}), - fsm_next_state(wait_for_session, - StateData#state{ - server = Server, - lang = Lang}) + <<"">> -> + RosterVersioningFeature = + ejabberd_hooks:run_fold(roster_get_versioning_feature, + Server, [], + [Server]), + StreamFeatures = [#xmlel{name = <<"bind">>, + attrs = [{<<"xmlns">>, ?NS_BIND}], + children = []}, + #xmlel{name = <<"session">>, + attrs = [{<<"xmlns">>, ?NS_SESSION}], + children = []}] + ++ + RosterVersioningFeature ++ + ejabberd_hooks:run_fold(c2s_stream_features, + Server, [], [Server]), + send_element(StateData, + #xmlel{name = <<"stream:features">>, + attrs = [], + children = StreamFeatures}), + fsm_next_state(wait_for_bind, + StateData#state{server = Server, lang = Lang}); + _ -> + send_element(StateData, + #xmlel{name = <<"stream:features">>, + attrs = [], + children = []}), + fsm_next_state(wait_for_session, + StateData#state{server = Server, lang = Lang}) end end; - _ -> - send_header(StateData, Server, "", DefaultLang), - if - (not StateData#state.tls_enabled) and - StateData#state.tls_required -> - send_element( - StateData, - ?POLICY_VIOLATION_ERR( - Lang, - "Use of STARTTLS required")), - send_trailer(StateData), - {stop, normal, StateData}; - true -> - fsm_next_state(wait_for_auth, - StateData#state{ - server = Server, - lang = Lang}) - end - end; _ -> - send_header(StateData, ?MYNAME, "", DefaultLang), - send_element(StateData, ?HOST_UNKNOWN_ERR), - send_trailer(StateData), - {stop, normal, StateData} + send_header(StateData, Server, <<"">>, DefaultLang), + if not StateData#state.tls_enabled and + StateData#state.tls_required -> + send_element(StateData, + ?POLICY_VIOLATION_ERR(Lang, + <<"Use of STARTTLS required">>)), + send_trailer(StateData), + {stop, normal, StateData}; + true -> + fsm_next_state(wait_for_auth, + StateData#state{server = Server, + lang = Lang}) + end end; _ -> - send_header(StateData, ?MYNAME, "", DefaultLang), - send_element(StateData, ?INVALID_NS_ERR), + send_header(StateData, ?MYNAME, <<"">>, DefaultLang), + send_element(StateData, ?HOST_UNKNOWN_ERR), send_trailer(StateData), {stop, normal, StateData} + end; + _ -> + send_header(StateData, ?MYNAME, <<"">>, DefaultLang), + send_element(StateData, ?INVALID_NS_ERR), + send_trailer(StateData), + {stop, normal, StateData} end; - wait_for_stream(timeout, StateData) -> {stop, normal, StateData}; - wait_for_stream({xmlstreamelement, _}, StateData) -> send_element(StateData, ?INVALID_XML_ERR), send_trailer(StateData), {stop, normal, StateData}; - wait_for_stream({xmlstreamend, _}, StateData) -> send_element(StateData, ?INVALID_XML_ERR), send_trailer(StateData), {stop, normal, StateData}; - wait_for_stream({xmlstreamerror, _}, StateData) -> - send_header(StateData, ?MYNAME, "1.0", ""), + send_header(StateData, ?MYNAME, <<"1.0">>, <<"">>), send_element(StateData, ?INVALID_XML_ERR), send_trailer(StateData), {stop, normal, StateData}; - wait_for_stream(closed, StateData) -> {stop, normal, StateData}. - wait_for_auth({xmlstreamelement, El}, StateData) -> case is_auth_packet(El) of - {auth, _ID, get, {U, _, _, _}} -> - {xmlelement, Name, Attrs, _Els} = jlib:make_result_iq_reply(El), - case U of - "" -> - UCdata = []; - _ -> - UCdata = [{xmlcdata, U}] - end, - Res = case ejabberd_auth:plain_password_required( - StateData#state.server) of - false -> - {xmlelement, Name, Attrs, - [{xmlelement, "query", [{"xmlns", ?NS_AUTH}], - [{xmlelement, "username", [], UCdata}, - {xmlelement, "password", [], []}, - {xmlelement, "digest", [], []}, - {xmlelement, "resource", [], []} - ]}]}; - true -> - {xmlelement, Name, Attrs, - [{xmlelement, "query", [{"xmlns", ?NS_AUTH}], - [{xmlelement, "username", [], UCdata}, - {xmlelement, "password", [], []}, - {xmlelement, "resource", [], []} - ]}]} - end, - send_element(StateData, Res), - fsm_next_state(wait_for_auth, StateData); - {auth, _ID, set, {_U, _P, _D, ""}} -> - Err = jlib:make_error_reply( - El, - ?ERR_AUTH_NO_RESOURCE_PROVIDED(StateData#state.lang)), - send_element(StateData, Err), - fsm_next_state(wait_for_auth, StateData); - {auth, _ID, set, {U, P, D, R}} -> - JID = jlib:make_jid(U, StateData#state.server, R), - case (JID /= error) andalso - (acl:match_rule(StateData#state.server, - StateData#state.access, JID) == allow) of - true -> - DGen = fun(PW) -> - sha:sha(StateData#state.streamid ++ PW) end, - case ejabberd_auth:check_password_with_authmodule( - U, StateData#state.server, P, D, DGen) of - {true, AuthModule} -> - ?INFO_MSG( - "(~w) Accepted legacy authentication for ~s by ~p", - [StateData#state.socket, - jlib:jid_to_string(JID), AuthModule]), - SID = {now(), self()}, - Conn = get_conn_type(StateData), - Info = [{ip, StateData#state.ip}, {conn, Conn}, + {auth, _ID, get, {U, _, _, _}} -> + #xmlel{name = Name, attrs = Attrs} = + jlib:make_result_iq_reply(El), + case U of + <<"">> -> UCdata = []; + _ -> UCdata = [{xmlcdata, U}] + end, + Res = case + ejabberd_auth:plain_password_required(StateData#state.server) + of + false -> + #xmlel{name = Name, attrs = Attrs, + children = + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, ?NS_AUTH}], + children = + [#xmlel{name = <<"username">>, + attrs = [], + children = UCdata}, + #xmlel{name = <<"password">>, + attrs = [], children = []}, + #xmlel{name = <<"digest">>, + attrs = [], children = []}, + #xmlel{name = <<"resource">>, + attrs = [], + children = []}]}]}; + true -> + #xmlel{name = Name, attrs = Attrs, + children = + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, ?NS_AUTH}], + children = + [#xmlel{name = <<"username">>, + attrs = [], + children = UCdata}, + #xmlel{name = <<"password">>, + attrs = [], children = []}, + #xmlel{name = <<"resource">>, + attrs = [], + children = []}]}]} + end, + send_element(StateData, Res), + fsm_next_state(wait_for_auth, StateData); + {auth, _ID, set, {_U, _P, _D, <<"">>}} -> + Err = jlib:make_error_reply(El, + ?ERR_AUTH_NO_RESOURCE_PROVIDED((StateData#state.lang))), + send_element(StateData, Err), + fsm_next_state(wait_for_auth, StateData); + {auth, _ID, set, {U, P, D, R}} -> + JID = jlib:make_jid(U, StateData#state.server, R), + case JID /= error andalso + acl:match_rule(StateData#state.server, + StateData#state.access, JID) + == allow + of + true -> + DGen = fun (PW) -> + sha:sha(<<(StateData#state.streamid)/binary, PW/binary>>) + end, + case ejabberd_auth:check_password_with_authmodule(U, + StateData#state.server, + P, D, DGen) + of + {true, AuthModule} -> + ?INFO_MSG("(~w) Accepted legacy authentication for ~s by ~p", + [StateData#state.socket, + jlib:jid_to_string(JID), AuthModule]), + SID = {now(), self()}, + Conn = (StateData#state.sockmod):get_conn_type( + StateData#state.socket), + Info = [{ip, StateData#state.ip}, {conn, Conn}, {auth_module, AuthModule}], - Res1 = jlib:make_result_iq_reply(El), - Res = setelement(4, Res1, []), - send_element(StateData, Res), - ejabberd_sm:open_session( - SID, U, StateData#state.server, R, Info), - change_shaper(StateData, JID), - {Fs, Ts} = ejabberd_hooks:run_fold( - roster_get_subscription_lists, - StateData#state.server, - {[], []}, - [U, StateData#state.server]), - LJID = jlib:jid_tolower( - jlib:jid_remove_resource(JID)), - Fs1 = [LJID | Fs], - Ts1 = [LJID | Ts], - PrivList = - ejabberd_hooks:run_fold( - privacy_get_user_list, StateData#state.server, - #userlist{}, - [U, StateData#state.server]), - NewStateData = - StateData#state{ - user = U, - resource = R, - jid = JID, - sid = SID, - conn = Conn, - auth_module = AuthModule, - pres_f = ?SETS:from_list(Fs1), - pres_t = ?SETS:from_list(Ts1), - privacy_list = PrivList}, - fsm_next_state_pack(session_established, - NewStateData); - _ -> - IP = peerip(StateData#state.sockmod, StateData#state.socket), - ?INFO_MSG( - "(~w) Failed legacy authentication for ~s from IP ~s (~w)", - [StateData#state.socket, - jlib:jid_to_string(JID), jlib:ip_to_list(IP), IP]), - Err = jlib:make_error_reply( - El, ?ERR_NOT_AUTHORIZED), - send_element(StateData, Err), - fsm_next_state(wait_for_auth, StateData) - end; - _ -> - if - JID == error -> - ?INFO_MSG( - "(~w) Forbidden legacy authentication for " - "username '~s' with resource '~s'", - [StateData#state.socket, U, R]), - Err = jlib:make_error_reply(El, ?ERR_JID_MALFORMED), - send_element(StateData, Err), - fsm_next_state(wait_for_auth, StateData); - true -> - ?INFO_MSG( - "(~w) Forbidden legacy authentication for ~s", - [StateData#state.socket, - jlib:jid_to_string(JID)]), - Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED), - send_element(StateData, Err), - fsm_next_state(wait_for_auth, StateData) - end - end; - _ -> - process_unauthenticated_stanza(StateData, El), - fsm_next_state(wait_for_auth, StateData) + Res1 = jlib:make_result_iq_reply(El), + Res = Res1#xmlel{children = []}, + send_element(StateData, Res), + ejabberd_sm:open_session(SID, U, StateData#state.server, R, Info), + change_shaper(StateData, JID), + {Fs, Ts} = + ejabberd_hooks:run_fold(roster_get_subscription_lists, + StateData#state.server, + {[], []}, + [U, + StateData#state.server]), + LJID = + jlib:jid_tolower(jlib:jid_remove_resource(JID)), + Fs1 = [LJID | Fs], + Ts1 = [LJID | Ts], + PrivList = ejabberd_hooks:run_fold(privacy_get_user_list, + StateData#state.server, + #userlist{}, + [U, StateData#state.server]), + NewStateData = StateData#state{user = U, + resource = R, + jid = JID, sid = SID, + conn = Conn, + auth_module = AuthModule, + pres_f = (?SETS):from_list(Fs1), + pres_t = (?SETS):from_list(Ts1), + privacy_list = PrivList}, + fsm_next_state(session_established, NewStateData); + _ -> + IP = peerip(StateData#state.sockmod, + StateData#state.socket), + ?INFO_MSG("(~w) Failed legacy authentication for " + "~s from IP ~s", + [StateData#state.socket, + jlib:jid_to_string(JID), jlib:ip_to_list(IP)]), + Err = jlib:make_error_reply(El, ?ERR_NOT_AUTHORIZED), + send_element(StateData, Err), + fsm_next_state(wait_for_auth, StateData) + end; + _ -> + if JID == error -> + ?INFO_MSG("(~w) Forbidden legacy authentication " + "for username '~s' with resource '~s'", + [StateData#state.socket, U, R]), + Err = jlib:make_error_reply(El, ?ERR_JID_MALFORMED), + send_element(StateData, Err), + fsm_next_state(wait_for_auth, StateData); + true -> + ?INFO_MSG("(~w) Forbidden legacy authentication " + "for ~s", + [StateData#state.socket, + jlib:jid_to_string(JID)]), + Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED), + send_element(StateData, Err), + fsm_next_state(wait_for_auth, StateData) + end + end; + _ -> + process_unauthenticated_stanza(StateData, El), + fsm_next_state(wait_for_auth, StateData) end; - wait_for_auth(timeout, StateData) -> {stop, normal, StateData}; - wait_for_auth({xmlstreamend, _Name}, StateData) -> - send_trailer(StateData), - {stop, normal, StateData}; - + send_trailer(StateData), {stop, normal, StateData}; wait_for_auth({xmlstreamerror, _}, StateData) -> send_element(StateData, ?INVALID_XML_ERR), send_trailer(StateData), {stop, normal, StateData}; - wait_for_auth(closed, StateData) -> {stop, normal, StateData}. - -wait_for_feature_request({xmlstreamelement, El}, StateData) -> - {xmlelement, Name, Attrs, Els} = El, +wait_for_feature_request({xmlstreamelement, El}, + StateData) -> + #xmlel{name = Name, attrs = Attrs, children = Els} = El, Zlib = StateData#state.zlib, TLS = StateData#state.tls, TLSEnabled = StateData#state.tls_enabled, TLSRequired = StateData#state.tls_required, - SockMod = (StateData#state.sockmod):get_sockmod(StateData#state.socket), - case {xml:get_attr_s("xmlns", Attrs), Name} of - {?NS_SASL, "auth"} when not ((SockMod == gen_tcp) and TLSRequired) -> - Mech = xml:get_attr_s("mechanism", Attrs), - ClientIn = jlib:decode_base64(xml:get_cdata(Els)), - case cyrsasl:server_start(StateData#state.sasl_state, - Mech, - ClientIn) of - {ok, Props} -> - (StateData#state.sockmod):reset_stream( - StateData#state.socket), - send_element(StateData, - {xmlelement, "success", - [{"xmlns", ?NS_SASL}], []}), - U = xml:get_attr_s(username, Props), - AuthModule = xml:get_attr_s(auth_module, Props), - ?INFO_MSG("(~w) Accepted authentication for ~s by ~p", - [StateData#state.socket, U, AuthModule]), - fsm_next_state(wait_for_stream, - StateData#state{ - streamid = new_id(), - authenticated = true, - auth_module = AuthModule, - user = U }); - {continue, ServerOut, NewSASLState} -> - send_element(StateData, - {xmlelement, "challenge", - [{"xmlns", ?NS_SASL}], - [{xmlcdata, - jlib:encode_base64(ServerOut)}]}), - fsm_next_state(wait_for_sasl_response, - StateData#state{ - sasl_state = NewSASLState}); - {error, Error, Username} -> - IP = peerip(StateData#state.sockmod, StateData#state.socket), - ?INFO_MSG( - "(~w) Failed authentication for ~s@~s from IP ~s (~w)", + SockMod = + (StateData#state.sockmod):get_sockmod(StateData#state.socket), + case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of + {?NS_SASL, <<"auth">>} + when not ((SockMod == gen_tcp) and TLSRequired) -> + Mech = xml:get_attr_s(<<"mechanism">>, Attrs), + ClientIn = jlib:decode_base64(xml:get_cdata(Els)), + case cyrsasl:server_start(StateData#state.sasl_state, + Mech, ClientIn) + of + {ok, Props} -> + (StateData#state.sockmod):reset_stream(StateData#state.socket), + %U = xml:get_attr_s(username, Props), + U = proplists:get_value(username, Props, <<>>), + %AuthModule = xml:get_attr_s(auth_module, Props), + AuthModule = proplists:get_value(auth_module, Props, undefined), + ?INFO_MSG("(~w) Accepted authentication for ~s " + "by ~p", + [StateData#state.socket, U, AuthModule]), + send_element(StateData, + #xmlel{name = <<"success">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = []}), + fsm_next_state(wait_for_stream, + StateData#state{streamid = new_id(), + authenticated = true, + auth_module = AuthModule, + user = U}); + {continue, ServerOut, NewSASLState} -> + send_element(StateData, + #xmlel{name = <<"challenge">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = + [{xmlcdata, + jlib:encode_base64(ServerOut)}]}), + fsm_next_state(wait_for_sasl_response, + StateData#state{sasl_state = NewSASLState}); + {error, Error, Username} -> + IP = peerip(StateData#state.sockmod, StateData#state.socket), + ?INFO_MSG("(~w) Failed authentication for ~s@~s from IP ~s", [StateData#state.socket, - Username, StateData#state.server, jlib:ip_to_list(IP), IP]), - send_element(StateData, - {xmlelement, "failure", - [{"xmlns", ?NS_SASL}], - [{xmlelement, Error, [], []}]}), - {next_state, wait_for_feature_request, StateData, - ?C2S_OPEN_TIMEOUT}; - {error, Error} -> - send_element(StateData, - {xmlelement, "failure", - [{"xmlns", ?NS_SASL}], - [{xmlelement, Error, [], []}]}), - fsm_next_state(wait_for_feature_request, StateData) - end; - {?NS_TLS, "starttls"} when TLS == true, - TLSEnabled == false, - SockMod == gen_tcp -> - TLSOpts = case ejabberd_config:get_local_option( - {domain_certfile, StateData#state.server}) of - undefined -> - StateData#state.tls_options; - CertFile -> - [{certfile, CertFile} | - lists:keydelete( - certfile, 1, StateData#state.tls_options)] - end, - Socket = StateData#state.socket, - TLSSocket = (StateData#state.sockmod):starttls( - Socket, TLSOpts, - xml:element_to_binary( - {xmlelement, "proceed", [{"xmlns", ?NS_TLS}], []})), - fsm_next_state(wait_for_stream, - StateData#state{socket = TLSSocket, - streamid = new_id(), - tls_enabled = true - }); - {?NS_COMPRESS, "compress"} when Zlib == true, - ((SockMod == gen_tcp) or - (SockMod == tls)) -> - case xml:get_subtag(El, "method") of - false -> - send_element(StateData, - {xmlelement, "failure", - [{"xmlns", ?NS_COMPRESS}], - [{xmlelement, "setup-failed", [], []}]}), - fsm_next_state(wait_for_feature_request, StateData); - Method -> - case xml:get_tag_cdata(Method) of - "zlib" -> - Socket = StateData#state.socket, - ZlibSocket = (StateData#state.sockmod):compress( - Socket, - xml:element_to_binary( - {xmlelement, "compressed", - [{"xmlns", ?NS_COMPRESS}], []})), - fsm_next_state(wait_for_stream, - StateData#state{socket = ZlibSocket, - streamid = new_id() - }); - _ -> - send_element(StateData, - {xmlelement, "failure", - [{"xmlns", ?NS_COMPRESS}], - [{xmlelement, "unsupported-method", - [], []}]}), - fsm_next_state(wait_for_feature_request, - StateData) - end - end; - _ -> - if - (SockMod == gen_tcp) and TLSRequired -> - Lang = StateData#state.lang, - send_element(StateData, ?POLICY_VIOLATION_ERR( - Lang, - "Use of STARTTLS required")), - send_trailer(StateData), - {stop, normal, StateData}; - true -> - process_unauthenticated_stanza(StateData, El), - fsm_next_state(wait_for_feature_request, StateData) - end + Username, StateData#state.server, jlib:ip_to_list(IP)]), + send_element(StateData, + #xmlel{name = <<"failure">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = + [#xmlel{name = Error, attrs = [], + children = []}]}), + {next_state, wait_for_feature_request, StateData, + ?C2S_OPEN_TIMEOUT}; + {error, Error} -> + send_element(StateData, + #xmlel{name = <<"failure">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = + [#xmlel{name = Error, attrs = [], + children = []}]}), + fsm_next_state(wait_for_feature_request, StateData) + end; + {?NS_TLS, <<"starttls">>} + when TLS == true, TLSEnabled == false, + SockMod == gen_tcp -> + TLSOpts = case + ejabberd_config:get_local_option( + {domain_certfile, StateData#state.server}, + fun iolist_to_binary/1) + of + undefined -> StateData#state.tls_options; + CertFile -> + [{certfile, CertFile} | lists:keydelete(certfile, 1, + StateData#state.tls_options)] + end, + Socket = StateData#state.socket, + TLSSocket = (StateData#state.sockmod):starttls(Socket, + TLSOpts, + xml:element_to_binary(#xmlel{name + = + <<"proceed">>, + attrs + = + [{<<"xmlns">>, + ?NS_TLS}], + children + = + []})), + fsm_next_state(wait_for_stream, + StateData#state{socket = TLSSocket, + streamid = new_id(), + tls_enabled = true}); + {?NS_COMPRESS, <<"compress">>} + when Zlib == true, + (SockMod == gen_tcp) or (SockMod == tls) -> + case xml:get_subtag(El, <<"method">>) of + false -> + send_element(StateData, + #xmlel{name = <<"failure">>, + attrs = [{<<"xmlns">>, ?NS_COMPRESS}], + children = + [#xmlel{name = <<"setup-failed">>, + attrs = [], children = []}]}), + fsm_next_state(wait_for_feature_request, StateData); + Method -> + case xml:get_tag_cdata(Method) of + <<"zlib">> -> + Socket = StateData#state.socket, + ZlibSocket = (StateData#state.sockmod):compress(Socket, + xml:element_to_binary(#xmlel{name + = + <<"compressed">>, + attrs + = + [{<<"xmlns">>, + ?NS_COMPRESS}], + children + = + []})), + fsm_next_state(wait_for_stream, + StateData#state{socket = ZlibSocket, + streamid = new_id()}); + _ -> + send_element(StateData, + #xmlel{name = <<"failure">>, + attrs = [{<<"xmlns">>, ?NS_COMPRESS}], + children = + [#xmlel{name = + <<"unsupported-method">>, + attrs = [], + children = []}]}), + fsm_next_state(wait_for_feature_request, StateData) + end + end; + _ -> + if (SockMod == gen_tcp) and TLSRequired -> + Lang = StateData#state.lang, + send_element(StateData, + ?POLICY_VIOLATION_ERR(Lang, + <<"Use of STARTTLS required">>)), + send_trailer(StateData), + {stop, normal, StateData}; + true -> + process_unauthenticated_stanza(StateData, El), + fsm_next_state(wait_for_feature_request, StateData) + end end; - wait_for_feature_request(timeout, StateData) -> {stop, normal, StateData}; - -wait_for_feature_request({xmlstreamend, _Name}, StateData) -> - send_trailer(StateData), - {stop, normal, StateData}; - -wait_for_feature_request({xmlstreamerror, _}, StateData) -> +wait_for_feature_request({xmlstreamend, _Name}, + StateData) -> + send_trailer(StateData), {stop, normal, StateData}; +wait_for_feature_request({xmlstreamerror, _}, + StateData) -> send_element(StateData, ?INVALID_XML_ERR), send_trailer(StateData), {stop, normal, StateData}; - wait_for_feature_request(closed, StateData) -> {stop, normal, StateData}. - -wait_for_sasl_response({xmlstreamelement, El}, StateData) -> - {xmlelement, Name, Attrs, Els} = El, - case {xml:get_attr_s("xmlns", Attrs), Name} of - {?NS_SASL, "response"} -> - ClientIn = jlib:decode_base64(xml:get_cdata(Els)), - case cyrsasl:server_step(StateData#state.sasl_state, - ClientIn) of - {ok, Props} -> - (StateData#state.sockmod):reset_stream( - StateData#state.socket), - send_element(StateData, - {xmlelement, "success", - [{"xmlns", ?NS_SASL}], []}), - U = xml:get_attr_s(username, Props), - AuthModule = xml:get_attr_s(auth_module, Props), - ?INFO_MSG("(~w) Accepted authentication for ~s by ~p", - [StateData#state.socket, U, AuthModule]), - fsm_next_state(wait_for_stream, - StateData#state{ - streamid = new_id(), - authenticated = true, - auth_module = AuthModule, - user = U}); - {ok, Props, ServerOut} -> - (StateData#state.sockmod):reset_stream( - StateData#state.socket), - send_element(StateData, - {xmlelement, "success", - [{"xmlns", ?NS_SASL}], - [{xmlcdata, - jlib:encode_base64(ServerOut)}]}), - U = xml:get_attr_s(username, Props), - AuthModule = xml:get_attr_s(auth_module, Props), - ?INFO_MSG("(~w) Accepted authentication for ~s by ~p", - [StateData#state.socket, U, AuthModule]), - fsm_next_state(wait_for_stream, - StateData#state{ - streamid = new_id(), - authenticated = true, - auth_module = AuthModule, - user = U}); - {continue, ServerOut, NewSASLState} -> - send_element(StateData, - {xmlelement, "challenge", - [{"xmlns", ?NS_SASL}], - [{xmlcdata, - jlib:encode_base64(ServerOut)}]}), - fsm_next_state(wait_for_sasl_response, - StateData#state{sasl_state = NewSASLState}); - {error, Error, Username} -> - IP = peerip(StateData#state.sockmod, StateData#state.socket), - ?INFO_MSG( - "(~w) Failed authentication for ~s@~s from IP ~s (~w)", +wait_for_sasl_response({xmlstreamelement, El}, + StateData) -> + #xmlel{name = Name, attrs = Attrs, children = Els} = El, + case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of + {?NS_SASL, <<"response">>} -> + ClientIn = jlib:decode_base64(xml:get_cdata(Els)), + case cyrsasl:server_step(StateData#state.sasl_state, + ClientIn) + of + {ok, Props} -> + catch + (StateData#state.sockmod):reset_stream(StateData#state.socket), +% U = xml:get_attr_s(username, Props), + U = proplists:get_value(username, Props, <<>>), +% AuthModule = xml:get_attr_s(auth_module, Props), + AuthModule = proplists:get_value(auth_module, Props, <<>>), + ?INFO_MSG("(~w) Accepted authentication for ~s " + "by ~p", + [StateData#state.socket, U, AuthModule]), + send_element(StateData, + #xmlel{name = <<"success">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = []}), + fsm_next_state(wait_for_stream, + StateData#state{streamid = new_id(), + authenticated = true, + auth_module = AuthModule, + user = U}); + {ok, Props, ServerOut} -> + (StateData#state.sockmod):reset_stream(StateData#state.socket), +% U = xml:get_attr_s(username, Props), + U = proplists:get_value(username, Props, <<>>), +% AuthModule = xml:get_attr_s(auth_module, Props), + AuthModule = proplists:get_value(auth_module, Props, undefined), + ?INFO_MSG("(~w) Accepted authentication for ~s " + "by ~p", + [StateData#state.socket, U, AuthModule]), + send_element(StateData, + #xmlel{name = <<"success">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = + [{xmlcdata, + jlib:encode_base64(ServerOut)}]}), + fsm_next_state(wait_for_stream, + StateData#state{streamid = new_id(), + authenticated = true, + auth_module = AuthModule, + user = U}); + {continue, ServerOut, NewSASLState} -> + send_element(StateData, + #xmlel{name = <<"challenge">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = + [{xmlcdata, + jlib:encode_base64(ServerOut)}]}), + fsm_next_state(wait_for_sasl_response, + StateData#state{sasl_state = NewSASLState}); + {error, Error, Username} -> + IP = peerip(StateData#state.sockmod, StateData#state.socket), + ?INFO_MSG("(~w) Failed authentication for ~s@~s from IP ~s", [StateData#state.socket, - Username, StateData#state.server, jlib:ip_to_list(IP), IP]), - send_element(StateData, - {xmlelement, "failure", - [{"xmlns", ?NS_SASL}], - [{xmlelement, Error, [], []}]}), - fsm_next_state(wait_for_feature_request, StateData); - {error, Error} -> - send_element(StateData, - {xmlelement, "failure", - [{"xmlns", ?NS_SASL}], - [{xmlelement, Error, [], []}]}), - fsm_next_state(wait_for_feature_request, StateData) - end; - _ -> - process_unauthenticated_stanza(StateData, El), - fsm_next_state(wait_for_feature_request, StateData) + Username, StateData#state.server, jlib:ip_to_list(IP)]), + send_element(StateData, + #xmlel{name = <<"failure">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = + [#xmlel{name = Error, attrs = [], + children = []}]}), + fsm_next_state(wait_for_feature_request, StateData); + {error, Error} -> + send_element(StateData, + #xmlel{name = <<"failure">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = + [#xmlel{name = Error, attrs = [], + children = []}]}), + fsm_next_state(wait_for_feature_request, StateData) + end; + _ -> + process_unauthenticated_stanza(StateData, El), + fsm_next_state(wait_for_feature_request, StateData) end; - wait_for_sasl_response(timeout, StateData) -> {stop, normal, StateData}; - -wait_for_sasl_response({xmlstreamend, _Name}, StateData) -> - send_trailer(StateData), - {stop, normal, StateData}; - -wait_for_sasl_response({xmlstreamerror, _}, StateData) -> +wait_for_sasl_response({xmlstreamend, _Name}, + StateData) -> + send_trailer(StateData), {stop, normal, StateData}; +wait_for_sasl_response({xmlstreamerror, _}, + StateData) -> send_element(StateData, ?INVALID_XML_ERR), send_trailer(StateData), {stop, normal, StateData}; - wait_for_sasl_response(closed, StateData) -> {stop, normal, StateData}. - resource_conflict_action(U, S, R) -> - OptionRaw = case ejabberd_sm:is_existing_resource(U, S, R) of - true -> - ejabberd_config:get_local_option({resource_conflict,S}); - false -> - acceptnew + OptionRaw = case ejabberd_sm:is_existing_resource(U, S, + R) + of + true -> + ejabberd_config:get_local_option( + {resource_conflict, S}, + fun(setresource) -> setresource; + (closeold) -> closeold; + (closenew) -> closenew; + (acceptnew) -> acceptnew + end); + false -> + acceptnew end, Option = case OptionRaw of - setresource -> setresource; - closeold -> acceptnew; %% ejabberd_sm will close old session - closenew -> closenew; - acceptnew -> acceptnew; - _ -> acceptnew %% default ejabberd behavior + setresource -> setresource; + closeold -> + acceptnew; %% ejabberd_sm will close old session + closenew -> closenew; + acceptnew -> acceptnew; + _ -> acceptnew %% default ejabberd behavior end, case Option of - acceptnew -> - {accept_resource, R}; - closenew -> - closenew; - setresource -> - Rnew = lists:concat([randoms:get_string() | tuple_to_list(now())]), - {accept_resource, Rnew} + acceptnew -> {accept_resource, R}; + closenew -> closenew; + setresource -> + Rnew = iolist_to_binary([randoms:get_string() + | tuple_to_list(now())]), + {accept_resource, Rnew} end. wait_for_bind({xmlstreamelement, El}, StateData) -> case jlib:iq_query_info(El) of - #iq{type = set, xmlns = ?NS_BIND, sub_el = SubEl} = IQ -> - U = StateData#state.user, - R1 = xml:get_path_s(SubEl, [{elem, "resource"}, cdata]), - R = case jlib:resourceprep(R1) of - error -> error; - "" -> - lists:concat( - [randoms:get_string() | tuple_to_list(now())]); - Resource -> Resource - end, - case R of - error -> - Err = jlib:make_error_reply(El, ?ERR_BAD_REQUEST), - send_element(StateData, Err), - fsm_next_state(wait_for_bind, StateData); - _ -> - %%Server = StateData#state.server, - %%RosterVersioningFeature = - %% ejabberd_hooks:run_fold( - %% roster_get_versioning_feature, Server, [], [Server]), - %%StreamFeatures = [{xmlelement, "session", - %% [{"xmlns", ?NS_SESSION}], []} | - %% RosterVersioningFeature], - %%send_element(StateData, {xmlelement, "stream:features", - %% [], StreamFeatures}), - case resource_conflict_action(U, StateData#state.server, R) of - closenew -> - Err = jlib:make_error_reply(El, ?STANZA_ERROR("409", "modify", "conflict")), - send_element(StateData, Err), - fsm_next_state(wait_for_bind, StateData); - {accept_resource, R2} -> - JID = jlib:make_jid(U, StateData#state.server, R2), - Res = IQ#iq{type = result, - sub_el = [{xmlelement, "bind", - [{"xmlns", ?NS_BIND}], - [{xmlelement, "jid", [], - [{xmlcdata, - jlib:jid_to_string(JID)}]}]}]}, - send_element(StateData, jlib:iq_to_xml(Res)), - fsm_next_state(wait_for_session, - StateData#state{resource = R2, jid = JID}) - end - end; - _ -> - fsm_next_state(wait_for_bind, StateData) + #iq{type = set, xmlns = ?NS_BIND, sub_el = SubEl} = + IQ -> + U = StateData#state.user, + R1 = xml:get_path_s(SubEl, + [{elem, <<"resource">>}, cdata]), + R = case jlib:resourceprep(R1) of + error -> error; + <<"">> -> + iolist_to_binary([randoms:get_string() + | tuple_to_list(now())]); + Resource -> Resource + end, + case R of + error -> + Err = jlib:make_error_reply(El, ?ERR_BAD_REQUEST), + send_element(StateData, Err), + fsm_next_state(wait_for_bind, StateData); + _ -> + case resource_conflict_action(U, StateData#state.server, + R) + of + closenew -> + Err = jlib:make_error_reply(El, + ?STANZA_ERROR(<<"409">>, + <<"modify">>, + <<"conflict">>)), + send_element(StateData, Err), + fsm_next_state(wait_for_bind, StateData); + {accept_resource, R2} -> + JID = jlib:make_jid(U, StateData#state.server, R2), + Res = IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"bind">>, + attrs = [{<<"xmlns">>, ?NS_BIND}], + children = + [#xmlel{name = <<"jid">>, + attrs = [], + children = + [{xmlcdata, + jlib:jid_to_string(JID)}]}]}]}, + send_element(StateData, jlib:iq_to_xml(Res)), + fsm_next_state(wait_for_session, + StateData#state{resource = R2, jid = JID}) + end + end; + _ -> fsm_next_state(wait_for_bind, StateData) end; - wait_for_bind(timeout, StateData) -> {stop, normal, StateData}; - wait_for_bind({xmlstreamend, _Name}, StateData) -> - send_trailer(StateData), - {stop, normal, StateData}; - + send_trailer(StateData), {stop, normal, StateData}; wait_for_bind({xmlstreamerror, _}, StateData) -> send_element(StateData, ?INVALID_XML_ERR), send_trailer(StateData), {stop, normal, StateData}; - wait_for_bind(closed, StateData) -> {stop, normal, StateData}. - - wait_for_session({xmlstreamelement, El}, StateData) -> case jlib:iq_query_info(El) of #iq{type = set, xmlns = ?NS_SESSION} -> @@ -988,23 +1020,18 @@ wait_for_session({xmlstreamelement, El}, StateData) -> wait_for_session(timeout, StateData) -> {stop, normal, StateData}; - wait_for_session({xmlstreamend, _Name}, StateData) -> - send_trailer(StateData), - {stop, normal, StateData}; - + send_trailer(StateData), {stop, normal, StateData}; wait_for_session({xmlstreamerror, _}, StateData) -> send_element(StateData, ?INVALID_XML_ERR), send_trailer(StateData), {stop, normal, StateData}; - wait_for_session(closed, StateData) -> {stop, normal, StateData}. - -session_established({xmlstreamelement, El}, StateData) -> +session_established({xmlstreamelement, El}, + StateData) -> FromJID = StateData#state.jid, - % Check 'from' attribute in stanza RFC 3920 Section 9.1.2 case check_from(El, FromJID) of 'invalid-from' -> send_element(StateData, ?INVALID_FROM), @@ -1013,124 +1040,109 @@ session_established({xmlstreamelement, El}, StateData) -> _NewEl -> session_established2(El, StateData) end; - %% We hibernate the process to reduce memory consumption after a %% configurable activity timeout session_established(timeout, StateData) -> - %% TODO: Options must be stored in state: Options = [], proc_lib:hibernate(?GEN_FSM, enter_loop, [?MODULE, Options, session_established, StateData]), fsm_next_state(session_established, StateData); - session_established({xmlstreamend, _Name}, StateData) -> + send_trailer(StateData), {stop, normal, StateData}; +session_established({xmlstreamerror, + <<"XML stanza is too big">> = E}, + StateData) -> + send_element(StateData, + ?POLICY_VIOLATION_ERR((StateData#state.lang), E)), send_trailer(StateData), {stop, normal, StateData}; - -session_established({xmlstreamerror, "XML stanza is too big" = E}, StateData) -> - send_element(StateData, ?POLICY_VIOLATION_ERR(StateData#state.lang, E)), - send_trailer(StateData), - {stop, normal, StateData}; - session_established({xmlstreamerror, _}, StateData) -> send_element(StateData, ?INVALID_XML_ERR), send_trailer(StateData), {stop, normal, StateData}; - session_established(closed, StateData) -> {stop, normal, StateData}. %% Process packets sent by user (coming from user on c2s XMPP %% connection) session_established2(El, StateData) -> - {xmlelement, Name, Attrs, _Els} = El, + #xmlel{name = Name, attrs = Attrs} = El, User = StateData#state.user, Server = StateData#state.server, FromJID = StateData#state.jid, - To = xml:get_attr_s("to", Attrs), + To = xml:get_attr_s(<<"to">>, Attrs), ToJID = case To of - "" -> - jlib:make_jid(User, Server, ""); - _ -> - jlib:string_to_jid(To) + <<"">> -> jlib:make_jid(User, Server, <<"">>); + _ -> jlib:string_to_jid(To) end, - NewEl1 = jlib:remove_attr("xmlns", El), - NewEl = case xml:get_attr_s("xml:lang", Attrs) of - "" -> - case StateData#state.lang of - "" -> NewEl1; - Lang -> - xml:replace_tag_attr("xml:lang", Lang, NewEl1) - end; - _ -> - NewEl1 + NewEl1 = jlib:remove_attr(<<"xmlns">>, El), + NewEl = case xml:get_attr_s(<<"xml:lang">>, Attrs) of + <<"">> -> + case StateData#state.lang of + <<"">> -> NewEl1; + Lang -> + xml:replace_tag_attr(<<"xml:lang">>, Lang, NewEl1) + end; + _ -> NewEl1 end, - NewState = - case ToJID of - error -> - case xml:get_attr_s("type", Attrs) of - "error" -> StateData; - "result" -> StateData; - _ -> - Err = jlib:make_error_reply(NewEl, ?ERR_JID_MALFORMED), - send_element(StateData, Err), - StateData - end; - _ -> - case Name of - "presence" -> - PresenceEl = ejabberd_hooks:run_fold( - c2s_update_presence, - Server, - NewEl, - [User, Server]), - ejabberd_hooks:run( - user_send_packet, - Server, - [FromJID, ToJID, PresenceEl]), - case ToJID of - #jid{user = User, - server = Server, - resource = ""} -> - ?DEBUG("presence_update(~p,~n\t~p,~n\t~p)", - [FromJID, PresenceEl, StateData]), - presence_update(FromJID, PresenceEl, - StateData); - _ -> - presence_track(FromJID, ToJID, PresenceEl, - StateData) - end; - "iq" -> - case jlib:iq_query_info(NewEl) of - #iq{xmlns = Xmlns} = IQ - when Xmlns == ?NS_PRIVACY; - Xmlns == ?NS_BLOCKING -> - process_privacy_iq( - FromJID, ToJID, IQ, StateData); - _ -> - ejabberd_hooks:run( - user_send_packet, - Server, - [FromJID, ToJID, NewEl]), - check_privacy_route(FromJID, StateData, FromJID, ToJID, NewEl), - StateData - end; - "message" -> - ejabberd_hooks:run(user_send_packet, - Server, - [FromJID, ToJID, NewEl]), - check_privacy_route(FromJID, StateData, FromJID, - ToJID, NewEl), - StateData; - _ -> - StateData - end - end, - ejabberd_hooks:run(c2s_loop_debug, [{xmlstreamelement, El}]), + NewState = case ToJID of + error -> + case xml:get_attr_s(<<"type">>, Attrs) of + <<"error">> -> StateData; + <<"result">> -> StateData; + _ -> + Err = jlib:make_error_reply(NewEl, + ?ERR_JID_MALFORMED), + send_element(StateData, Err), + StateData + end; + _ -> + case Name of + <<"presence">> -> + PresenceEl = + ejabberd_hooks:run_fold(c2s_update_presence, + Server, NewEl, + [User, Server]), + ejabberd_hooks:run(user_send_packet, Server, + [FromJID, ToJID, PresenceEl]), + case ToJID of + #jid{user = User, server = Server, + resource = <<"">>} -> + ?DEBUG("presence_update(~p,~n\t~p,~n\t~p)", + [FromJID, PresenceEl, StateData]), + presence_update(FromJID, PresenceEl, + StateData); + _ -> + presence_track(FromJID, ToJID, PresenceEl, + StateData) + end; + <<"iq">> -> + case jlib:iq_query_info(NewEl) of + #iq{xmlns = Xmlns} = IQ + when Xmlns == (?NS_PRIVACY); + Xmlns == (?NS_BLOCKING) -> + process_privacy_iq(FromJID, ToJID, IQ, + StateData); + _ -> + ejabberd_hooks:run(user_send_packet, Server, + [FromJID, ToJID, NewEl]), + check_privacy_route(FromJID, StateData, + FromJID, ToJID, NewEl), + StateData + end; + <<"message">> -> + ejabberd_hooks:run(user_send_packet, Server, + [FromJID, ToJID, NewEl]), + check_privacy_route(FromJID, StateData, FromJID, + ToJID, NewEl), + StateData; + _ -> StateData + end + end, + ejabberd_hooks:run(c2s_loop_debug, + [{xmlstreamelement, El}]), fsm_next_state(session_established, NewState). - - %%---------------------------------------------------------------------- %% Func: StateName/3 %% Returns: {next_state, NextStateName, NextStateData} | @@ -1162,24 +1174,22 @@ handle_event(_Event, StateName, StateData) -> %% {stop, Reason, NewStateData} | %% {stop, Reason, Reply, NewStateData} %%---------------------------------------------------------------------- -handle_sync_event({get_presence}, _From, StateName, StateData) -> +handle_sync_event({get_presence}, _From, StateName, + StateData) -> User = StateData#state.user, PresLast = StateData#state.pres_last, - Show = get_showtag(PresLast), Status = get_statustag(PresLast), Resource = StateData#state.resource, - Reply = {User, Resource, Show, Status}, fsm_reply(Reply, StateName, StateData); - -handle_sync_event(get_subscribed, _From, StateName, StateData) -> - Subscribed = ?SETS:to_list(StateData#state.pres_f), +handle_sync_event(get_subscribed, _From, StateName, + StateData) -> + Subscribed = (?SETS):to_list(StateData#state.pres_f), {reply, Subscribed, StateName, StateData}; - -handle_sync_event(_Event, _From, StateName, StateData) -> - Reply = ok, - fsm_reply(Reply, StateName, StateData). +handle_sync_event(_Event, _From, StateName, + StateData) -> + Reply = ok, fsm_reply(Reply, StateName, StateData). code_change(_OldVsn, StateName, StateData, _Extra) -> {ok, StateName, StateData}. @@ -1197,209 +1207,301 @@ handle_info({send_text, Text}, StateName, StateData) -> handle_info(replaced, _StateName, StateData) -> Lang = StateData#state.lang, send_element(StateData, - ?SERRT_CONFLICT(Lang, "Replaced by new connection")), + ?SERRT_CONFLICT(Lang, + <<"Replaced by new connection">>)), send_trailer(StateData), - {stop, normal, StateData#state{authenticated = replaced}}; + {stop, normal, + StateData#state{authenticated = replaced}}; +handle_info({route, _From, _To, {broadcast, Data}}, + StateName, StateData) -> + ?DEBUG("broadcast~n~p~n", [Data]), + case Data of + {item, IJID, ISubscription} -> + fsm_next_state(StateName, + roster_change(IJID, ISubscription, StateData)); + {exit, Reason} -> + Lang = StateData#state.lang, + send_element(StateData, ?SERRT_CONFLICT(Lang, Reason)), + catch send_trailer(StateData), + {stop, normal, StateData}; + {privacy_list, PrivList, PrivListName} -> + case ejabberd_hooks:run_fold(privacy_updated_list, + StateData#state.server, + false, + [StateData#state.privacy_list, + PrivList]) of + false -> + fsm_next_state(StateName, StateData); + NewPL -> + PrivPushIQ = #iq{type = set, + xmlns = ?NS_PRIVACY, + id = <<"push", + (randoms:get_string())/binary>>, + sub_el = + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, + ?NS_PRIVACY}], + children = + [#xmlel{name = <<"list">>, + attrs = [{<<"name">>, + PrivListName}], + children = []}]}]}, + PrivPushEl = jlib:replace_from_to( + jlib:jid_remove_resource(StateData#state.jid), + StateData#state.jid, + jlib:iq_to_xml(PrivPushIQ)), + send_element(StateData, PrivPushEl), + fsm_next_state(StateName, + StateData#state{privacy_list = NewPL}) + end; + {blocking, What} -> + route_blocking(What, StateData), + fsm_next_state(StateName, StateData); + _ -> + fsm_next_state(StateName, StateData) + end; %% Process Packets that are to be send to the user -handle_info({route, From, To, Packet}, StateName, StateData) -> - {xmlelement, Name, Attrs, Els} = Packet, - {Pass, NewAttrs, NewState} = - case Name of - "presence" -> - State = ejabberd_hooks:run_fold( - c2s_presence_in, StateData#state.server, - StateData, - [{From, To, Packet}]), - case xml:get_attr_s("type", Attrs) of - "probe" -> - LFrom = jlib:jid_tolower(From), - LBFrom = jlib:jid_remove_resource(LFrom), - NewStateData = - case ?SETS:is_element( - LFrom, State#state.pres_a) orelse - ?SETS:is_element( - LBFrom, State#state.pres_a) of - true -> - State; - false -> - case ?SETS:is_element( - LFrom, State#state.pres_f) of - true -> - A = ?SETS:add_element( - LFrom, - State#state.pres_a), - State#state{pres_a = A}; - false -> - case ?SETS:is_element( - LBFrom, State#state.pres_f) of - true -> - A = ?SETS:add_element( - LBFrom, - State#state.pres_a), - State#state{pres_a = A}; - false -> - State - end - end - end, - process_presence_probe(From, To, NewStateData), - {false, Attrs, NewStateData}; - "error" -> - NewA = remove_element(jlib:jid_tolower(From), - State#state.pres_a), - {true, Attrs, State#state{pres_a = NewA}}; - "invisible" -> - Attrs1 = lists:keydelete("type", 1, Attrs), - {true, [{"type", "unavailable"} | Attrs1], State}; - "subscribe" -> - SRes = is_privacy_allow(State, From, To, Packet, in), - {SRes, Attrs, State}; - "subscribed" -> - SRes = is_privacy_allow(State, From, To, Packet, in), - {SRes, Attrs, State}; - "unsubscribe" -> - SRes = is_privacy_allow(State, From, To, Packet, in), - {SRes, Attrs, State}; - "unsubscribed" -> - SRes = is_privacy_allow(State, From, To, Packet, in), - {SRes, Attrs, State}; - _ -> - case privacy_check_packet(State, From, To, Packet, in) of - allow -> - LFrom = jlib:jid_tolower(From), - LBFrom = jlib:jid_remove_resource(LFrom), - case ?SETS:is_element( - LFrom, State#state.pres_a) orelse - ?SETS:is_element( - LBFrom, State#state.pres_a) of - true -> - {true, Attrs, State}; - false -> - case ?SETS:is_element( - LFrom, State#state.pres_f) of - true -> - A = ?SETS:add_element( - LFrom, - State#state.pres_a), - {true, Attrs, - State#state{pres_a = A}}; - false -> - case ?SETS:is_element( - LBFrom, State#state.pres_f) of - true -> - A = ?SETS:add_element( - LBFrom, - State#state.pres_a), - {true, Attrs, - State#state{pres_a = A}}; - false -> - {true, Attrs, State} - end - end - end; - deny -> - {false, Attrs, State} - end - end; - "broadcast" -> - ?DEBUG("broadcast~n~p~n", [Els]), - case Els of - [{item, IJID, ISubscription}] -> - {false, Attrs, - roster_change(IJID, ISubscription, - StateData)}; - [{exit, Reason}] -> - {exit, Attrs, Reason}; - [{privacy_list, PrivList, PrivListName}] -> - case ejabberd_hooks:run_fold( - privacy_updated_list, StateData#state.server, - false, - [StateData#state.privacy_list, - PrivList]) of - false -> - {false, Attrs, StateData}; - NewPL -> - PrivPushIQ = - #iq{type = set, xmlns = ?NS_PRIVACY, - id = "push" ++ randoms:get_string(), - sub_el = [{xmlelement, "query", - [{"xmlns", ?NS_PRIVACY}], - [{xmlelement, "list", - [{"name", PrivListName}], - []}]}]}, - PrivPushEl = - jlib:replace_from_to( - jlib:jid_remove_resource( - StateData#state.jid), - StateData#state.jid, - jlib:iq_to_xml(PrivPushIQ)), - send_element(StateData, PrivPushEl), - {false, Attrs, StateData#state{privacy_list = NewPL}} - end; - [{blocking, What}] -> - route_blocking(What, StateData), - {false, Attrs, StateData}; - _ -> - {false, Attrs, StateData} - end; - "iq" -> - IQ = jlib:iq_query_info(Packet), - case IQ of - #iq{xmlns = ?NS_LAST} -> - LFrom = jlib:jid_tolower(From), - LBFrom = jlib:jid_remove_resource(LFrom), - HasFromSub = (?SETS:is_element(LFrom, StateData#state.pres_f) orelse ?SETS:is_element(LBFrom, StateData#state.pres_f)) - andalso is_privacy_allow(StateData, To, From, {xmlelement, "presence", [], []}, out), - case HasFromSub of - true -> - case privacy_check_packet(StateData, From, To, Packet, in) of - allow -> - {true, Attrs, StateData}; - deny -> - {false, Attrs, StateData} - end; - _ -> - Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN), - ejabberd_router:route(To, From, Err), - {false, Attrs, StateData} - end; - IQ when (is_record(IQ, iq)) or (IQ == reply) -> - case privacy_check_packet(StateData, From, To, Packet, in) of - allow -> - {true, Attrs, StateData}; - deny when is_record(IQ, iq) -> - Err = jlib:make_error_reply( - Packet, ?ERR_SERVICE_UNAVAILABLE), - ejabberd_router:route(To, From, Err), - {false, Attrs, StateData}; - deny when IQ == reply -> - {false, Attrs, StateData} - end; - IQ when (IQ == invalid) or (IQ == not_iq) -> - {false, Attrs, StateData} - end; - "message" -> - case privacy_check_packet(StateData, From, To, Packet, in) of - allow -> - {true, Attrs, StateData}; - deny -> - {false, Attrs, StateData} - end; - _ -> - {true, Attrs, StateData} - end, - if - Pass == exit -> +handle_info({route, From, To, + #xmlel{name = Name, attrs = Attrs, children = Els} = Packet}, + StateName, StateData) -> + {Pass, NewAttrs, NewState} = case Name of + <<"presence">> -> + State = + ejabberd_hooks:run_fold(c2s_presence_in, + StateData#state.server, + StateData, + [{From, To, + Packet}]), + case xml:get_attr_s(<<"type">>, Attrs) of + <<"probe">> -> + LFrom = jlib:jid_tolower(From), + LBFrom = + jlib:jid_remove_resource(LFrom), + NewStateData = case + (?SETS):is_element(LFrom, + State#state.pres_a) + orelse + (?SETS):is_element(LBFrom, + State#state.pres_a) + of + true -> State; + false -> + case + (?SETS):is_element(LFrom, + State#state.pres_f) + of + true -> + A = + (?SETS):add_element(LFrom, + State#state.pres_a), + State#state{pres_a + = + A}; + false -> + case + (?SETS):is_element(LBFrom, + State#state.pres_f) + of + true -> + A = + (?SETS):add_element(LBFrom, + State#state.pres_a), + State#state{pres_a + = + A}; + false -> + State + end + end + end, + process_presence_probe(From, To, + NewStateData), + {false, Attrs, NewStateData}; + <<"error">> -> + NewA = + remove_element(jlib:jid_tolower(From), + State#state.pres_a), + {true, Attrs, + State#state{pres_a = NewA}}; + <<"subscribe">> -> + SRes = is_privacy_allow(State, + From, To, + Packet, + in), + {SRes, Attrs, State}; + <<"subscribed">> -> + SRes = is_privacy_allow(State, + From, To, + Packet, + in), + {SRes, Attrs, State}; + <<"unsubscribe">> -> + SRes = is_privacy_allow(State, + From, To, + Packet, + in), + {SRes, Attrs, State}; + <<"unsubscribed">> -> + SRes = is_privacy_allow(State, + From, To, + Packet, + in), + {SRes, Attrs, State}; + _ -> + case privacy_check_packet(State, + From, To, + Packet, + in) + of + allow -> + LFrom = + jlib:jid_tolower(From), + LBFrom = + jlib:jid_remove_resource(LFrom), + case + (?SETS):is_element(LFrom, + State#state.pres_a) + orelse + (?SETS):is_element(LBFrom, + State#state.pres_a) + of + true -> + {true, Attrs, State}; + false -> + case + (?SETS):is_element(LFrom, + State#state.pres_f) + of + true -> + A = + (?SETS):add_element(LFrom, + State#state.pres_a), + {true, Attrs, + State#state{pres_a + = + A}}; + false -> + case + (?SETS):is_element(LBFrom, + State#state.pres_f) + of + true -> + A = + (?SETS):add_element(LBFrom, + State#state.pres_a), + {true, + Attrs, + State#state{pres_a + = + A}}; + false -> + {true, + Attrs, + State} + end + end + end; + deny -> {false, Attrs, State} + end + end; + <<"iq">> -> + IQ = jlib:iq_query_info(Packet), + case IQ of + #iq{xmlns = ?NS_LAST} -> + LFrom = jlib:jid_tolower(From), + LBFrom = + jlib:jid_remove_resource(LFrom), + HasFromSub = + ((?SETS):is_element(LFrom, + StateData#state.pres_f) + orelse + (?SETS):is_element(LBFrom, + StateData#state.pres_f)) + andalso + is_privacy_allow(StateData, + To, From, + #xmlel{name + = + <<"presence">>, + attrs + = + [], + children + = + []}, + out), + case HasFromSub of + true -> + case + privacy_check_packet(StateData, + From, + To, + Packet, + in) + of + allow -> + {true, Attrs, + StateData}; + deny -> + {false, Attrs, + StateData} + end; + _ -> + Err = + jlib:make_error_reply(Packet, + ?ERR_FORBIDDEN), + ejabberd_router:route(To, + From, + Err), + {false, Attrs, StateData} + end; + IQ + when is_record(IQ, iq) or + (IQ == reply) -> + case + privacy_check_packet(StateData, + From, To, + Packet, in) + of + allow -> + {true, Attrs, StateData}; + deny when is_record(IQ, iq) -> + Err = + jlib:make_error_reply(Packet, + ?ERR_SERVICE_UNAVAILABLE), + ejabberd_router:route(To, + From, + Err), + {false, Attrs, StateData}; + deny when IQ == reply -> + {false, Attrs, StateData} + end; + IQ + when (IQ == invalid) or + (IQ == not_iq) -> + {false, Attrs, StateData} + end; + <<"message">> -> + case privacy_check_packet(StateData, + From, To, + Packet, in) + of + allow -> {true, Attrs, StateData}; + deny -> {false, Attrs, StateData} + end; + _ -> {true, Attrs, StateData} + end, + if Pass == exit -> %% When Pass==exit, NewState contains a string instead of a #state{} Lang = StateData#state.lang, send_element(StateData, ?SERRT_CONFLICT(Lang, NewState)), send_trailer(StateData), {stop, normal, StateData}; Pass -> - Attrs2 = jlib:replace_from_to_attrs(jlib:jid_to_string(From), - jlib:jid_to_string(To), - NewAttrs), - FixedPacket = {xmlelement, Name, Attrs2, Els}, + Attrs2 = + jlib:replace_from_to_attrs(jlib:jid_to_string(From), + jlib:jid_to_string(To), NewAttrs), + FixedPacket = #xmlel{name = Name, attrs = Attrs2, children = Els}, send_element(StateData, FixedPacket), ejabberd_hooks:run(user_receive_packet, StateData#state.server, @@ -1410,40 +1512,38 @@ handle_info({route, From, To, Packet}, StateName, StateData) -> ejabberd_hooks:run(c2s_loop_debug, [{route, From, To, Packet}]), fsm_next_state(StateName, NewState) end; -handle_info({'DOWN', Monitor, _Type, _Object, _Info}, _StateName, StateData) - when Monitor == StateData#state.socket_monitor -> +handle_info({'DOWN', Monitor, _Type, _Object, _Info}, + _StateName, StateData) + when Monitor == StateData#state.socket_monitor -> {stop, normal, StateData}; handle_info(system_shutdown, StateName, StateData) -> case StateName of - wait_for_stream -> - send_header(StateData, ?MYNAME, "1.0", "en"), - send_element(StateData, ?SERR_SYSTEM_SHUTDOWN), - send_trailer(StateData), - ok; - _ -> - send_element(StateData, ?SERR_SYSTEM_SHUTDOWN), - send_trailer(StateData), - ok + wait_for_stream -> + send_header(StateData, ?MYNAME, <<"1.0">>, <<"en">>), + send_element(StateData, ?SERR_SYSTEM_SHUTDOWN), + send_trailer(StateData), + ok; + _ -> + send_element(StateData, ?SERR_SYSTEM_SHUTDOWN), + send_trailer(StateData), + ok end, {stop, normal, StateData}; handle_info({force_update_presence, LUser}, StateName, - #state{user = LUser, server = LServer} = StateData) -> - NewStateData = - case StateData#state.pres_last of - {xmlelement, "presence", _Attrs, _Els} -> - PresenceEl = ejabberd_hooks:run_fold( - c2s_update_presence, - LServer, - StateData#state.pres_last, - [LUser, LServer]), - StateData2 = StateData#state{pres_last = PresenceEl}, - presence_update(StateData2#state.jid, - PresenceEl, - StateData2), - StateData2; - _ -> - StateData - end, + #state{user = LUser, server = LServer} = StateData) -> + NewStateData = case StateData#state.pres_last of + #xmlel{name = <<"presence">>} -> + PresenceEl = + ejabberd_hooks:run_fold(c2s_update_presence, + LServer, + StateData#state.pres_last, + [LUser, LServer]), + StateData2 = StateData#state{pres_last = PresenceEl}, + presence_update(StateData2#state.jid, PresenceEl, + StateData2), + StateData2; + _ -> StateData + end, {next_state, StateName, NewStateData}; handle_info({broadcast, Type, From, Packet}, StateName, StateData) -> Recipients = ejabberd_hooks:run_fold( @@ -1480,61 +1580,59 @@ print_state(State = #state{pres_t = T, pres_f = F, pres_a = A, pres_i = I}) -> %%---------------------------------------------------------------------- terminate(_Reason, StateName, StateData) -> case StateName of - session_established -> - case StateData#state.authenticated of - replaced -> - ?INFO_MSG("(~w) Replaced session for ~s", - [StateData#state.socket, - jlib:jid_to_string(StateData#state.jid)]), - From = StateData#state.jid, - Packet = {xmlelement, "presence", - [{"type", "unavailable"}], - [{xmlelement, "status", [], - [{xmlcdata, "Replaced by new connection"}]}]}, - ejabberd_sm:close_session_unset_presence( - StateData#state.sid, - StateData#state.user, - StateData#state.server, - StateData#state.resource, - "Replaced by new connection"), - presence_broadcast( - StateData, From, StateData#state.pres_a, Packet), - presence_broadcast( - StateData, From, StateData#state.pres_i, Packet); - _ -> - ?INFO_MSG("(~w) Close session for ~s", - [StateData#state.socket, - jlib:jid_to_string(StateData#state.jid)]), - - EmptySet = ?SETS:new(), - case StateData of - #state{pres_last = undefined, - pres_a = EmptySet, - pres_i = EmptySet, - pres_invis = false} -> - ejabberd_sm:close_session(StateData#state.sid, - StateData#state.user, - StateData#state.server, - StateData#state.resource); - _ -> - From = StateData#state.jid, - Packet = {xmlelement, "presence", - [{"type", "unavailable"}], []}, - ejabberd_sm:close_session_unset_presence( - StateData#state.sid, - StateData#state.user, - StateData#state.server, - StateData#state.resource, - ""), - presence_broadcast( - StateData, From, StateData#state.pres_a, Packet), - presence_broadcast( - StateData, From, StateData#state.pres_i, Packet) - end - end, - bounce_messages(); - _ -> - ok + session_established -> + case StateData#state.authenticated of + replaced -> + ?INFO_MSG("(~w) Replaced session for ~s", + [StateData#state.socket, + jlib:jid_to_string(StateData#state.jid)]), + From = StateData#state.jid, + Packet = #xmlel{name = <<"presence">>, + attrs = [{<<"type">>, <<"unavailable">>}], + children = + [#xmlel{name = <<"status">>, attrs = [], + children = + [{xmlcdata, + <<"Replaced by new connection">>}]}]}, + ejabberd_sm:close_session_unset_presence(StateData#state.sid, + StateData#state.user, + StateData#state.server, + StateData#state.resource, + <<"Replaced by new connection">>), + presence_broadcast(StateData, From, + StateData#state.pres_a, Packet), + presence_broadcast(StateData, From, + StateData#state.pres_i, Packet); + _ -> + ?INFO_MSG("(~w) Close session for ~s", + [StateData#state.socket, + jlib:jid_to_string(StateData#state.jid)]), + EmptySet = (?SETS):new(), + case StateData of + #state{pres_last = undefined, pres_a = EmptySet, pres_i = EmptySet, pres_invis = false} -> + ejabberd_sm:close_session(StateData#state.sid, + StateData#state.user, + StateData#state.server, + StateData#state.resource); + _ -> + From = StateData#state.jid, + Packet = #xmlel{name = <<"presence">>, + attrs = [{<<"type">>, <<"unavailable">>}], + children = []}, + ejabberd_sm:close_session_unset_presence(StateData#state.sid, + StateData#state.user, + StateData#state.server, + StateData#state.resource, + <<"">>), + presence_broadcast(StateData, From, + StateData#state.pres_a, Packet), + presence_broadcast(StateData, From, + StateData#state.pres_i, Packet) + end + end, + bounce_messages(); + _ -> + ok end, (StateData#state.sockmod):close(StateData#state.socket), ok. @@ -1546,10 +1644,11 @@ terminate(_Reason, StateName, StateData) -> change_shaper(StateData, JID) -> Shaper = acl:match_rule(StateData#state.server, StateData#state.shaper, JID), - (StateData#state.sockmod):change_shaper(StateData#state.socket, Shaper). + (StateData#state.sockmod):change_shaper(StateData#state.socket, + Shaper). send_text(StateData, Text) when StateData#state.xml_socket -> - ?DEBUG("Send Text on stream = ~p", [lists:flatten(Text)]), + ?DEBUG("Send Text on stream = ~p", [Text]), (StateData#state.sockmod):send_xml(StateData#state.socket, {xmlstreamraw, Text}); send_text(StateData, Text) -> @@ -1563,82 +1662,67 @@ send_element(StateData, El) -> send_text(StateData, xml:element_to_binary(El)). send_header(StateData, Server, Version, Lang) - when StateData#state.xml_socket -> - VersionAttr = - case Version of - "" -> []; - _ -> [{"version", Version}] - end, - LangAttr = - case Lang of - "" -> []; - _ -> [{"xml:lang", Lang}] - end, - Header = - {xmlstreamstart, - "stream:stream", - VersionAttr ++ - LangAttr ++ - [{"xmlns", "jabber:client"}, - {"xmlns:stream", "http://etherx.jabber.org/streams"}, - {"id", StateData#state.streamid}, - {"from", Server}]}, - (StateData#state.sockmod):send_xml( - StateData#state.socket, Header); + when StateData#state.xml_socket -> + VersionAttr = case Version of + <<"">> -> []; + _ -> [{<<"version">>, Version}] + end, + LangAttr = case Lang of + <<"">> -> []; + _ -> [{<<"xml:lang">>, Lang}] + end, + Header = {xmlstreamstart, <<"stream:stream">>, + VersionAttr ++ + LangAttr ++ + [{<<"xmlns">>, <<"jabber:client">>}, + {<<"xmlns:stream">>, + <<"http://etherx.jabber.org/streams">>}, + {<<"id">>, StateData#state.streamid}, + {<<"from">>, Server}]}, + (StateData#state.sockmod):send_xml(StateData#state.socket, + Header); send_header(StateData, Server, Version, Lang) -> - VersionStr = - case Version of - "" -> ""; - _ -> [" version='", Version, "'"] - end, - LangStr = - case Lang of - "" -> ""; - _ -> [" xml:lang='", Lang, "'"] - end, + VersionStr = case Version of + <<"">> -> <<"">>; + _ -> [<<" version='">>, Version, <<"'">>] + end, + LangStr = case Lang of + <<"">> -> <<"">>; + _ -> [<<" xml:lang='">>, Lang, <<"'">>] + end, Header = io_lib:format(?STREAM_HEADER, - [StateData#state.streamid, - Server, - VersionStr, + [StateData#state.streamid, Server, VersionStr, LangStr]), - send_text(StateData, Header). + send_text(StateData, iolist_to_binary(Header)). -send_trailer(StateData) when StateData#state.xml_socket -> - (StateData#state.sockmod):send_xml( - StateData#state.socket, - {xmlstreamend, "stream:stream"}); +send_trailer(StateData) + when StateData#state.xml_socket -> + (StateData#state.sockmod):send_xml(StateData#state.socket, + {xmlstreamend, <<"stream:stream">>}); send_trailer(StateData) -> send_text(StateData, ?STREAM_TRAILER). - -new_id() -> - randoms:get_string(). - +new_id() -> randoms:get_string(). is_auth_packet(El) -> case jlib:iq_query_info(El) of - #iq{id = ID, type = Type, xmlns = ?NS_AUTH, sub_el = SubEl} -> - {xmlelement, _, _, Els} = SubEl, - {auth, ID, Type, - get_auth_tags(Els, "", "", "", "")}; - _ -> - false + #iq{id = ID, type = Type, xmlns = ?NS_AUTH, + sub_el = SubEl} -> + #xmlel{children = Els} = SubEl, + {auth, ID, Type, + get_auth_tags(Els, <<"">>, <<"">>, <<"">>, <<"">>)}; + _ -> false end. - -get_auth_tags([{xmlelement, Name, _Attrs, Els}| L], U, P, D, R) -> +get_auth_tags([#xmlel{name = Name, children = Els} | L], + U, P, D, R) -> CData = xml:get_cdata(Els), case Name of - "username" -> - get_auth_tags(L, CData, P, D, R); - "password" -> - get_auth_tags(L, U, CData, D, R); - "digest" -> - get_auth_tags(L, U, P, CData, R); - "resource" -> - get_auth_tags(L, U, P, D, CData); - _ -> - get_auth_tags(L, U, P, D, R) + <<"username">> -> get_auth_tags(L, CData, P, D, R); + <<"password">> -> get_auth_tags(L, U, CData, D, R); + <<"digest">> -> get_auth_tags(L, U, P, CData, R); + <<"resource">> -> get_auth_tags(L, U, P, D, CData); + _ -> get_auth_tags(L, U, P, D, R) end; get_auth_tags([_ | L], U, P, D, R) -> get_auth_tags(L, U, P, D, R); @@ -1664,7 +1748,7 @@ get_conn_type(StateData) -> process_presence_probe(From, To, StateData) -> LFrom = jlib:jid_tolower(From), - LBFrom = setelement(3, LFrom, ""), + LBFrom = setelement(3, LFrom, <<"">>), case StateData#state.pres_last of undefined -> ok; @@ -1688,7 +1772,7 @@ process_presence_probe(From, To, StateData) -> Packet = xml:append_subtags( StateData#state.pres_last, %% To is the one sending the presence (the target of the probe) - [jlib:timestamp_to_xml(Timestamp, utc, To, ""), + [jlib:timestamp_to_xml(Timestamp, utc, To, <<"">>), %% TODO: Delete the next line once XEP-0091 is Obsolete jlib:timestamp_to_xml(Timestamp)]), case privacy_check_packet(StateData, To, From, Packet, out) of @@ -1707,9 +1791,9 @@ process_presence_probe(From, To, StateData) -> end; Cond2 -> ejabberd_router:route(To, From, - {xmlelement, "presence", - [], - []}); + #xmlel{name = <<"presence">>, + attrs = [], + children = []}); true -> ok end @@ -1717,500 +1801,414 @@ process_presence_probe(From, To, StateData) -> %% User updates his presence (non-directed presence packet) presence_update(From, Packet, StateData) -> - {xmlelement, _Name, Attrs, _Els} = Packet, - case xml:get_attr_s("type", Attrs) of - "unavailable" -> - Status = case xml:get_subtag(Packet, "status") of - false -> - ""; - StatusTag -> - xml:get_tag_cdata(StatusTag) + #xmlel{attrs = Attrs} = Packet, + case xml:get_attr_s(<<"type">>, Attrs) of + <<"unavailable">> -> + Status = case xml:get_subtag(Packet, <<"status">>) of + false -> <<"">>; + StatusTag -> xml:get_tag_cdata(StatusTag) + end, + Info = [{ip, StateData#state.ip}, + {conn, StateData#state.conn}, + {auth_module, StateData#state.auth_module}], + ejabberd_sm:unset_presence(StateData#state.sid, + StateData#state.user, + StateData#state.server, + StateData#state.resource, Status, Info), + presence_broadcast(StateData, From, + StateData#state.pres_a, Packet), + StateData#state{pres_last = undefined, + pres_timestamp = undefined, pres_a = (?SETS):new()}; + <<"error">> -> StateData; + <<"probe">> -> StateData; + <<"subscribe">> -> StateData; + <<"subscribed">> -> StateData; + <<"unsubscribe">> -> StateData; + <<"unsubscribed">> -> StateData; + _ -> + OldPriority = case StateData#state.pres_last of + undefined -> 0; + OldPresence -> get_priority_from_presence(OldPresence) + end, + NewPriority = get_priority_from_presence(Packet), + Timestamp = calendar:now_to_universal_time(now()), + update_priority(NewPriority, Packet, StateData), + FromUnavail = (StateData#state.pres_last == undefined), + ?DEBUG("from unavail = ~p~n", [FromUnavail]), + NewStateData = StateData#state{pres_last = Packet, + pres_timestamp = Timestamp}, + NewState = if FromUnavail -> + ejabberd_hooks:run(user_available_hook, + NewStateData#state.server, + [NewStateData#state.jid]), + if NewPriority >= 0 -> + resend_offline_messages(NewStateData), + resend_subscription_requests(NewStateData); + true -> ok + end, + presence_broadcast_first(From, NewStateData, + Packet); + true -> + presence_broadcast_to_trusted(NewStateData, From, + NewStateData#state.pres_f, + NewStateData#state.pres_a, + Packet), + if OldPriority < 0, NewPriority >= 0 -> + resend_offline_messages(NewStateData); + true -> ok + end, + NewStateData end, - Info = [{ip, StateData#state.ip}, {conn, StateData#state.conn}, - {auth_module, StateData#state.auth_module}], - ejabberd_sm:unset_presence(StateData#state.sid, - StateData#state.user, - StateData#state.server, - StateData#state.resource, - Status, - Info), - presence_broadcast(StateData, From, StateData#state.pres_a, Packet), - presence_broadcast(StateData, From, StateData#state.pres_i, Packet), - StateData#state{pres_last = undefined, - pres_timestamp = undefined, - pres_a = ?SETS:new(), - pres_i = ?SETS:new(), - pres_invis = false}; - "invisible" -> - NewPriority = get_priority_from_presence(Packet), - update_priority(NewPriority, Packet, StateData), - NewState = - if - not StateData#state.pres_invis -> - presence_broadcast(StateData, From, - StateData#state.pres_a, - Packet), - presence_broadcast(StateData, From, - StateData#state.pres_i, - Packet), - S1 = StateData#state{pres_last = undefined, - pres_timestamp = undefined, - pres_a = ?SETS:new(), - pres_i = ?SETS:new(), - pres_invis = true}, - presence_broadcast_first(From, S1, Packet); - true -> - StateData - end, - NewState; - "error" -> - StateData; - "probe" -> - StateData; - "subscribe" -> - StateData; - "subscribed" -> - StateData; - "unsubscribe" -> - StateData; - "unsubscribed" -> - StateData; - _ -> - OldPriority = case StateData#state.pres_last of - undefined -> - 0; - OldPresence -> - get_priority_from_presence(OldPresence) - end, - NewPriority = get_priority_from_presence(Packet), - Timestamp = calendar:now_to_universal_time(now()), - update_priority(NewPriority, Packet, StateData), - FromUnavail = (StateData#state.pres_last == undefined) or - StateData#state.pres_invis, - ?DEBUG("from unavail = ~p~n", [FromUnavail]), - NewStateData = StateData#state{pres_last = Packet, - pres_invis = false, - pres_timestamp = Timestamp}, - NewState = - if - FromUnavail -> - ejabberd_hooks:run(user_available_hook, - NewStateData#state.server, - [NewStateData#state.jid]), - if NewPriority >= 0 -> - resend_offline_messages(NewStateData), - resend_subscription_requests(NewStateData); - true -> - ok - end, - presence_broadcast_first(From, NewStateData, Packet); - true -> - presence_broadcast_to_trusted(NewStateData, - From, - NewStateData#state.pres_f, - NewStateData#state.pres_a, - Packet), - if OldPriority < 0, NewPriority >= 0 -> - resend_offline_messages(NewStateData); - true -> - ok - end, - NewStateData - end, - NewState + NewState end. %% User sends a directed presence packet presence_track(From, To, Packet, StateData) -> - {xmlelement, _Name, Attrs, _Els} = Packet, + #xmlel{attrs = Attrs} = Packet, LTo = jlib:jid_tolower(To), User = StateData#state.user, Server = StateData#state.server, - case xml:get_attr_s("type", Attrs) of - "unavailable" -> - check_privacy_route(From, StateData, From, To, Packet), - I = remove_element(LTo, StateData#state.pres_i), - A = remove_element(LTo, StateData#state.pres_a), - StateData#state{pres_i = I, - pres_a = A}; - "invisible" -> - check_privacy_route(From, StateData, From, To, Packet), - I = ?SETS:add_element(LTo, StateData#state.pres_i), - A = remove_element(LTo, StateData#state.pres_a), - StateData#state{pres_i = I, - pres_a = A}; - "subscribe" -> - ejabberd_hooks:run(roster_out_subscription, - Server, - [User, Server, To, subscribe]), - check_privacy_route(From, StateData, jlib:jid_remove_resource(From), - To, Packet), - StateData; - "subscribed" -> - ejabberd_hooks:run(roster_out_subscription, - Server, - [User, Server, To, subscribed]), - check_privacy_route(From, StateData, jlib:jid_remove_resource(From), - To, Packet), - StateData; - "unsubscribe" -> - ejabberd_hooks:run(roster_out_subscription, - Server, - [User, Server, To, unsubscribe]), - check_privacy_route(From, StateData, jlib:jid_remove_resource(From), - To, Packet), - StateData; - "unsubscribed" -> - ejabberd_hooks:run(roster_out_subscription, - Server, - [User, Server, To, unsubscribed]), - check_privacy_route(From, StateData, jlib:jid_remove_resource(From), - To, Packet), - StateData; - "error" -> - check_privacy_route(From, StateData, From, To, Packet), - StateData; - "probe" -> - check_privacy_route(From, StateData, From, To, Packet), - StateData; - _ -> - check_privacy_route(From, StateData, From, To, Packet), - I = remove_element(LTo, StateData#state.pres_i), - A = ?SETS:add_element(LTo, StateData#state.pres_a), - StateData#state{pres_i = I, - pres_a = A} + case xml:get_attr_s(<<"type">>, Attrs) of + <<"unavailable">> -> + check_privacy_route(From, StateData, From, To, Packet), + A = remove_element(LTo, StateData#state.pres_a), + StateData#state{pres_a = A}; + <<"subscribe">> -> + ejabberd_hooks:run(roster_out_subscription, Server, + [User, Server, To, subscribe]), + check_privacy_route(From, StateData, + jlib:jid_remove_resource(From), To, Packet), + StateData; + <<"subscribed">> -> + ejabberd_hooks:run(roster_out_subscription, Server, + [User, Server, To, subscribed]), + check_privacy_route(From, StateData, + jlib:jid_remove_resource(From), To, Packet), + StateData; + <<"unsubscribe">> -> + ejabberd_hooks:run(roster_out_subscription, Server, + [User, Server, To, unsubscribe]), + check_privacy_route(From, StateData, + jlib:jid_remove_resource(From), To, Packet), + StateData; + <<"unsubscribed">> -> + ejabberd_hooks:run(roster_out_subscription, Server, + [User, Server, To, unsubscribed]), + check_privacy_route(From, StateData, + jlib:jid_remove_resource(From), To, Packet), + StateData; + <<"error">> -> + check_privacy_route(From, StateData, From, To, Packet), + StateData; + <<"probe">> -> + check_privacy_route(From, StateData, From, To, Packet), + StateData; + _ -> + check_privacy_route(From, StateData, From, To, Packet), + A = (?SETS):add_element(LTo, StateData#state.pres_a), + StateData#state{pres_a = A} end. -check_privacy_route(From, StateData, FromRoute, To, Packet) -> - case privacy_check_packet(StateData, From, To, Packet, out) of - deny -> - Lang = StateData#state.lang, - ErrText = "Your active privacy list has denied the routing of this stanza.", - Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), - ejabberd_router:route(To, From, Err), - ok; - allow -> - ejabberd_router:route(FromRoute, To, Packet) +check_privacy_route(From, StateData, FromRoute, To, + Packet) -> + case privacy_check_packet(StateData, From, To, Packet, + out) + of + deny -> + Lang = StateData#state.lang, + ErrText = <<"Your active privacy list has denied " + "the routing of this stanza.">>, + Err = jlib:make_error_reply(Packet, + ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), + ejabberd_router:route(To, From, Err), + ok; + allow -> ejabberd_router:route(FromRoute, To, Packet) end. -privacy_check_packet(StateData, From, To, Packet, Dir) -> - ejabberd_hooks:run_fold( - privacy_check_packet, StateData#state.server, - allow, - [StateData#state.user, - StateData#state.server, - StateData#state.privacy_list, - {From, To, Packet}, - Dir]). - %% Check if privacy rules allow this delivery +privacy_check_packet(StateData, From, To, Packet, + Dir) -> + ejabberd_hooks:run_fold(privacy_check_packet, + StateData#state.server, allow, + [StateData#state.user, StateData#state.server, + StateData#state.privacy_list, {From, To, Packet}, + Dir]). + is_privacy_allow(StateData, From, To, Packet, Dir) -> - allow == privacy_check_packet(StateData, From, To, Packet, Dir). + allow == + privacy_check_packet(StateData, From, To, Packet, Dir). +%% Send presence when disconnecting presence_broadcast(StateData, From, JIDSet, Packet) -> - lists:foreach(fun(JID) -> - FJID = jlib:make_jid(JID), - case privacy_check_packet(StateData, From, FJID, Packet, out) of - deny -> - ok; - allow -> - ejabberd_router:route(From, FJID, Packet) - end - end, ?SETS:to_list(JIDSet)). - -presence_broadcast_to_trusted(StateData, From, T, A, Packet) -> - lists:foreach( - fun(JID) -> - case ?SETS:is_element(JID, T) of - true -> - FJID = jlib:make_jid(JID), - case privacy_check_packet(StateData, From, FJID, Packet, out) of - deny -> - ok; - allow -> - ejabberd_router:route(From, FJID, Packet) - end; - _ -> - ok - end - end, ?SETS:to_list(A)). + JIDs = ?SETS:to_list(JIDSet), + JIDs2 = format_and_check_privacy(From, StateData, Packet, JIDs, out), + Server = StateData#state.server, + send_multiple(From, Server, JIDs2, Packet). +%% Send presence when updating presence +presence_broadcast_to_trusted(StateData, From, Trusted, JIDSet, Packet) -> + JIDs = ?SETS:to_list(JIDSet), + JIDs_trusted = [JID || JID <- JIDs, ?SETS:is_element(JID, Trusted)], + JIDs2 = format_and_check_privacy(From, StateData, Packet, JIDs_trusted, out), + Server = StateData#state.server, + send_multiple(From, Server, JIDs2, Packet). +%% Send presence when connecting presence_broadcast_first(From, StateData, Packet) -> - ?SETS:fold(fun(JID, X) -> - ejabberd_router:route( - From, - jlib:make_jid(JID), - {xmlelement, "presence", - [{"type", "probe"}], - []}), - X - end, - [], - StateData#state.pres_t), - if - StateData#state.pres_invis -> - StateData; - true -> - As = ?SETS:fold( - fun(JID, A) -> - FJID = jlib:make_jid(JID), - case privacy_check_packet(StateData, From, FJID, Packet, out) of - deny -> - ok; - allow -> - ejabberd_router:route(From, FJID, Packet) - end, - ?SETS:add_element(JID, A) - end, - StateData#state.pres_a, - StateData#state.pres_f), - StateData#state{pres_a = As} - end. + JIDsProbe = + ?SETS:fold( + fun(JID, L) -> [JID | L] end, + [], + StateData#state.pres_t), + PacketProbe = #xmlel{name = <<"presence">>, attrs = [{<<"type">>,<<"probe">>}], children = []}, + JIDs2Probe = format_and_check_privacy(From, StateData, Packet, JIDsProbe, out), + Server = StateData#state.server, + send_multiple(From, Server, JIDs2Probe, PacketProbe), + {As, JIDs} = + ?SETS:fold( + fun(JID, {A, JID_list}) -> + {?SETS:add_element(JID, A), JID_list++[JID]} + end, + {StateData#state.pres_a, []}, + StateData#state.pres_f), + JIDs2 = format_and_check_privacy(From, StateData, Packet, JIDs, out), + Server = StateData#state.server, + send_multiple(From, Server, JIDs2, Packet), + StateData#state{pres_a = As}. + +format_and_check_privacy(From, StateData, Packet, JIDs, Dir) -> + FJIDs = [jlib:make_jid(JID) || JID <- JIDs], + lists:filter( + fun(FJID) -> + case ejabberd_hooks:run_fold( + privacy_check_packet, StateData#state.server, + allow, + [StateData#state.user, + StateData#state.server, + StateData#state.privacy_list, + {From, FJID, Packet}, + Dir]) of + deny -> false; + allow -> true + end + end, + FJIDs). + +send_multiple(From, Server, JIDs, Packet) -> + ejabberd_router_multicast:route_multicast(From, Server, JIDs, Packet). remove_element(E, Set) -> - case ?SETS:is_element(E, Set) of - true -> - ?SETS:del_element(E, Set); - _ -> - Set + case (?SETS):is_element(E, Set) of + true -> (?SETS):del_element(E, Set); + _ -> Set end. - roster_change(IJID, ISubscription, StateData) -> LIJID = jlib:jid_tolower(IJID), - IsFrom = (ISubscription == both) or (ISubscription == from), - IsTo = (ISubscription == both) or (ISubscription == to), - OldIsFrom = ?SETS:is_element(LIJID, StateData#state.pres_f), - FSet = if - IsFrom -> - ?SETS:add_element(LIJID, StateData#state.pres_f); - true -> - remove_element(LIJID, StateData#state.pres_f) + IsFrom = (ISubscription == both) or + (ISubscription == from), + IsTo = (ISubscription == both) or (ISubscription == to), + OldIsFrom = (?SETS):is_element(LIJID, + StateData#state.pres_f), + FSet = if IsFrom -> + (?SETS):add_element(LIJID, StateData#state.pres_f); + true -> remove_element(LIJID, StateData#state.pres_f) end, - TSet = if - IsTo -> - ?SETS:add_element(LIJID, StateData#state.pres_t); - true -> - remove_element(LIJID, StateData#state.pres_t) + TSet = if IsTo -> + (?SETS):add_element(LIJID, StateData#state.pres_t); + true -> remove_element(LIJID, StateData#state.pres_t) end, case StateData#state.pres_last of - undefined -> - StateData#state{pres_f = FSet, pres_t = TSet}; - P -> - ?DEBUG("roster changed for ~p~n", [StateData#state.user]), - From = StateData#state.jid, - To = jlib:make_jid(IJID), - Cond1 = (not StateData#state.pres_invis) and IsFrom - and (not OldIsFrom), - Cond2 = (not IsFrom) and OldIsFrom - and (?SETS:is_element(LIJID, StateData#state.pres_a) or - ?SETS:is_element(LIJID, StateData#state.pres_i)), - if - Cond1 -> - ?DEBUG("C1: ~p~n", [LIJID]), - case privacy_check_packet(StateData, From, To, P, out) of - deny -> - ok; - allow -> - ejabberd_router:route(From, To, P) - end, - A = ?SETS:add_element(LIJID, - StateData#state.pres_a), - StateData#state{pres_a = A, - pres_f = FSet, - pres_t = TSet}; - Cond2 -> - ?DEBUG("C2: ~p~n", [LIJID]), - PU = {xmlelement, "presence", - [{"type", "unavailable"}], []}, - case privacy_check_packet(StateData, From, To, PU, out) of - deny -> - ok; - allow -> - ejabberd_router:route(From, To, PU) - end, - I = remove_element(LIJID, - StateData#state.pres_i), - A = remove_element(LIJID, - StateData#state.pres_a), - StateData#state{pres_i = I, - pres_a = A, - pres_f = FSet, - pres_t = TSet}; - true -> - StateData#state{pres_f = FSet, pres_t = TSet} - end + undefined -> + StateData#state{pres_f = FSet, pres_t = TSet}; + P -> + ?DEBUG("roster changed for ~p~n", + [StateData#state.user]), + From = StateData#state.jid, + To = jlib:make_jid(IJID), + Cond1 = IsFrom andalso not OldIsFrom, + Cond2 = not IsFrom andalso OldIsFrom andalso + ((?SETS):is_element(LIJID, StateData#state.pres_a)), + if Cond1 -> + ?DEBUG("C1: ~p~n", [LIJID]), + case privacy_check_packet(StateData, From, To, P, out) + of + deny -> ok; + allow -> ejabberd_router:route(From, To, P) + end, + A = (?SETS):add_element(LIJID, StateData#state.pres_a), + StateData#state{pres_a = A, pres_f = FSet, + pres_t = TSet}; + Cond2 -> + ?DEBUG("C2: ~p~n", [LIJID]), + PU = #xmlel{name = <<"presence">>, + attrs = [{<<"type">>, <<"unavailable">>}], + children = []}, + case privacy_check_packet(StateData, From, To, PU, out) + of + deny -> ok; + allow -> ejabberd_router:route(From, To, PU) + end, + A = remove_element(LIJID, StateData#state.pres_a), + StateData#state{pres_a = A, pres_f = FSet, + pres_t = TSet}; + true -> StateData#state{pres_f = FSet, pres_t = TSet} + end end. - update_priority(Priority, Packet, StateData) -> Info = [{ip, StateData#state.ip}, {conn, StateData#state.conn}, {auth_module, StateData#state.auth_module}], ejabberd_sm:set_presence(StateData#state.sid, - StateData#state.user, - StateData#state.server, - StateData#state.resource, - Priority, - Packet, - Info). + StateData#state.user, StateData#state.server, + StateData#state.resource, Priority, Packet, Info). get_priority_from_presence(PresencePacket) -> - case xml:get_subtag(PresencePacket, "priority") of - false -> - 0; - SubEl -> - case catch list_to_integer(xml:get_tag_cdata(SubEl)) of - P when is_integer(P) -> - P; - _ -> - 0 - end + case xml:get_subtag(PresencePacket, <<"priority">>) of + false -> 0; + SubEl -> + case catch + jlib:binary_to_integer(xml:get_tag_cdata(SubEl)) + of + P when is_integer(P) -> P; + _ -> 0 + end end. process_privacy_iq(From, To, - #iq{type = Type, sub_el = SubEl} = IQ, - StateData) -> - {Res, NewStateData} = - case Type of - get -> - R = ejabberd_hooks:run_fold( - privacy_iq_get, StateData#state.server, - {error, ?ERR_FEATURE_NOT_IMPLEMENTED}, - [From, To, IQ, StateData#state.privacy_list]), - {R, StateData}; - set -> - case ejabberd_hooks:run_fold( - privacy_iq_set, StateData#state.server, - {error, ?ERR_FEATURE_NOT_IMPLEMENTED}, - [From, To, IQ]) of - {result, R, NewPrivList} -> - {{result, R}, - StateData#state{privacy_list = NewPrivList}}; - R -> {R, StateData} - end - end, - IQRes = - case Res of - {result, Result} -> - IQ#iq{type = result, sub_el = Result}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end, - ejabberd_router:route( - To, From, jlib:iq_to_xml(IQRes)), + #iq{type = Type, sub_el = SubEl} = IQ, StateData) -> + {Res, NewStateData} = case Type of + get -> + R = ejabberd_hooks:run_fold(privacy_iq_get, + StateData#state.server, + {error, + ?ERR_FEATURE_NOT_IMPLEMENTED}, + [From, To, IQ, + StateData#state.privacy_list]), + {R, StateData}; + set -> + case ejabberd_hooks:run_fold(privacy_iq_set, + StateData#state.server, + {error, + ?ERR_FEATURE_NOT_IMPLEMENTED}, + [From, To, IQ]) + of + {result, R, NewPrivList} -> + {{result, R}, + StateData#state{privacy_list = + NewPrivList}}; + R -> {R, StateData} + end + end, + IQRes = case Res of + {result, Result} -> + IQ#iq{type = result, sub_el = Result}; + {error, Error} -> + IQ#iq{type = error, sub_el = [SubEl, Error]} + end, + ejabberd_router:route(To, From, jlib:iq_to_xml(IQRes)), NewStateData. - resend_offline_messages(StateData) -> - case ejabberd_hooks:run_fold( - resend_offline_messages_hook, StateData#state.server, - [], - [StateData#state.user, StateData#state.server]) of - Rs when is_list(Rs) -> - lists:foreach( - fun({route, - From, To, {xmlelement, _Name, _Attrs, _Els} = Packet}) -> - Pass = case privacy_check_packet(StateData, From, To, Packet, in) of - allow -> - true; - deny -> - false - end, - if - Pass -> - %% Attrs2 = jlib:replace_from_to_attrs( - %% jlib:jid_to_string(From), - %% jlib:jid_to_string(To), - %% Attrs), - %% FixedPacket = {xmlelement, Name, Attrs2, Els}, - %% Use route instead of send_element to go through standard workflow - ejabberd_router:route(From, To, Packet); - %% send_element(StateData, FixedPacket), - %% ejabberd_hooks:run(user_receive_packet, - %% StateData#state.server, - %% [StateData#state.jid, - %% From, To, FixedPacket]); - true -> - ok - end - end, Rs) + case + ejabberd_hooks:run_fold(resend_offline_messages_hook, + StateData#state.server, [], + [StateData#state.user, StateData#state.server]) + of + Rs -> %%when is_list(Rs) -> + lists:foreach(fun ({route, From, To, + #xmlel{} = Packet}) -> + Pass = case privacy_check_packet(StateData, + From, To, + Packet, in) + of + allow -> true; + deny -> false + end, + if Pass -> + ejabberd_router:route(From, To, Packet); + %% send_element(StateData, FixedPacket), + %% ejabberd_hooks:run(user_receive_packet, + %% StateData#state.server, + %% [StateData#state.jid, + %% From, To, FixedPacket]); + true -> ok + end + end, + Rs) end. resend_subscription_requests(#state{user = User, - server = Server} = StateData) -> - PendingSubscriptions = ejabberd_hooks:run_fold( - resend_subscription_requests_hook, - Server, - [], - [User, Server]), - lists:foreach(fun(XMLPacket) -> - send_element(StateData, - XMLPacket) + server = Server} = + StateData) -> + PendingSubscriptions = + ejabberd_hooks:run_fold(resend_subscription_requests_hook, + Server, [], [User, Server]), + lists:foreach(fun (XMLPacket) -> + send_element(StateData, XMLPacket) end, PendingSubscriptions). -get_showtag(undefined) -> - "unavailable"; +get_showtag(undefined) -> <<"unavailable">>; get_showtag(Presence) -> - case xml:get_path_s(Presence, [{elem, "show"}, cdata]) of - "" -> "available"; - ShowTag -> ShowTag + case xml:get_path_s(Presence, + [{elem, <<"show">>}, cdata]) + of + <<"">> -> <<"available">>; + ShowTag -> ShowTag end. -get_statustag(undefined) -> - ""; +get_statustag(undefined) -> <<"">>; get_statustag(Presence) -> - case xml:get_path_s(Presence, [{elem, "status"}, cdata]) of - ShowTag -> ShowTag + case xml:get_path_s(Presence, + [{elem, <<"status">>}, cdata]) + of + ShowTag -> ShowTag end. process_unauthenticated_stanza(StateData, El) -> - NewEl = case xml:get_tag_attr_s("xml:lang", El) of - "" -> - case StateData#state.lang of - "" -> El; - Lang -> - xml:replace_tag_attr("xml:lang", Lang, El) - end; - _ -> - El + NewEl = case xml:get_tag_attr_s(<<"xml:lang">>, El) of + <<"">> -> + case StateData#state.lang of + <<"">> -> El; + Lang -> xml:replace_tag_attr(<<"xml:lang">>, Lang, El) + end; + _ -> El end, case jlib:iq_query_info(NewEl) of - #iq{} = IQ -> - Res = ejabberd_hooks:run_fold(c2s_unauthenticated_iq, - StateData#state.server, - empty, - [StateData#state.server, IQ, - StateData#state.ip]), - case Res of - empty -> - % The only reasonable IQ's here are auth and register IQ's - % They contain secrets, so don't include subelements to response - ResIQ = IQ#iq{type = error, - sub_el = [?ERR_SERVICE_UNAVAILABLE]}, - Res1 = jlib:replace_from_to( - jlib:make_jid("", StateData#state.server, ""), - jlib:make_jid("", "", ""), - jlib:iq_to_xml(ResIQ)), - send_element(StateData, jlib:remove_attr("to", Res1)); - _ -> - send_element(StateData, Res) - end; - _ -> - % Drop any stanza, which isn't IQ stanza - ok + #iq{} = IQ -> + Res = ejabberd_hooks:run_fold(c2s_unauthenticated_iq, + StateData#state.server, empty, + [StateData#state.server, IQ, + StateData#state.ip]), + case Res of + empty -> + ResIQ = IQ#iq{type = error, + sub_el = [?ERR_SERVICE_UNAVAILABLE]}, + Res1 = jlib:replace_from_to(jlib:make_jid(<<"">>, + StateData#state.server, + <<"">>), + jlib:make_jid(<<"">>, <<"">>, + <<"">>), + jlib:iq_to_xml(ResIQ)), + send_element(StateData, + jlib:remove_attr(<<"to">>, Res1)); + _ -> send_element(StateData, Res) + end; + _ -> + % Drop any stanza, which isn't IQ stanza + ok end. peerip(SockMod, Socket) -> IP = case SockMod of - gen_tcp -> inet:peername(Socket); - _ -> SockMod:peername(Socket) + gen_tcp -> inet:peername(Socket); + _ -> SockMod:peername(Socket) end, case IP of - {ok, IPOK} -> IPOK; - _ -> undefined + {ok, IPOK} -> IPOK; + _ -> undefined end. %% fsm_next_state_pack: Pack the StateData structure to improve @@ -2227,70 +2225,64 @@ fsm_next_state_gc(StateName, PackedStateData) -> %% fsm_next_state: Generate the next_state FSM tuple with different %% timeout, depending on the future state fsm_next_state(session_established, StateData) -> - {next_state, session_established, StateData, ?C2S_HIBERNATE_TIMEOUT}; + {next_state, session_established, StateData, + ?C2S_HIBERNATE_TIMEOUT}; fsm_next_state(StateName, StateData) -> {next_state, StateName, StateData, ?C2S_OPEN_TIMEOUT}. %% fsm_reply: Generate the reply FSM tuple with different timeout, %% depending on the future state fsm_reply(Reply, session_established, StateData) -> - {reply, Reply, session_established, StateData, ?C2S_HIBERNATE_TIMEOUT}; + {reply, Reply, session_established, StateData, + ?C2S_HIBERNATE_TIMEOUT}; fsm_reply(Reply, StateName, StateData) -> {reply, Reply, StateName, StateData, ?C2S_OPEN_TIMEOUT}. %% Used by c2s blacklist plugins -is_ip_blacklisted(undefined) -> - false; -is_ip_blacklisted({IP,_Port}) -> +is_ip_blacklisted(undefined) -> false; +is_ip_blacklisted({IP, _Port}) -> ejabberd_hooks:run_fold(check_bl_c2s, false, [IP]). %% Check from attributes %% returns invalid-from|NewElement check_from(El, FromJID) -> - case xml:get_tag_attr("from", El) of - false -> - El; - {value, SJID} -> - JID = jlib:string_to_jid(SJID), - case JID of - error -> - 'invalid-from'; - #jid{} -> - if - (JID#jid.luser == FromJID#jid.luser) and - (JID#jid.lserver == FromJID#jid.lserver) and - (JID#jid.lresource == FromJID#jid.lresource) -> - El; - (JID#jid.luser == FromJID#jid.luser) and - (JID#jid.lserver == FromJID#jid.lserver) and - (JID#jid.lresource == "") -> - El; - true -> - 'invalid-from' - end - end + case xml:get_tag_attr(<<"from">>, El) of + false -> El; + {value, SJID} -> + JID = jlib:string_to_jid(SJID), + case JID of + error -> 'invalid-from'; + #jid{} -> + if (JID#jid.luser == FromJID#jid.luser) and + (JID#jid.lserver == FromJID#jid.lserver) + and (JID#jid.lresource == FromJID#jid.lresource) -> + El; + (JID#jid.luser == FromJID#jid.luser) and + (JID#jid.lserver == FromJID#jid.lserver) + and (JID#jid.lresource == <<"">>) -> + El; + true -> 'invalid-from' + end + end end. 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}]; - _ -> - [] - end + {value, {_, 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. bounce_messages() -> receive - {route, From, To, El} -> - ejabberd_router:route(From, To, El), - bounce_messages() - after 0 -> - ok + {route, From, To, El} -> + ejabberd_router:route(From, To, El), bounce_messages() + after 0 -> ok end. %%%---------------------------------------------------------------------- @@ -2298,40 +2290,40 @@ bounce_messages() -> %%%---------------------------------------------------------------------- route_blocking(What, StateData) -> - SubEl = - case What of - {block, JIDs} -> - {xmlelement, "block", - [{"xmlns", ?NS_BLOCKING}], - lists:map( - fun(JID) -> - {xmlelement, "item", - [{"jid", jlib:jid_to_string(JID)}], - []} - end, JIDs)}; - {unblock, JIDs} -> - {xmlelement, "unblock", - [{"xmlns", ?NS_BLOCKING}], - lists:map( - fun(JID) -> - {xmlelement, "item", - [{"jid", jlib:jid_to_string(JID)}], - []} - end, JIDs)}; - unblock_all -> - {xmlelement, "unblock", - [{"xmlns", ?NS_BLOCKING}], []} - end, - PrivPushIQ = - #iq{type = set, xmlns = ?NS_BLOCKING, - id = "push", - sub_el = [SubEl]}, + SubEl = case What of + {block, JIDs} -> + #xmlel{name = <<"block">>, + attrs = [{<<"xmlns">>, ?NS_BLOCKING}], + children = + lists:map(fun (JID) -> + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, + jlib:jid_to_string(JID)}], + children = []} + end, + JIDs)}; + {unblock, JIDs} -> + #xmlel{name = <<"unblock">>, + attrs = [{<<"xmlns">>, ?NS_BLOCKING}], + children = + lists:map(fun (JID) -> + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, + jlib:jid_to_string(JID)}], + children = []} + end, + JIDs)}; + unblock_all -> + #xmlel{name = <<"unblock">>, + attrs = [{<<"xmlns">>, ?NS_BLOCKING}], children = []} + end, + PrivPushIQ = #iq{type = set, xmlns = ?NS_BLOCKING, + id = <<"push">>, sub_el = [SubEl]}, PrivPushEl = - jlib:replace_from_to( - jlib:jid_remove_resource( - StateData#state.jid), - StateData#state.jid, - jlib:iq_to_xml(PrivPushIQ)), + jlib:replace_from_to(jlib:jid_remove_resource(StateData#state.jid), + StateData#state.jid, jlib:iq_to_xml(PrivPushIQ)), send_element(StateData, PrivPushEl), %% No need to replace active privacy list here, %% blocking pushes are always accompanied by @@ -2344,45 +2336,35 @@ route_blocking(What, StateData) -> %% Try to reduce the heap footprint of the four presence sets %% by ensuring that we re-use strings and Jids wherever possible. -pack(S = #state{pres_a=A, - pres_i=I, - pres_f=F, - pres_t=T}) -> - {NewA, Pack1} = pack_jid_set(A, gb_trees:empty()), - {NewI, Pack2} = pack_jid_set(I, Pack1), +pack(S = #state{pres_a = A, pres_f = F, + pres_t = T}) -> + {NewA, Pack2} = pack_jid_set(A, gb_trees:empty()), {NewF, Pack3} = pack_jid_set(F, Pack2), {NewT, _Pack4} = pack_jid_set(T, Pack3), - %% Throw away Pack4 so that if we delete references to - %% Strings or Jids in any of the sets there will be - %% no live references for the GC to find. - S#state{pres_a=NewA, - pres_i=NewI, - pres_f=NewF, - pres_t=NewT}. + S#state{pres_a = NewA, pres_f = NewF, + pres_t = NewT}. pack_jid_set(Set, Pack) -> - Jids = ?SETS:to_list(Set), + Jids = (?SETS):to_list(Set), {PackedJids, NewPack} = pack_jids(Jids, Pack, []), - {?SETS:from_list(PackedJids), NewPack}. + {(?SETS):from_list(PackedJids), NewPack}. pack_jids([], Pack, Acc) -> {Acc, Pack}; -pack_jids([{U,S,R}=Jid | Jids], Pack, Acc) -> +pack_jids([{U, S, R} = Jid | Jids], Pack, Acc) -> case gb_trees:lookup(Jid, Pack) of - {value, PackedJid} -> - pack_jids(Jids, Pack, [PackedJid | Acc]); - none -> - {NewU, Pack1} = pack_string(U, Pack), - {NewS, Pack2} = pack_string(S, Pack1), - {NewR, Pack3} = pack_string(R, Pack2), - NewJid = {NewU, NewS, NewR}, - NewPack = gb_trees:insert(NewJid, NewJid, Pack3), - pack_jids(Jids, NewPack, [NewJid | Acc]) + {value, PackedJid} -> + pack_jids(Jids, Pack, [PackedJid | Acc]); + none -> + {NewU, Pack1} = pack_string(U, Pack), + {NewS, Pack2} = pack_string(S, Pack1), + {NewR, Pack3} = pack_string(R, Pack2), + NewJid = {NewU, NewS, NewR}, + NewPack = gb_trees:insert(NewJid, NewJid, Pack3), + pack_jids(Jids, NewPack, [NewJid | Acc]) end. pack_string(String, Pack) -> case gb_trees:lookup(String, Pack) of - {value, PackedString} -> - {PackedString, Pack}; - none -> - {String, gb_trees:insert(String, String, Pack)} + {value, PackedString} -> {PackedString, Pack}; + none -> {String, gb_trees:insert(String, String, Pack)} end. diff --git a/src/ejabberd_c2s_config.erl b/src/ejabberd_c2s_config.erl index b416edd2b..4dbc48f38 100644 --- a/src/ejabberd_c2s_config.erl +++ b/src/ejabberd_c2s_config.erl @@ -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 -> - []; - C2SFirstListen -> - case lists:keysearch(ejabberd_c2s, 2, C2SFirstListen) of - false -> - []; - {value, {_Port, ejabberd_c2s, Opts}} -> - select_opts_values(Opts) - end + case ejabberd_config:get_local_option(listen, fun(V) -> V end) of + undefined -> []; + C2SFirstListen -> + case lists:keysearch(ejabberd_c2s, 2, C2SFirstListen) of + false -> []; + {value, {_Port, ejabberd_c2s, Opts}} -> + select_opts_values(Opts) + end end. %% Only get access, shaper and max_stanza_size values + select_opts_values(Opts) -> select_opts_values(Opts, []). + select_opts_values([], SelectedValues) -> SelectedValues; -select_opts_values([{access,Value}|Opts], SelectedValues) -> - select_opts_values(Opts, [{access, Value}|SelectedValues]); -select_opts_values([{shaper,Value}|Opts], SelectedValues) -> - select_opts_values(Opts, [{shaper, Value}|SelectedValues]); -select_opts_values([{max_stanza_size,Value}|Opts], SelectedValues) -> - select_opts_values(Opts, [{max_stanza_size, Value}|SelectedValues]); -select_opts_values([_Opt|Opts], SelectedValues) -> +select_opts_values([{access, Value} | Opts], + SelectedValues) -> + select_opts_values(Opts, + [{access, Value} | SelectedValues]); +select_opts_values([{shaper, Value} | Opts], + SelectedValues) -> + select_opts_values(Opts, + [{shaper, Value} | SelectedValues]); +select_opts_values([{max_stanza_size, Value} | Opts], + SelectedValues) -> + select_opts_values(Opts, + [{max_stanza_size, Value} | SelectedValues]); +select_opts_values([_Opt | Opts], SelectedValues) -> select_opts_values(Opts, SelectedValues). - diff --git a/src/ejabberd_captcha.erl b/src/ejabberd_captcha.erl index 319bf8e81..6cf23a493 100644 --- a/src/ejabberd_captcha.erl +++ b/src/ejabberd_captcha.erl @@ -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)), - 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 -> - {ok, Id, [Body, OOB, Captcha, Data]}; - Err -> - {error, Err} - end; - Err -> - Err + {ok, Type, Key, Image} -> + Id = <<(randoms:get_string())/binary>>, + B64Image = jlib:encode_base64((Image)), + JID = jlib:jid_to_string(From), + 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 -> 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 -> - {ok, [Captcha, Data]}; - Err -> - {error, Err} - end; - Err -> - Err + {ok, Type, Key, Image} -> + 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(<>), + 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 -> 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")}], []}, - 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"}], - [ImgEl, - {xmlelement, "br", [], []}, - TextEl, - {xmlelement, "br", [], []}, - IdEl, - KeyEl, - {xmlelement, "br", [], []}, - {xmlelement, "input", [{"type", "submit"}, - {"name", "enter"}, - {"value", "OK"}], []} - ]}, - {FormEl, {ImgEl, TextEl, IdEl, KeyEl}}; - _ -> - captcha_not_found + case lookup_captcha(Id) of + {ok, _} -> + ImgEl = #xmlel{name = <<"img">>, + attrs = + [{<<"src">>, get_url(<>)}], + children = []}, + TextEl = {xmlcdata, ?CAPTCHA_TEXT(Lang)}, + 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, + #xmlel{name = <<"br">>, attrs = [], + children = []}, + TextEl, + #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 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}; - Xdata -> - Fields = jlib:parse_xdata_submit(Xdata), - case catch {proplists:get_value("challenge", Fields), - proplists:get_value("ocr", Fields)} of - {[Id|_], [OCR|_]} -> - ?T(case mnesia:read(captcha, Id, write) of - [#captcha{pid=Pid, args=Args, key=Key, tref=Tref}] -> - mnesia:delete({captcha, Id}), - erlang:cancel_timer(Tref), - if OCR == Key -> - if is_pid(Pid) -> - Pid ! {captcha_succeed, Args}; - true -> - ok - end, - ok; - true -> - if is_pid(Pid) -> - Pid ! {captcha_failed, Args}; - true -> - ok - end, - {error, bad_match} - end; - _ -> - {error, not_found} - end); - _ -> - {error, malformed} - end +-spec process_reply(xmlel()) -> ok | {error, bad_match | not_found | malformed}. + +process_reply(#xmlel{} = El) -> + case xml:get_subtag(El, <<"x">>) of + false -> {error, malformed}; + Xdata -> + Fields = jlib:parse_xdata_submit(Xdata), + case catch {proplists:get_value(<<"challenge">>, + Fields), + proplists:get_value(<<"ocr">>, Fields)} + of + {[Id | _], [OCR | _]} -> + case check_captcha(Id, OCR) of + captcha_valid -> ok; + captcha_non_valid -> {error, bad_match}; + captcha_not_found -> {error, not_found} + end; + _ -> {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]}, - ejabberd_web:make_xhtml([Form]); - captcha_not_found -> - ejabberd_web:error(not_found) + {FormEl, _} when is_tuple(FormEl) -> + Form = #xmlel{name = <<"div">>, + attrs = [{<<"align">>, <<"center">>}], + children = [FormEl]}, + ejabberd_web:make_xhtml([Form]); + 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 create_image(Addr, Key) of - {ok, Type, _, Img} -> - {200, - [{"Content-Type", Type}, - {"Cache-Control", "no-cache"}, - {"Last-Modified", httpd_util:rfc1123_date()}], - Img}; - {error, limit} -> - ejabberd_web:error(not_allowed); - _ -> - ejabberd_web:error(not_found) - end; - _ -> - ejabberd_web:error(not_found) + 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">>, list_to_binary(httpd_util:rfc1123_date())}], + Img}; + {error, limit} -> ejabberd_web:error(not_allowed); + _ -> ejabberd_web:error(not_found) + end; + _ -> 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", [], - [{xmlcdata, - 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_valid -> + Form = #xmlel{name = <<"p">>, attrs = [], + children = + [{xmlcdata, + 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) 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), - {reply, false, State#state{limits = NewLimits}}; - _ -> - NewLimits = treap:insert(Limiter, NowPriority, 1, Limits), - {reply, false, State#state{limits = NewLimits}} + {ok, _, Rate} when Rate >= RateLimit -> + {reply, true, State#state{limits = Limits}}; + {ok, Priority, Rate} -> + NewLimits = treap:insert(Limiter, Priority, Rate + 1, + Limits), + {reply, false, State#state{limits = NewLimits}}; + _ -> + NewLimits = treap:insert(Limiter, NowPriority, 1, + Limits), + {reply, false, State#state{limits = NewLimits}} end; handle_call(_Request, _From, State) -> {reply, bad_request, State}. -handle_cast(_Msg, State) -> - {noreply, State}. +handle_cast(_Msg, State) -> {noreply, State}. handle_info({remove_id, Id}, State) -> ?DEBUG("captcha ~p timed out", [Id]), - _ = ?T(case mnesia:read(captcha, Id, write) of - [#captcha{args=Args, pid=Pid}] -> - if is_pid(Pid) -> - Pid ! {captcha_failed, Args}; - true -> - ok - end, - mnesia:delete({captcha, Id}); - _ -> - ok - end), + case ets:lookup(captcha, Id) of + [#captcha{args = Args, pid = Pid}] -> + if is_pid(Pid) -> Pid ! {captcha_failed, Args}; + true -> ok + end, + ets:delete(captcha, Id); + _ -> ok + end, {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,126 +460,136 @@ 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}; - {error, enodata = Reason} -> - ?ERROR_MSG("Failed to process output from \"~s\". " - "Maybe ImageMagick's Convert program is not installed.", - [Cmd]), - {error, Reason}; - {error, Reason} -> - ?ERROR_MSG("Failed to process an output from \"~s\": ~p", - [Cmd, Reason]), - {error, Reason}; - _ -> - Reason = malformed_image, - ?ERROR_MSG("Failed to process an output from \"~s\": ~p", - [Cmd, Reason]), - {error, Reason} + {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.", + [Cmd]), + {error, Reason}; + {error, Reason} -> + ?ERROR_MSG("Failed to process an output from \"~s\": ~p", + [Cmd, Reason]), + {error, Reason}; + _ -> + Reason = malformed_image, + ?ERROR_MSG("Failed to process an output from \"~s\": ~p", + [Cmd, Reason]), + {error, Reason} 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 - [Host] -> - "http://" ++ Host ++ "/captcha/" ++ Str; - ["http"++_ = TransferProt, Host] -> - TransferProt ++ ":" ++ Host ++ "/captcha/" ++ Str; - [Host, PortString] -> - TransferProt = atom_to_list(get_transfer_protocol(PortString)), - TransferProt ++ "://" ++ Host ++ ":" ++ PortString ++ "/captcha/" ++ Str; - [TransferProt, Host, PortString] -> - TransferProt ++ ":" ++ Host ++ ":" ++ PortString ++ "/captcha/" ++ Str; - _ -> - "http://" ++ ?MYNAME ++ "/captcha/" ++ Str + CaptchaHost = ejabberd_config:get_local_option( + captcha_host, + fun iolist_to_binary/1, + <<"">>), + case str:tokens(CaptchaHost, <<":">>) of + [Host] -> + <<"http://", Host/binary, "/captcha/", Str/binary>>; + [<<"http", _/binary>> = TransferProt, Host] -> + <>; + [Host, PortString] -> + TransferProt = + iolist_to_binary(atom_to_list(get_transfer_protocol(PortString))), + <>; + [TransferProt, Host, PortString] -> + <>; + _ -> + <<"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 -> - true; - (_) -> - false - end, - AllListeners). + AllListeners = ejabberd_config:get_local_option(listen, fun(V) -> V end), + lists:filter(fun ({{Port, _Ip, _Netp}, _Module1, + _Opts1}) + when Port == PortNumber -> + true; + (_) -> 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 - end; - false -> - get_captcha_transfer_protocol(Listeners) + true -> + case lists:member(tls, Opts) of + true -> https; + false -> http + end; + 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,82 +599,97 @@ is_limited(Limiter) -> %% Description: os:cmd/1 replacement %%-------------------------------------------------------------------- -define(CMD_TIMEOUT, 5000). --define(MAX_FILE_SIZE, 64*1024). + +-define(MAX_FILE_SIZE, 64 * 1024). cmd(Cmd) -> Port = open_port({spawn, Cmd}, [stream, eof, binary]), - TRef = erlang:start_timer(?CMD_TIMEOUT, self(), timeout), + TRef = erlang:start_timer(?CMD_TIMEOUT, self(), + timeout), recv_data(Port, TRef, <<>>). recv_data(Port, TRef, Buf) -> receive - {Port, {data, Bytes}} -> - NewBuf = <>, - if size(NewBuf) > ?MAX_FILE_SIZE -> - return(Port, TRef, {error, efbig}); - true -> - recv_data(Port, TRef, NewBuf) - end; - {Port, {data, _}} -> - return(Port, TRef, {error, efbig}); - {Port, eof} when Buf /= <<>> -> - return(Port, TRef, {ok, Buf}); - {Port, eof} -> - return(Port, TRef, {error, enodata}); - {timeout, TRef, _} -> - return(Port, TRef, {error, timeout}) + {Port, {data, Bytes}} -> + NewBuf = <>, + if byte_size(NewBuf) > (?MAX_FILE_SIZE) -> + return(Port, TRef, {error, efbig}); + true -> recv_data(Port, TRef, NewBuf) + end; + {Port, {data, _}} -> return(Port, TRef, {error, efbig}); + {Port, eof} when Buf /= <<>> -> + return(Port, TRef, {ok, Buf}); + {Port, eof} -> return(Port, TRef, {error, enodata}); + {timeout, TRef, _} -> + return(Port, TRef, {error, timeout}) end. return(Port, TRef, Result) -> case erlang:cancel_timer(TRef) of - false -> - receive - {timeout, TRef, _} -> - ok - after 0 -> - ok - end; - _ -> - ok + false -> + 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; - false -> false + Prog when is_binary(Prog) -> true; + false -> false end. check_captcha_setup() -> case is_feature_available() of - true -> - case create_image() of - {ok, _, _, _} -> - ok; - _Err -> - ?CRITICAL_MSG("Captcha is enabled in the option captcha_cmd, " - "but it can't generate images.", []), - throw({error, captcha_cmd_enabled_but_fails}) - end; - false -> - ok + true -> + case create_image() of + {ok, _, _, _} -> ok; + _Err -> + ?CRITICAL_MSG("Captcha is enabled in the option captcha_cmd, " + "but it can't generate images.", + []), + throw({error, captcha_cmd_enabled_but_fails}) + end; + 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; - false -> - {_Key, Priority, _Value} = treap:get_root(Treap), - if - Priority > CleanPriority -> - clean_treap(treap:delete_root(Treap), CleanPriority); - true -> - Treap - end + true -> Treap; + false -> + {_Key, Priority, _Value} = treap:get_root(Treap), + if Priority > CleanPriority -> + clean_treap(treap:delete_root(Treap), CleanPriority); + true -> Treap + end end. now_priority() -> {MSec, Sec, USec} = now(), - -((MSec*1000000 + Sec)*1000000 + USec). + -((MSec * 1000000 + Sec) * 1000000 + USec). diff --git a/src/ejabberd_check.erl b/src/ejabberd_check.erl index ddb2cbdb0..352251806 100644 --- a/src/ejabberd_check.erl +++ b/src/ejabberd_check.erl @@ -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 diff --git a/src/ejabberd_commands.erl b/src/ejabberd_commands.erl index b61ef46de..3b8abf97b 100644 --- a/src/ejabberd_commands.erl +++ b/src/ejabberd_commands.erl @@ -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. diff --git a/src/ejabberd_commands.hrl b/src/ejabberd_commands.hrl index 1ababc8be..116bb7357 100644 --- a/src/ejabberd_commands.hrl +++ b/src/ejabberd_commands.hrl @@ -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. + diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index e2a8633ee..0a4208b7f 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -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]). diff --git a/src/ejabberd_config.hrl b/src/ejabberd_config.hrl index b0fa46aca..bf749dd19 100644 --- a/src/ejabberd_config.hrl +++ b/src/ejabberd_config.hrl @@ -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()}). diff --git a/src/ejabberd_ctl.erl b/src/ejabberd_ctl.erl index 9b41b1463..2b4702176 100644 --- a/src/ejabberd_ctl.erl +++ b/src/ejabberd_ctl.erl @@ -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; @@ -214,30 +217,27 @@ process2(Args, AccessCommands) -> process2(Args, Auth, AccessCommands) -> case try_run_ctp(Args, Auth, AccessCommands) of {String, wrong_command_arguments} - when is_list(String) -> + when is_list(String) -> io:format(lists:flatten(["\n" | String]++["\n"])), [CommandString | _] = Args, process(["help" | [CommandString]]), {lists:flatten(String), ?STATUS_ERROR}; {String, Code} - when is_list(String) and is_integer(Code) -> + when is_list(String) and is_integer(Code) -> {lists:flatten(String), Code}; String - when is_list(String) -> + when is_list(String) -> {lists:flatten(String), ?STATUS_SUCCESS}; Code - when is_integer(Code) -> + when is_integer(Code) -> {"", Code}; Other -> {"Erroneous result: " ++ io_lib:format("~p", [Other]), ?STATUS_ERROR} 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 - true -> - %% This word fits in the last segment - %% If this word ends with "\n", reset column counter - case string:str(Word, "\n") of - 0 -> - join(L, Words, LenLastSeg+LWord+1, [" ", Word | LastSeg], ResSeg); - _ -> - join(L, Words, LWord+1, [" ", Word | LastSeg], ResSeg) - end; - false -> - join(L, Words, LWord, [" ", Word], [lists:reverse(LastSeg) | ResSeg]) - end. +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 -> + {Word, [CurSeg | AllSegs], WordLen} + end, + NewLen = case string:str(Word, "\n") of + 0 -> + NewCurSegLen; + _ -> + 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 -> diff --git a/src/ejabberd_ctl.hrl b/src/ejabberd_ctl.hrl index 27bb2489c..09a5287ee 100644 --- a/src/ejabberd_ctl.hrl +++ b/src/ejabberd_ctl.hrl @@ -20,6 +20,9 @@ %%%---------------------------------------------------------------------- -define(STATUS_SUCCESS, 0). --define(STATUS_ERROR, 1). --define(STATUS_USAGE, 2). --define(STATUS_BADRPC, 3). + +-define(STATUS_ERROR, 1). + +-define(STATUS_USAGE, 2). + +-define(STATUS_BADRPC, 3). diff --git a/src/ejabberd_frontend_socket.erl b/src/ejabberd_frontend_socket.erl index 93df685c7..98f305536 100644 --- a/src/ejabberd_frontend_socket.erl +++ b/src/ejabberd_frontend_socket.erl @@ -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}). @@ -68,30 +69,30 @@ 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 - {value, {_, Size}} -> Size; - _ -> infinity - end, - Receiver = ejabberd_receiver:start(Socket, SockMod, none, MaxStanzaSize), - case SockMod:controlling_process(Socket, Receiver) of - ok -> - ok; - {error, _Reason} -> - SockMod:close(Socket) - end, - supervisor:start_child(ejabberd_frontend_socket_sup, - [Module, SockMod, Socket, Opts, Receiver]); - raw -> - %{ok, Pid} = Module:start({SockMod, Socket}, Opts), - %case SockMod:controlling_process(Socket, Pid) of - % ok -> - % ok; - % {error, _Reason} -> - % SockMod:close(Socket) - %end - todo + xml_stream -> + MaxStanzaSize = case lists:keysearch(max_stanza_size, 1, + Opts) + of + {value, {_, Size}} -> Size; + _ -> infinity + end, + Receiver = ejabberd_receiver:start(Socket, SockMod, + none, MaxStanzaSize), + case SockMod:controlling_process(Socket, Receiver) of + ok -> ok; + {error, _Reason} -> SockMod:close(Socket) + end, + supervisor:start_child(ejabberd_frontend_socket_sup, + [Module, SockMod, Socket, Opts, Receiver]); + raw -> + %{ok, Pid} = Module:start({SockMod, Socket}, Opts), + %case SockMod:controlling_process(Socket, Pid) of + % ok -> + % ok; + % {error, _Reason} -> + % SockMod:close(Socket) + %end + todo end. starttls(FsmRef, _TLSOpts) -> @@ -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) - end, + 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 diff --git a/src/ejabberd_hooks.erl b/src/ejabberd_hooks.erl index 26573aa7d..e4f9f597b 100644 --- a/src/ejabberd_hooks.erl +++ b/src/ejabberd_hooks.erl @@ -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}] -> diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl index 78f1691e1..9df8678b0 100644 --- a/src/ejabberd_listener.erl +++ b/src/ejabberd_listener.erl @@ -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)). diff --git a/src/ejabberd_local.erl b/src/ejabberd_local.erl index 1fe7cb0a4..12dfea0c8 100644 --- a/src/ejabberd_local.erl +++ b/src/ejabberd_local.erl @@ -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,65 +68,59 @@ %% 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), case IQ of - #iq{xmlns = XMLNS} -> - Host = To#jid.lserver, - 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 - 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), - ejabberd_router:route(To, From, Err) - end; - reply -> - IQReply = jlib:iq_query_or_response_info(Packet), - process_iq_reply(From, To, IQReply); - _ -> - Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST), - ejabberd_router:route(To, From, Err), - ok + #iq{xmlns = XMLNS} -> + Host = To#jid.lserver, + 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 + 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), + ejabberd_router:route(To, From, Err) + end; + reply -> + IQReply = jlib:iq_query_or_response_info(Packet), + process_iq_reply(From, To, IQReply); + _ -> + Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST), + ejabberd_router:route(To, From, Err), + ok end. process_iq_reply(From, To, #iq{id = ID} = IQ) -> case get_iq_callback(ID) of - {ok, undefined, Function} -> - Function(IQ), - ok; - {ok, Module, Function} -> - Module:Function(From, To, IQ), - ok; - _ -> - nothing + {ok, undefined, Function} -> Function(IQ), ok; + {ok, Module, Function} -> + Module:Function(From, To, IQ), ok; + _ -> nothing end. 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 + {'EXIT', Reason} -> + ?ERROR_MSG("~p~nwhen processing: ~p", + [Reason, {From, To, Packet}]); + _ -> 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}), - ejabberd_hooks:add(local_send_to_resource_hook, Host, - ?MODULE, bounce_resource_packet, 100) - end, ?MYHOSTS), + 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), 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 + {'EXIT', Reason} -> + ?ERROR_MSG("~p~nwhen processing: ~p", + [Reason, {From, To, Packet}]); + _ -> 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 + [{_, Module, Function, Opts}] -> + gen_iq_handler:stop_iq_handler(Module, Function, Opts); + _ -> 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) -> - 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 - end - end, ets:tab2list(?IQTABLE)), + 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 + end + 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,48 +279,43 @@ 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 /= "" -> - ejabberd_sm:route(From, To, Packet); - To#jid.lresource == "" -> - {xmlelement, Name, _Attrs, _Els} = Packet, - case Name of - "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; - _ -> - ejabberd_hooks:run(local_send_to_resource_hook, - To#jid.lserver, - [From, To, Packet]) - end - end. + if To#jid.luser /= <<"">> -> + ejabberd_sm:route(From, To, Packet); + To#jid.lresource == <<"">> -> + #xmlel{name = Name} = Packet, + case Name of + <<"iq">> -> process_iq(From, To, Packet); + <<"message">> -> ok; + <<"presence">> -> ok; + _ -> ok + end; + true -> + #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]) + end + end. update_table() -> case catch mnesia:table_info(iq_response, attributes) of @@ -365,13 +356,7 @@ process_iq_timeout() -> cancel_timer(TRef) -> case erlang:cancel_timer(TRef) of - false -> - receive - {timeout, TRef, _} -> - ok - after 0 -> - ok - end; - _ -> - ok + false -> + receive {timeout, TRef, _} -> ok after 0 -> ok end; + _ -> ok end. diff --git a/src/ejabberd_node_groups.erl b/src/ejabberd_node_groups.erl index 84c1d69ca..371a1bc28 100644 --- a/src/ejabberd_node_groups.erl +++ b/src/ejabberd_node_groups.erl @@ -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 -> diff --git a/src/ejabberd_piefxis.erl b/src/ejabberd_piefxis.erl index b193dc67c..668f1ba62 100644 --- a/src/ejabberd_piefxis.erl +++ b/src/ejabberd_piefxis.erl @@ -3,6 +3,10 @@ %%% Author : Pablo Polvorin, Vidal Santiago Martinez %%% Purpose : XEP-0227: Portable Import/Export Format for XMPP-IM Servers %%% Created : 17 Jul 2008 by Pablo Polvorin +%%%------------------------------------------------------------------- +%%% @author Evgeniy Khramtsov +%%% @copyright (C) 2012, Evgeniy Khramtsov +%%% @doc %%% %%% %%% ejabberd, Copyright (C) 2002-2013 ProcessOne @@ -31,239 +35,85 @@ %%% - XEP-227: 6. Security Considerations %%% - Other schemas of XInclude are not tested, and may not be imported correctly. %%% - If a host has many users, split that host in XML files with 50 users each. - %%%% Headers -module(ejabberd_piefxis). +%% API -export([import_file/1, export_server/1, export_host/2]). --record(parsing_state, {parser, host, dir}). +-define(CHUNK_SIZE, 1024*20). %20k -include("ejabberd.hrl"). +-include("jlib.hrl"). +-include("mod_privacy.hrl"). +-include("mod_roster.hrl"). %%-include_lib("exmpp/include/exmpp.hrl"). %%-include_lib("exmpp/include/exmpp_client.hrl"). %% Copied from exmpp header files: --define(NS_ROSTER, "jabber:iq:roster"). --define(NS_VCARD, "vcard-temp"). --record(xmlcdata, { - cdata = <<>> - }). --record(xmlattr, { - ns = undefined, - name, - value - }). --record(xmlel, { - ns = undefined, - declared_ns = [], - name, - attrs = [], - children = [] - }). --record(iq, { - kind, - type, - id, - ns, - payload, - error, - lang, - iq_ns - }). --record(xmlendtag, { - ns = undefined, - name - }). - - %% Copied from mod_private.erl --record(private_storage, {usns, xml}). - %%-define(ERROR_MSG(M,Args),io:format(M,Args)). %%-define(INFO_MSG(M,Args),ok). - --define(CHUNK_SIZE,1024*20). %20k - --define(BTL, binary_to_list). --define(LTB, list_to_binary). - --define(NS_XINCLUDE, 'http://www.w3.org/2001/XInclude'). - %%%================================== - %%%% Import file +-define(NS_PIE, <<"urn:xmpp:pie:0">>). +-define(NS_PIEFXIS, <<"http://www.xmpp.org/extensions/xep-0227.html#ns">>). +-define(NS_XI, <<"http://www.w3.org/2001/XInclude">>). -import_file(FileName) -> - _ = #xmlattr{}, %% this stupid line is only to prevent compilation warning about "recod xmlattr is unused" - import_file(FileName, 2). +-record(state, {xml_stream_state :: xml_stream:xml_stream_state(), + user = <<"">> :: binary(), + server = <<"">> :: binary(), + fd :: file:io_device(), + dir = <<"">> :: binary()}). -import_file(FileName, RootDepth) -> - try_start_exmpp(), - Dir = filename:dirname(FileName), - {ok, IO} = try_open_file(FileName), - Parser = exmpp_xml:start_parser([{max_size,infinity}, - {root_depth, RootDepth}, - {emit_endtag,true}]), - read_chunks(IO, #parsing_state{parser=Parser, dir=Dir}), - file:close(IO), - exmpp_xml:stop_parser(Parser). - -try_start_exmpp() -> - try exmpp:start() - catch - error:{already_started, exmpp} -> ok; - error:undef -> throw({error, exmpp_not_installed}) - end. - -try_open_file(FileName) -> - case file:open(FileName,[read,binary]) of - {ok, IO} -> {ok, IO}; - {error, enoent} -> throw({error, {file_not_found, FileName}}) - end. +-type state() :: #state{}. %%File could be large.. we read it in chunks -read_chunks(IO,State) -> - case file:read(IO,?CHUNK_SIZE) of - {ok,Chunk} -> - NewState = process_chunk(Chunk,State), - read_chunks(IO,NewState); - eof -> - ok - end. +%%%=================================================================== +%%% API +%%%=================================================================== +import_file(FileName) -> + import_file(FileName, #state{}). -process_chunk(Chunk,S =#parsing_state{parser=Parser}) -> - case exmpp_xml:parse(Parser,Chunk) of - continue -> - S; - XMLElements -> - process_elements(XMLElements,S) +-spec import_file(binary(), state()) -> ok | {error, atom()}. + +import_file(FileName, State) -> + case file:open(FileName, [read, binary]) of + {ok, Fd} -> + Dir = filename:dirname(FileName), + XMLStreamState = xml_stream:new(self(), infinity), + Res = process(State#state{xml_stream_state = XMLStreamState, + fd = Fd, + dir = Dir}), + file:close(Fd), + Res; + {error, Reason} -> + ErrTxt = file:format_error(Reason), + ?ERROR_MSG("Failed to open file '~s': ~s", [FileName, ErrTxt]), + {error, Reason} end. %%%================================== %%%% Process Elements - -process_elements(Elements,State) -> - lists:foldl(fun process_element/2,State,Elements). - %%%================================== %%%% Process Element - -process_element(El=#xmlel{name=user, ns=_XMLNS}, - State=#parsing_state{host=Host}) -> - case add_user(El,Host) of - ok -> ok; - {error, _Other} -> error - end, - State; - -process_element(H=#xmlel{name=host},State) -> - State#parsing_state{host=?BTL(exmpp_xml:get_attribute(H, <<"jid">>, none))}; - -process_element(#xmlel{name='server-data'},State) -> - State; - -process_element(El=#xmlel{name=include, ns=?NS_XINCLUDE}, State=#parsing_state{dir=Dir}) -> - case exmpp_xml:get_attribute(El, <<"href">>, none) of - none -> - ok; - HrefB -> - Href = binary_to_list(HrefB), - %%?INFO_MSG("Parse also this file: ~n~p", [Href]), - FileName = filename:join([Dir, Href]), - import_file(FileName, 1), - Href - end, - State; - -process_element(#xmlcdata{cdata = _CData},State) -> - State; - -process_element(#xmlendtag{ns = _NS, name='server-data'},State) -> - State; - -process_element(#xmlendtag{ns = _NS, name=_Name},State) -> - State; - -process_element(El,State) -> - io:format("Warning!: unknown element found: ~p ~n",[El]), - State. - %%%================================== %%%% Add user - -add_user(El, Domain) -> - User = exmpp_xml:get_attribute(El, <<"name">>, none), - PasswordFormat = exmpp_xml:get_attribute(El, <<"password-format">>, <<"plaintext">>), - Password = exmpp_xml:get_attribute(El, <<"password">>, none), - add_user(El, Domain, User, PasswordFormat, Password). - %% @spec (El::xmlel(), Domain::string(), User::binary(), Password::binary() | none) %% -> ok | {error, ErrorText::string()} %% @doc Add a new user to the database. %% If user already exists, it will be only updated. -add_user(El, Domain, UserBinary, <<"plaintext">>, none) -> - User = ?BTL(UserBinary), - io:format("Account ~s@~s will not be created, updating it...~n", - [User, Domain]), - io:format(""), - populate_user_with_elements(El, Domain, User), - ok; -add_user(El, Domain, UserBinary, PasswordFormat, PasswordBinary) -> - User = ?BTL(UserBinary), - Password2 = prepare_password(PasswordFormat, PasswordBinary, El), - case create_user(User,Password2,Domain) of - ok -> - populate_user_with_elements(El, Domain, User), - ok; - {atomic, exists} -> - io:format("Account ~s@~s already exists, updating it...~n", - [User, Domain]), - io:format(""), - populate_user_with_elements(El, Domain, User), - ok; - {error, Other} -> - ?ERROR_MSG("Error adding user ~s@~s: ~p~n", [User, Domain, Other]), - {error, Other} - end. - -prepare_password(<<"plaintext">>, PasswordBinary, _El) -> - ?BTL(PasswordBinary); -prepare_password(<<"scram">>, none, El) -> - ScramEl = exmpp_xml:get_element(El, 'scram-hash'), - #scram{storedkey = base64:decode(exmpp_xml:get_attribute( - ScramEl, <<"stored-key">>, none)), - serverkey = base64:decode(exmpp_xml:get_attribute( - ScramEl, <<"server-key">>, none)), - salt = base64:decode(exmpp_xml:get_attribute( - ScramEl, <<"salt">>, none)), - iterationcount = list_to_integer(exmpp_xml:get_attribute_as_list( - ScramEl, <<"iteration-count">>, - ?SCRAM_DEFAULT_ITERATION_COUNT)) - }. - -populate_user_with_elements(El, Domain, User) -> - exmpp_xml:foreach( - fun (_,Child) -> - populate_user(User,Domain,Child) - end, - El). +-spec export_server(binary()) -> any(). %% @spec (User::string(), Password::string(), Domain::string()) %% -> ok | {atomic, exists} | {error, not_allowed} %% @doc Create a new user -create_user(User,Password,Domain) -> - case ejabberd_auth:try_register(User,Domain,Password) of - {atomic,ok} -> ok; - {atomic, exists} -> {atomic, exists}; - {error, not_allowed} -> {error, not_allowed}; - Other -> {error, Other} - end. +export_server(Dir) -> + export_hosts(?MYHOSTS, Dir). %%%================================== %%%% Populate user - %% @spec (User::string(), Domain::string(), El::xml()) %% -> ok | {error, not_found} %% @@ -286,28 +136,10 @@ create_user(User,Password,Domain) -> %% %% %% ''' +-spec export_host(binary(), binary()) -> any(). -populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:roster'}) -> - io:format("Trying to add/update roster list...",[]), - case loaded_module(Domain, mod_roster) of - {ok, _DBType} -> - case mod_roster:set_items(User, Domain, - exmpp_xml:xmlel_to_xmlelement(El)) of - {atomic, ok} -> - io:format(" DONE.~n",[]), - ok; - _ -> - io:format(" ERROR.~n",[]), - ?ERROR_MSG("Error trying to add a new user: ~s ~n", - [exmpp_xml:document_to_list(El)]), - {error, not_found} - end; - E -> io:format(" ERROR: ~p~n",[E]), - ?ERROR_MSG("No modules loaded [mod_roster] ~s ~n", - [exmpp_xml:document_to_list(El)]), - {error, not_found} - end; - +export_host(Dir, Host) -> + export_hosts([Host], Dir). %% @spec User = String with the user name %% Domain = String with a domain name @@ -328,180 +160,471 @@ populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:roster'}) -> %% %% %% ''' - -populate_user(User,Domain,El=#xmlel{name='vCard', ns='vcard-temp'}) -> - io:format("Trying to add/update vCards...",[]), - case loaded_module(Domain, mod_vcard) of - {ok, _} -> FullUser = jid_to_old_jid(exmpp_jid:make(User, Domain)), - IQ = iq_to_old_iq(#iq{type = set, payload = El}), - case mod_vcard:process_sm_iq(FullUser, FullUser , IQ) of - {error,_Err} -> - io:format(" ERROR.~n",[]), - ?ERROR_MSG("Error processing vcard ~s : ~p ~n", - [exmpp_xml:document_to_list(El), _Err]); - _ -> - io:format(" DONE.~n",[]), ok - end; - _ -> - io:format(" ERROR.~n",[]), - ?ERROR_MSG("No modules loaded [mod_vcard] ~s ~n", - [exmpp_xml:document_to_list(El)]), - {error, not_found} - end; +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +export_hosts(Hosts, Dir) -> + FnT = make_filename_template(), + DFn = make_main_basefilename(Dir, FnT), + case file:open(DFn, [raw, write]) of + {ok, Fd} -> + print(Fd, make_piefxis_xml_head()), + print(Fd, make_piefxis_server_head()), + FilesAndHosts = [{make_host_filename(FnT, Host), Host} + || Host <- Hosts], + lists:foreach( + fun({FnH, _}) -> + print(Fd, make_xinclude(FnH)) + end, FilesAndHosts), + print(Fd, make_piefxis_server_tail()), + print(Fd, make_piefxis_xml_tail()), + file:close(Fd), + lists:foldl( + fun({FnH, Host}, ok) -> + export_host(Dir, FnH, Host); + (_, Err) -> + Err + end, ok, FilesAndHosts); + {error, Reason} -> + ErrTxt = file:format_error(Reason), + ?ERROR_MSG("Failed to open file '~s': ~s", [DFn, ErrTxt]), + {error, Reason} + end. %% @spec User = String with the user name %% Domain = String with a domain name %% El = Sub XML element with offline messages values %% @ret ok | {error, not_found} %% @doc Read off-line message from the XML and send it to the server - -populate_user(User,Domain,El=#xmlel{name='offline-messages'}) -> - io:format("Trying to add/update offline-messages...",[]), - case loaded_module(Domain, mod_offline) of - {ok, _DBType} -> - ok = exmpp_xml:foreach( - fun (_Element, {xmlcdata, _}) -> - ok; - (_Element, Child) -> - From = exmpp_xml:get_attribute(Child, <<"from">>,none), - FullFrom = jid_to_old_jid(exmpp_jid:parse(From)), - FullUser = jid_to_old_jid(exmpp_jid:make(User, - Domain)), - OldChild = exmpp_xml:xmlel_to_xmlelement(Child), - _R = mod_offline:store_packet(FullFrom, FullUser, OldChild) - end, El), io:format(" DONE.~n",[]); - _ -> - io:format(" ERROR.~n",[]), - ?ERROR_MSG("No modules loaded [mod_offline] ~s ~n", - [exmpp_xml:document_to_list(El)]), - {error, not_found} - end; +export_host(Dir, FnH, Host) -> + DFn = make_host_basefilename(Dir, FnH), + case file:open(DFn, [raw, write]) of + {ok, Fd} -> + print(Fd, make_piefxis_xml_head()), + print(Fd, make_piefxis_host_head(Host)), + Users = ejabberd_auth:get_vh_registered_users(Host), + case export_users(Users, Host, Fd) of + ok -> + print(Fd, make_piefxis_host_tail()), + print(Fd, make_piefxis_xml_tail()), + file:close(Fd), + ok; + Err -> + file:close(Fd), + file:delete(DFn), + Err + end; + {error, Reason} -> + ErrTxt = file:format_error(Reason), + ?ERROR_MSG("Failed to open file '~s': ~s", [DFn, ErrTxt]), + {error, Reason} + end. %% @spec User = String with the user name %% Domain = String with a domain name %% El = Sub XML element with private storage values %% @ret ok | {error, not_found} %% @doc Private storage parsing - -populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:private'}) -> - io:format("Trying to add/update private storage...",[]), - case loaded_module(Domain, mod_private) of - {ok, _DBType} -> - FullUser = jid_to_old_jid(exmpp_jid:make(User, Domain)), - IQ = iq_to_old_iq(#iq{type = set, - ns = 'jabber:iq:private', - kind = request, - iq_ns = 'jabberd:client', - payload = El}), - case mod_private:process_sm_iq(FullUser, FullUser, IQ ) of - {error, _Err} -> - io:format(" ERROR.~n",[]), - ?ERROR_MSG("Error processing private storage ~s : ~p ~n", - [exmpp_xml:document_to_list(El), _Err]); - _ -> io:format(" DONE.~n",[]), ok - end; - _ -> - io:format(" ERROR.~n",[]), - ?ERROR_MSG("No modules loaded [mod_private] ~s ~n", - [exmpp_xml:document_to_list(El)]), - {error, not_found} +export_users([{User, _S}|Users], Server, Fd) -> + case export_user(User, Server, Fd) of + ok -> + export_users(Users, Server, Fd); + Err -> + Err end; - -populate_user(_User, _Domain, #xmlcdata{cdata = _CData}) -> - ok; - -populate_user(_User, _Domain, _El) -> +export_users([], _Server, _Fd) -> ok. %%%================================== %%%% Utilities +export_user(User, Server, Fd) -> + Pass = ejabberd_auth:get_password_s(User, Server), + Els = get_offline(User, Server) ++ + get_vcard(User, Server) ++ + get_privacy(User, Server) ++ + get_roster(User, Server) ++ + get_private(User, Server), + print(Fd, xml:element_to_binary( + #xmlel{name = <<"user">>, + attrs = [{<<"name">>, User}, + {<<"password">>, Pass}], + children = Els})). -loaded_module(Domain, Module) -> - case gen_mod:is_loaded(Domain, Module) of - true -> - {ok, gen_mod:db_type(Domain, Module)}; - false -> - {error, not_found} +get_vcard(User, Server) -> + JID = jlib:make_jid(User, Server, <<>>), + case mod_vcard:process_sm_iq(JID, JID, #iq{type = get}) of + #iq{type = result, sub_el = [_|_] = VCardEls} -> + VCardEls; + _ -> + [] end. -jid_to_old_jid(Jid) -> - {jid, to_list(exmpp_jid:node_as_list(Jid)), - to_list(exmpp_jid:domain_as_list(Jid)), - to_list(exmpp_jid:resource_as_list(Jid)), - to_list(exmpp_jid:prep_node_as_list(Jid)), - to_list(exmpp_jid:prep_domain_as_list(Jid)), - to_list(exmpp_jid:prep_resource_as_list(Jid))}. - -iq_to_old_iq(#iq{id = ID, type = Type, lang = Lang, ns= NS, payload = El }) -> - {iq, to_list(ID), Type, to_list(NS), to_list(Lang), - exmpp_xml:xmlel_to_xmlelement(El)}. - -to_list(L) when is_list(L) -> L; -to_list(B) when is_binary(B) -> binary_to_list(B); -to_list(undefined) -> ""; -to_list(B) when is_atom(B) -> atom_to_list(B). - %%%================================== +get_offline(User, Server) -> + case mod_offline:get_offline_els(User, Server) of + [] -> + []; + Els -> + NewEls = lists:map( + fun(#xmlel{attrs = Attrs} = El) -> + NewAttrs = lists:keystore(<<"xmlns">>, 1, + Attrs, + {<<"xmlns">>, + <<"jabber:client">>}), + El#xmlel{attrs = NewAttrs} + end, Els), + [#xmlel{name = <<"offline-messages">>, children = NewEls}] + end. %%%% Export hosts +get_privacy(User, Server) -> + case mod_privacy:get_user_lists(User, Server) of + {ok, #privacy{default = Default, + lists = [_|_] = Lists}} -> + XLists = lists:map( + fun({Name, Items}) -> + XItems = lists:map( + fun mod_privacy:item_to_xml/1, Items), + #xmlel{name = <<"list">>, + attrs = [{<<"name">>, Name}], + children = XItems} + end, Lists), + DefaultEl = case Default of + none -> + []; + _ -> + [#xmlel{name = <<"default">>, + attrs = [{<<"name">>, Default}]}] + end, + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, ?NS_PRIVACY}], + children = DefaultEl ++ XLists}]; + _ -> + [] + end. %% @spec (Dir::string(), Hosts::[string()]) -> ok -export_hosts(Dir, Hosts) -> - try_start_exmpp(), +get_roster(User, Server) -> + JID = jlib:make_jid(User, Server, <<>>), + case mod_roster:get_roster(User, Server) of + [_|_] = Items -> + Subs = + lists:flatmap( + fun(#roster{ask = Ask, + askmessage = Msg} = R) + when Ask == in; Ask == both -> + Status = if is_binary(Msg) -> (Msg); + true -> <<"">> + end, + [#xmlel{name = <<"presence">>, + attrs = + [{<<"from">>, + jlib:jid_to_string(R#roster.jid)}, + {<<"to">>, jlib:jid_to_string(JID)}, + {<<"xmlns">>, <<"jabber:client">>}, + {<<"type">>, <<"subscribe">>}], + children = + [#xmlel{name = <<"status">>, + attrs = [], + children = + [{xmlcdata, Status}]}]}]; + (_) -> + [] + end, Items), + Rs = lists:flatmap( + fun(#roster{ask = in, subscription = none}) -> + []; + (R) -> + [mod_roster:item_to_xml(R)] + end, Items), + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, ?NS_ROSTER}], + children = Rs} | Subs]; + _ -> + [] + end. - FnT = make_filename_template(), - DFn = make_main_basefilename(Dir, FnT), +get_private(User, Server) -> + case mod_private:get_data(User, Server) of + [_|_] = Els -> + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, ?NS_PRIVATE}], + children = Els}]; + _ -> + [] + end. - {ok, Fd} = file_open(DFn), - print(Fd, make_piefxis_xml_head()), - print(Fd, make_piefxis_server_head()), +process(#state{xml_stream_state = XMLStreamState, fd = Fd} = State) -> + case file:read(Fd, ?CHUNK_SIZE) of + {ok, Data} -> + NewXMLStreamState = xml_stream:parse(XMLStreamState, Data), + case process_els(State#state{xml_stream_state = + NewXMLStreamState}) of + {ok, NewState} -> + process(NewState); + Err -> + xml_stream:close(NewXMLStreamState), + Err + end; + eof -> + xml_stream:close(XMLStreamState), + ok + end. - FilesAndHosts = [{make_host_filename(FnT, Host), Host} || Host <- Hosts], - [print(Fd, make_xinclude(FnH)) || {FnH, _Host} <- FilesAndHosts], +process_els(State) -> + receive + {'$gen_event', El} -> + case process_el(El, State) of + {ok, NewState} -> + process_els(NewState); + Err -> + Err + end + after 0 -> + {ok, State} + end. - print(Fd, make_piefxis_server_tail()), - print(Fd, make_piefxis_xml_tail()), - file_close(Fd), +process_el({xmlstreamstart, <<"server-data">>, Attrs}, State) -> + case xml:get_attr_s(<<"xmlns">>, Attrs) of + ?NS_PIEFXIS -> + {ok, State}; + ?NS_PIE -> + {ok, State}; + NS -> + stop("Unknown 'server-data' namespace = ~s", [NS]) + end; +process_el({xmlstreamend, _}, State) -> + {ok, State}; +process_el({xmlstreamcdata, _}, State) -> + {ok, State}; +process_el({xmlstreamelement, #xmlel{name = <<"xi:include">>, + attrs = Attrs}}, + #state{dir = Dir, user = <<"">>} = State) -> + FileName = xml:get_attr_s(<<"href">>, Attrs), + case import_file(filename:join([Dir, FileName]), State) of + ok -> + {ok, State}; + Err -> + Err + end; +process_el({xmlstreamstart, <<"host">>, Attrs}, State) -> + process_el({xmlstreamelement, #xmlel{name = <<"host">>, + attrs = Attrs}}, State); +process_el({xmlstreamelement, #xmlel{name = <<"host">>, + attrs = Attrs, + children = Els}}, State) -> + JIDS = xml:get_attr_s(<<"jid">>, Attrs), + case jlib:string_to_jid(JIDS) of + #jid{lserver = S} -> + case lists:member(S, ?MYHOSTS) of + true -> + process_users(Els, State#state{server = S}); + false -> + stop("Unknown host: ~s", [S]) + end; + error -> + stop("Invalid 'jid': ~s", [JIDS]) + end; +process_el({xmlstreamstart, <<"user">>, Attrs}, State = #state{server = S}) + when S /= <<"">> -> + process_el({xmlstreamelement, #xmlel{name = <<"user">>, attrs = Attrs}}, + State); +process_el({xmlstreamelement, #xmlel{name = <<"user">>} = El}, + State = #state{server = S}) when S /= <<"">> -> + process_user(El, State); +process_el({xmlstreamelement, El}, State = #state{server = S, user = U}) + when S /= <<"">>, U /= <<"">> -> + process_user_el(El, State); +process_el({xmlstreamelement, El}, _State) -> + stop("Unexpected tag: ~p", [El]); +process_el({xmlstreamstart, El, Attrs}, _State) -> + stop("Unexpected payload: ~p", [{El, Attrs}]); +process_el({xmlstreamerror, Err}, _State) -> + stop("Failed to process element = ~p", [Err]). - [export_host(Dir, FnH, Host) || {FnH, Host} <- FilesAndHosts], +process_users([#xmlel{} = El|Els], State) -> + case process_user(El, State) of + {ok, NewState} -> + process_users(Els, NewState); + Err -> + Err + end; +process_users([_|Els], State) -> + process_users(Els, State); +process_users([], State) -> + {ok, State}. - ok. +process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els}, + #state{server = LServer} = State) -> + Name = xml:get_attr_s(<<"name">>, Attrs), + Pass = xml:get_attr_s(<<"password">>, Attrs), + case jlib:nodeprep(Name) of + error -> + stop("Invalid 'user': ~s", [Name]); + LUser -> + case ejabberd_auth:try_register(LUser, LServer, Pass) of + {atomic, _} -> + process_user_els(Els, State#state{user = LUser}); + Err -> + stop("Failed to create user '~s': ~p", [Name, Err]) + end + end. + +process_user_els([#xmlel{} = El|Els], State) -> + case process_user_el(El, State) of + {ok, NewState} -> + process_user_els(Els, NewState); + Err -> + Err + end; +process_user_els([_|Els], State) -> + process_user_els(Els, State); +process_user_els([], State) -> + {ok, State}. + +process_user_el(#xmlel{name = Name, attrs = Attrs, children = Els} = El, + State) -> + case {Name, xml:get_attr_s(<<"xmlns">>, Attrs)} of + {<<"query">>, ?NS_ROSTER} -> + process_roster(El, State); + {<<"query">>, ?NS_PRIVACY} -> + %% Make sure elements go before and + NewEls = lists:reverse(lists:keysort(#xmlel.name, Els)), + process_privacy_el(El#xmlel{children = NewEls}, State); + {<<"query">>, ?NS_PRIVATE} -> + process_private(El, State); + {<<"vCard">>, ?NS_VCARD} -> + process_vcard(El, State); + {<<"offline-messages">>, _} -> + process_offline_msgs(Els, State); + {<<"presence">>, <<"jabber:client">>} -> + process_presence(El, State); + _ -> + {ok, State} + end. + +process_privacy_el(#xmlel{children = [#xmlel{} = SubEl|SubEls]} = El, State) -> + case process_privacy(#xmlel{children = [SubEl]}, State) of + {ok, NewState} -> + process_privacy_el(El#xmlel{children = SubEls}, NewState); + Err -> + Err + end; +process_privacy_el(#xmlel{children = [_|SubEls]} = El, State) -> + process_privacy_el(El#xmlel{children = SubEls}, State); +process_privacy_el(#xmlel{children = []}, State) -> + {ok, State}. + +process_offline_msgs([#xmlel{} = El|Els], State) -> + case process_offline_msg(El, State) of + {ok, NewState} -> + process_offline_msgs(Els, NewState); + Err -> + Err + end; +process_offline_msgs([_|Els], State) -> + process_offline_msgs(Els, State); +process_offline_msgs([], State) -> + {ok, State}. + +process_roster(El, State = #state{user = U, server = S}) -> + case mod_roster:set_items(U, S, El) of + {atomic, _} -> + {ok, State}; + Err -> + stop("Failed to write roster: ~p", [Err]) + end. %%%================================== %%%% Export server +process_privacy(El, State = #state{user = U, server = S}) -> + JID = jlib:make_jid(U, S, <<"">>), + case mod_privacy:process_iq_set( + [], JID, JID, #iq{type = set, sub_el = El}) of + {error, _} = Err -> + stop("Failed to write privacy: ~p", [Err]); + _ -> + {ok, State} + end. %% @spec (Dir::string()) -> ok -export_server(Dir) -> - Hosts = ?MYHOSTS, - export_hosts(Dir, Hosts). +process_private(El, State = #state{user = U, server = S}) -> + JID = jlib:make_jid(U, S, <<"">>), + case mod_private:process_sm_iq( + JID, JID, #iq{type = set, sub_el = El}) of + #iq{type = result} -> + {ok, State}; + Err -> + stop("Failed to write private: ~p", [Err]) + end. %%%================================== %%%% Export host +process_vcard(El, State = #state{user = U, server = S}) -> + JID = jlib:make_jid(U, S, <<"">>), + case mod_vcard:process_sm_iq( + JID, JID, #iq{type = set, sub_el = El}) of + #iq{type = result} -> + {ok, State}; + Err -> + stop("Failed to write vcard: ~p", [Err]) + end. %% @spec (Dir::string(), Host::string()) -> ok -export_host(Dir, Host) -> - Hosts = [Host], - export_hosts(Dir, Hosts). +process_offline_msg(El, State = #state{user = U, server = S}) -> + FromS = xml:get_attr_s(<<"from">>, El#xmlel.attrs), + case jlib:string_to_jid(FromS) of + #jid{} = From -> + To = jlib:make_jid(U, S, <<>>), + NewEl = jlib:replace_from_to(From, To, El), + case catch mod_offline:store_packet(From, To, NewEl) of + {'EXIT', _} = Err -> + stop("Failed to store offline message: ~p", [Err]); + _ -> + {ok, State} + end; + _ -> + stop("Invalid 'from' = ~s", [FromS]) + end. %% @spec (Dir::string(), Fn::string(), Host::string()) -> ok -export_host(Dir, FnH, Host) -> +process_presence(El, #state{user = U, server = S} = State) -> + FromS = xml:get_attr_s(<<"from">>, El#xmlel.attrs), + case jlib:string_to_jid(FromS) of + #jid{} = From -> + To = jlib:make_jid(U, S, <<>>), + NewEl = jlib:replace_from_to(From, To, El), + ejabberd_router:route(From, To, NewEl), + {ok, State}; + _ -> + stop("Invalid 'from' = ~s", [FromS]) + end. - DFn = make_host_basefilename(Dir, FnH), +stop(Fmt, Args) -> + ?ERROR_MSG(Fmt, Args), + {error, import_failed}. - {ok, Fd} = file_open(DFn), - print(Fd, make_piefxis_xml_head()), - print(Fd, make_piefxis_host_head(Host)), +make_filename_template() -> + {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(), + list_to_binary( + io_lib:format("~4..0w~2..0w~2..0w-~2..0w~2..0w~2..0w", + [Year, Month, Day, Hour, Minute, Second])). - Users = ejabberd_auth:get_vh_registered_users(Host), - [export_user(Fd, Username, Host) || {Username, _Host} <- Users], - timer:sleep(500), % Delay to ensure ERROR_MSG are displayed in the shell +make_main_basefilename(Dir, FnT) -> + Filename2 = <>, + filename:join([Dir, Filename2]). - print(Fd, make_piefxis_host_tail()), - print(Fd, make_piefxis_xml_tail()), - file_close(Fd). +%% @doc Make the filename for the host. +%% Example: ``(<<"20080804-231550">>, <<"jabber.example.org">>) -> +%% <<"20080804-231550_jabber_example_org.xml">>'' +make_host_filename(FnT, Host) -> + Host2 = str:join(str:tokens(Host, <<".">>), <<"_">>), + <>. %%%================================== %%%% PIEFXIS formatting +make_host_basefilename(Dir, FnT) -> + filename:join([Dir, FnT]). %% @spec () -> string() make_piefxis_xml_head() -> @@ -513,9 +636,8 @@ make_piefxis_xml_tail() -> %% @spec () -> string() make_piefxis_server_head() -> - "". + io_lib:format("", + [?NS_PIE, ?NS_XI]). %% @spec () -> string() make_piefxis_server_tail() -> @@ -523,10 +645,8 @@ make_piefxis_server_tail() -> %% @spec (Host::string()) -> string() make_piefxis_host_head(Host) -> - NSString = - " xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'" - " xmlns:xi='http://www.w3.org/2001/XInclude'", - io_lib:format("", [NSString, Host]). + io_lib:format("", + [?NS_PIE, ?NS_XI, Host]). %% @spec () -> string() make_piefxis_host_tail() -> @@ -539,196 +659,26 @@ make_xinclude(Fn) -> %%%================================== %%%% Export user - %% @spec (Fd, Username::string(), Host::string()) -> ok %% @doc Extract user information and print it. -export_user(Fd, Username, Host) -> - try extract_user(Username, Host) of - UserString -> - print(Fd, UserString) - catch - E1:E2 -> - ?ERROR_MSG("The account ~s@~s is not exported because a problem " - "was found in it:~n~p: ~p", [Username, Host, E1, E2]) - end. - %% @spec (Username::string(), Host::string()) -> string() -extract_user(Username, Host) -> - Password = ejabberd_auth:get_password(Username, Host), - PasswordStr = build_password_string(Password), - UserInfo = [extract_user_info(InfoName, Username, Host) || InfoName <- [roster, offline, private, vcard]], - UserInfoString = lists:flatten(UserInfo), - io_lib:format("", - [Username, PasswordStr, UserInfoString]). - -build_password_string({StoredKey, ServerKey, Salt, IterationCount}) -> - io_lib:format("password-format='scram'>" - " ", - [base64:encode_to_string(StoredKey), - base64:encode_to_string(ServerKey), - base64:encode_to_string(Salt), - IterationCount]); -build_password_string(Password) when is_list(Password) -> - io_lib:format("password-format='plaintext' password='~s'>", [Password]). - %% @spec (InfoName::atom(), Username::string(), Host::string()) -> string() -extract_user_info(roster, Username, Host) -> - case loaded_module(Host, mod_roster) of - {ok, _DBType} -> - From = To = jlib:make_jid(Username, Host, ""), - SubelGet = {xmlelement, "query", [{"xmlns",?NS_ROSTER}], []}, - %%IQGet = #iq{type=get, xmlns=?NS_ROSTER, payload=SubelGet}, % this is for 3.0.0 version - IQGet = {iq, "", get, ?NS_ROSTER, "" , SubelGet}, - Res = mod_roster:process_local_iq(From, To, IQGet), - %%[El] = Res#iq.payload, % this is for 3.0.0 version - {iq, _, result, _, _, Els} = Res, - case Els of - [El] -> exmpp_xml:document_to_list(El); - [] -> "" - end; - _E -> - "" - end; - -extract_user_info(offline, Username, Host) -> - case loaded_module(Host, mod_offline) of - {ok, mnesia} -> - Els = mnesia_pop_offline_messages([], Username, Host), - case Els of - [] -> ""; - Els -> - OfEl = {xmlelement, "offline-messages", [], Els}, - exmpp_xml:document_to_list(OfEl) - end; - {ok, odbc} -> - ""; - _E -> - "" - end; - -extract_user_info(private, Username, Host) -> - case loaded_module(Host, mod_private) of - {ok, mnesia} -> - get_user_private_mnesia(Username, Host); - {ok, odbc} -> - ""; - _E -> - "" - end; - -extract_user_info(vcard, Username, Host) -> - case loaded_module(Host, mod_vcard) of - {ok, _DBType} -> - From = To = jlib:make_jid(Username, Host, ""), - SubelGet = {xmlelement, "vCard", [{"xmlns",?NS_VCARD}], []}, - %%IQGet = #iq{type=get, xmlns=?NS_VCARD, payload=SubelGet}, % this is for 3.0.0 version - IQGet = {iq, "", get, ?NS_VCARD, "" , SubelGet}, - Res = mod_vcard:process_sm_iq(From, To, IQGet), - %%[El] = Res#iq.payload, % this is for 3.0.0 version - {iq, _, result, _, _, Els} = Res, - case Els of - [El] -> exmpp_xml:document_to_list(El); - [] -> "" - end; - _E -> - "" - end. - %%%================================== %%%% Interface with ejabberd offline storage - %% Copied from mod_offline.erl and customized --record(offline_msg, {us, timestamp, expire, from, to, packet}). -mnesia_pop_offline_messages(Ls, User, Server) -> - LUser = jlib:nodeprep(User), - LServer = jlib:nameprep(Server), - US = {LUser, LServer}, - F = fun() -> - Rs = mnesia:wread({offline_msg, US}), - %%mnesia:delete({offline_msg, US}), - Rs - end, - case mnesia:transaction(F) of - {atomic, Rs} -> - TS = now(), - Ls ++ lists:map( - fun(R) -> - {xmlelement, Name, Attrs, Els} = R#offline_msg.packet, - FromString = jlib:jid_to_string(R#offline_msg.from), - Attrs2 = lists:keystore("from", 1, Attrs, {"from", FromString}), - Attrs3 = lists:keystore("xmlns", 1, Attrs2, {"xmlns", "jabber:client"}), - {xmlelement, Name, Attrs3, - Els ++ - [jlib:timestamp_to_xml( - calendar:now_to_universal_time( - R#offline_msg.timestamp))]} - end, - lists:filter( - fun(R) -> - case R#offline_msg.expire of - never -> - true; - TimeStamp -> - TS < TimeStamp - end - end, - lists:keysort(#offline_msg.timestamp, Rs))); - _ -> - Ls - end. - %%%================================== %%%% Interface with ejabberd private storage - -get_user_private_mnesia(Username, Host) -> - ListNsEl = mnesia:dirty_select(private_storage, - [{#private_storage{usns={Username, Host, '$1'}, xml = '$2'}, - [], ['$$']}]), - Els = [exmpp_xml:document_to_list(El) || [_Ns, El] <- ListNsEl], - case lists:flatten(Els) of - "" -> ""; - ElsString -> - io_lib:format("~s", [ElsString]) - end. - %%%================================== %%%% Disk file access - %% @spec () -> string() -make_filename_template() -> - {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(), - lists:flatten( - io_lib:format("~4..0w~2..0w~2..0w-~2..0w~2..0w~2..0w", - [Year, Month, Day, Hour, Minute, Second])). - %% @spec (Dir::string(), FnT::string()) -> string() -make_main_basefilename(Dir, FnT) -> - Filename2 = filename:flatten([FnT, ".xml"]), - filename:join([Dir, Filename2]). - %% @spec (FnT::string(), Host::string()) -> FnH::string() %% @doc Make the filename for the host. %% Example: ``("20080804-231550", "jabber.example.org") -> "20080804-231550_jabber_example_org.xml"'' -make_host_filename(FnT, Host) -> - Host2 = string:join(string:tokens(Host, "."), "_"), - filename:flatten([FnT, "_", Host2, ".xml"]). - -make_host_basefilename(Dir, FnT) -> - filename:join([Dir, FnT]). - %% @spec (Fn::string()) -> {ok, Fd} -file_open(Fn) -> - file:open(Fn, [write]). - %% @spec (Fd) -> ok -file_close(Fd) -> - file:close(Fd). - %% @spec (Fd, String::string()) -> ok print(Fd, String) -> - io:format(Fd, String, []). - %%%================================== - %%% vim: set filetype=erlang tabstop=8 foldmarker=%%%%,%%%= foldmethod=marker: + file:write(Fd, String). diff --git a/src/ejabberd_rdbms.erl b/src/ejabberd_rdbms.erl index d0b20e6f7..abb17974c 100644 --- a/src/ejabberd_rdbms.erl +++ b/src/ejabberd_rdbms.erl @@ -25,54 +25,51 @@ %%%---------------------------------------------------------------------- -module(ejabberd_rdbms). + -author('alexey@process-one.net'). -export([start/0]). + -include("ejabberd.hrl"). start() -> - %% Check if ejabberd has been compiled with ODBC case catch ejabberd_odbc_sup:module_info() of - {'EXIT',{undef,_}} -> - ?INFO_MSG("ejabberd has not been compiled with relational database support. Skipping database startup.", []); - _ -> - %% If compiled with ODBC, start ODBC on the needed host - start_hosts() + {'EXIT', {undef, _}} -> + ?INFO_MSG("ejabberd has not been compiled with " + "relational database support. Skipping " + "database startup.", + []); + _ -> start_hosts() end. %% Start relationnal DB module on the nodes where it is needed start_hosts() -> - lists:foreach( - fun(Host) -> - case needs_odbc(Host) of - true -> start_odbc(Host); - false -> ok - end - end, ?MYHOSTS). + lists:foreach(fun (Host) -> + case needs_odbc(Host) of + true -> start_odbc(Host); + false -> ok + end + 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; - _Error -> - ?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying...~n", [Supervisor_name, _Error]), - start_odbc(Host) + {ok, _PID} -> ok; + _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). diff --git a/src/ejabberd_receiver.erl b/src/ejabberd_receiver.erl index 7e93feeb9..c9ed6b350 100644 --- a/src/ejabberd_receiver.erl +++ b/src/ejabberd_receiver.erl @@ -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, - [Socket, SockMod, Shaper, MaxStanzaSize]), + {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, - shaper_state = ShaperState, - max_stanza_size = MaxStanzaSize, - timeout = Timeout}}. + {ok, + #state{socket = Socket, sock_mod = SockMod, + shaper_state = ShaperState, + 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,45 +228,42 @@ 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} - 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} - end; - _ -> - {noreply, process_data(Data, State), ?HIBERNATE_TIMEOUT} + tls -> + case tls:recv_data(Socket, Data) of + {ok, TLSData} -> + {noreply, process_data(TLSData, State), + ?HIBERNATE_TIMEOUT}; + {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} + end; + _ -> + {noreply, process_data(Data, State), ?HIBERNATE_TIMEOUT} end; handle_info({Tag, _TCPSocket}, State) - when (Tag == tcp_closed) or (Tag == ssl_closed) -> + when (Tag == tcp_closed) or (Tag == ssl_closed) -> {stop, normal, State}; handle_info({Tag, _TCPSocket, Reason}, State) - when (Tag == tcp_error) or (Tag == ssl_error) -> + when (Tag == tcp_error) or (Tag == ssl_error) -> case Reason of - 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 -> - gen_fsm:send_event(C2SPid, closed); - true -> - ok + if C2SPid /= undefined -> + gen_fsm:send_event(C2SPid, closed); + 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,48 +299,44 @@ code_change(_OldVsn, State, _Extra) -> activate_socket(#state{socket = Socket, sock_mod = SockMod}) -> - PeerName = - case SockMod of - gen_tcp -> - inet:setopts(Socket, [{active, once}]), - inet:peername(Socket); - _ -> - SockMod:setopts(Socket, [{active, once}]), - SockMod:peername(Socket) - end, + PeerName = case SockMod of + gen_tcp -> + inet:setopts(Socket, [{active, once}]), + inet:peername(Socket); + _ -> + SockMod:setopts(Socket, [{active, once}]), + 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; - element(1, Element) == xmlstreamstart; - element(1, Element) == xmlstreamelement; - element(1, Element) == xmlstreamend -> - if - C2SPid == undefined -> - State; - true -> - catch gen_fsm:send_event(C2SPid, element_wrapper(Element)), - process_data(Els, State) + 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; + true -> + 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. diff --git a/src/ejabberd_regexp.erl b/src/ejabberd_regexp.erl index d6210b562..6603ec626 100644 --- a/src/ejabberd_regexp.erl +++ b/src/ejabberd_regexp.erl @@ -25,48 +25,72 @@ %%%---------------------------------------------------------------------- -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 - {match, _, _} -> match; - {match, _} -> match; - match -> match; - nomatch -> nomatch; - {error, Error} -> {error, Error} + 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; + nomatch -> nomatch; + {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; - {error, Error} -> throw(Error); - A -> A + 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; - {error, Error} -> throw(Error); - A -> A + 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; - {error, Error} -> throw(Error); - A -> A + 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. diff --git a/src/ejabberd_router.erl b/src/ejabberd_router.erl index f1e70ad0f..8577d81ad 100644 --- a/src/ejabberd_router.erl +++ b/src/ejabberd_router.erl @@ -25,6 +25,7 @@ %%%---------------------------------------------------------------------- -module(ejabberd_router). + -author('alexey@process-one.net'). -behaviour(gen_server). @@ -44,13 +45,17 @@ -export([start_link/0]). %% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). +-export([init/1, handle_call/3, handle_cast/2, + handle_info/2, terminate/2, code_change/3]). -include("ejabberd.hrl"). + -include("jlib.hrl"). +-type local_hint() :: undefined | integer() | {apply, atom(), atom()}. + -record(route, {domain, pid, local_hint}). + -record(state, {}). %%==================================================================== @@ -63,6 +68,7 @@ start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). +-spec route(jid(), jid(), xmlel()) -> ok. route(From, To, Packet) -> case catch do_route(From, To, Packet) of @@ -75,119 +81,131 @@ route(From, To, Packet) -> %% Route the error packet only if the originating packet is not an error itself. %% RFC3920 9.3.1 +-spec route_error(jid(), jid(), xmlel(), xmlel()) -> ok. + route_error(From, To, ErrPacket, OrigPacket) -> - {xmlelement, _Name, Attrs, _Els} = OrigPacket, - case "error" == xml:get_attr_s("type", Attrs) of - false -> - route(From, To, ErrPacket); - true -> - ok + #xmlel{attrs = Attrs} = OrigPacket, + case <<"error">> == xml:get_attr_s(<<"type">>, Attrs) of + false -> route(From, To, ErrPacket); + true -> ok end. +-spec register_route(binary()) -> term(). + register_route(Domain) -> register_route(Domain, undefined). +-spec register_route(binary(), local_hint()) -> term(). + register_route(Domain, LocalHint) -> case jlib:nameprep(Domain) of - error -> - erlang:error({invalid_domain, Domain}); - LDomain -> - Pid = self(), - case get_component_number(LDomain) of - undefined -> - F = fun() -> - mnesia:write(#route{domain = LDomain, - pid = Pid, - local_hint = LocalHint}) - end, - mnesia:transaction(F); - N -> - F = fun() -> - case mnesia:wread({route, LDomain}) of - [] -> - 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)); - Rs -> - 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) - end - end, - mnesia:transaction(F) - end + 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, + local_hint = LocalHint}) + end, + mnesia:transaction(F); + N -> + F = fun () -> + case mnesia:wread({route, LDomain}) of + [] -> + 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)); + Rs -> + 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) + 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}); - 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 - end - end, - mnesia:transaction(F); - _ -> - F = fun() -> - case mnesia:match_object(#route{domain=LDomain, - pid = Pid, - _ = '_'}) of - [R] -> - I = R#route.local_hint, - mnesia:write( - #route{domain = LDomain, - pid = undefined, - local_hint = I}), - mnesia:delete_object(R); - _ -> - ok - end - end, - mnesia:transaction(F) - end + 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 + end + end, + mnesia:transaction(F); + _ -> + F = fun () -> + case mnesia:match_object(#route{domain = LDomain, + pid = Pid, _ = '_'}) + of + [R] -> + I = R#route.local_hint, + mnesia:write(#route{domain = LDomain, + pid = undefined, + local_hint = I}), + mnesia:delete_object(R); + _ -> 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) - end, - mnesia:dirty_select(route, [{{route, '_', '$1', '_'}, [], ['$1']}])), + lists:foreach(fun (Pid) -> erlang:monitor(process, Pid) + end, + 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} | @@ -250,39 +263,35 @@ handle_cast(_Msg, 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 + {'EXIT', Reason} -> + ?ERROR_MSG("~p~nwhen processing: ~p", + [Reason, {From, To, Packet}]); + _ -> 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) -> - LDomain = E#route.domain, - I = E#route.local_hint, - mnesia:write( - #route{domain = LDomain, - pid = undefined, - local_hint = I}), - mnesia:delete_object(E); - true -> - mnesia:delete_object(E) - end - end, Es) + F = fun () -> + Es = mnesia:select(route, + [{#route{pid = Pid, _ = '_'}, [], ['$_']}]), + lists:foreach(fun (E) -> + if is_integer(E#route.local_hint) -> + LDomain = E#route.domain, + I = E#route.local_hint, + mnesia:write(#route{domain = + LDomain, + pid = + undefined, + local_hint = + I}), + mnesia:delete_object(E); + true -> mnesia:delete_object(E) + end + 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 - {From, To, Packet} -> - LDstDomain = To#jid.lserver, - case mnesia:dirty_read(route, LDstDomain) of - [] -> - ejabberd_s2s:route(From, To, Packet); - [R] -> - Pid = R#route.pid, - if - node(Pid) == node() -> - case R#route.local_hint of - {apply, Module, Function} -> - Module:Function(From, To, Packet); - _ -> - Pid ! {route, From, To, Packet} - end; - is_pid(Pid) -> - Pid ! {route, From, To, Packet}; - true -> - drop - end; - Rs -> - Value = case ejabberd_config:get_local_option( - {domain_balancing, LDstDomain}) 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)); - bare_destination -> - 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), - Pid = R#route.pid, - if - is_pid(Pid) -> - Pid ! {route, From, To, Packet}; - true -> - drop - end; - LRs -> - R = lists:nth(erlang:phash(Value, length(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} - end - end; - _ -> - SRs = lists:ukeysort(#route.local_hint, Rs), - R = lists:nth(erlang:phash(Value, length(SRs)), SRs), + {OrigFrom, OrigTo, OrigPacket}, []) + of + {From, To, Packet} -> + LDstDomain = To#jid.lserver, + case mnesia:dirty_read(route, LDstDomain) of + [] -> ejabberd_s2s:route(From, To, Packet); + [R] -> + Pid = R#route.pid, + if node(Pid) == node() -> + case R#route.local_hint of + {apply, Module, Function} -> + Module:Function(From, To, Packet); + _ -> Pid ! {route, From, To, Packet} + end; + is_pid(Pid) -> Pid ! {route, From, To, Packet}; + true -> drop + end; + Rs -> + 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)); + bare_destination -> + 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, 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, 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} end - end - end; - drop -> - ok + end; + _ -> + SRs = lists:ukeysort(#route.local_hint, Rs), + 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 + end + end + end; + 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. diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl index b06a7ab6c..0832d1dfd 100644 --- a/src/ejabberd_s2s.erl +++ b/src/ejabberd_s2s.erl @@ -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 + {'EXIT', Reason} -> + ?ERROR_MSG("~p~nwhen processing: ~p", + [Reason, {From, To, Packet}]); + _ -> ok end. clean_temporarily_blocked_table() -> - mnesia:clear_table(temporarily_blocked). + mnesia:clear_table(temporarily_blocked). + +-spec list_temporarily_blocked_hosts() -> [temporarily_blocked()]. + list_temporarily_blocked_hosts() -> - ets:tab2list(temporarily_blocked). + ets:tab2list(temporarily_blocked). + +-spec external_host_overloaded(binary()) -> {aborted, any()} | {atomic, ok}. external_host_overloaded(Host) -> - ?INFO_MSG("Disabling connections from ~s for ~p seconds", [Host, ?S2S_OVERLOAD_BLOCK_PERIOD]), - mnesia:transaction( fun() -> - mnesia:write(#temporarily_blocked{host = Host, timestamp = now()}) - end). + ?INFO_MSG("Disabling connections from ~s for ~p " + "seconds", + [Host, ?S2S_OVERLOAD_BLOCK_PERIOD]), + mnesia:transaction(fun () -> + mnesia:write(#temporarily_blocked{host = Host, + timestamp = + now()}) + end). + +-spec is_temporarly_blocked(binary()) -> boolean(). is_temporarly_blocked(Host) -> - case mnesia:dirty_read(temporarily_blocked, Host) of - [] -> false; - [#temporarily_blocked{timestamp = T}=Entry] -> - case timer:now_diff(now(), T) of - N when N > ?S2S_OVERLOAD_BLOCK_PERIOD * 1000 * 1000 -> - mnesia:dirty_delete_object(Entry), - false; - _ -> - true - end - end. + 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 + 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 - [#s2s{pid = Pid, key = Key}] -> - F = fun() -> - mnesia:delete_object(#s2s{fromto = FromTo, - pid = Pid, - key = Key}) - end, - mnesia:transaction(F); - _ -> - ok + 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, + key = Key}) + end, + mnesia:transaction(F); + _ -> ok end. +-spec have_connection({binary(), binary()}) -> boolean(). + have_connection(FromTo) -> case catch mnesia:dirty_read(s2s, FromTo) of [_] -> @@ -136,6 +155,8 @@ have_connection(FromTo) -> false end. +-spec has_key({binary(), binary()}, binary()) -> boolean(). + has_key(FromTo, Key) -> case mnesia:dirty_select(s2s, [{#s2s{fromto = FromTo, key = Key, _ = '_'}, @@ -147,6 +168,8 @@ has_key(FromTo, Key) -> true end. +-spec get_connections_pids({binary(), binary()}) -> [pid()]. + get_connections_pids(FromTo) -> case catch mnesia:dirty_read(s2s, FromTo) of L when is_list(L) -> @@ -155,33 +178,32 @@ get_connections_pids(FromTo) -> [] end. +-spec try_register({binary(), binary()}) -> {key, binary()} | false. + try_register(FromTo) -> Key = randoms:get_string(), MaxS2SConnectionsNumber = max_s2s_connections_number(FromTo), MaxS2SConnectionsNumberPerNode = max_s2s_connections_number_per_node(FromTo), - F = fun() -> + F = fun () -> L = mnesia:read({s2s, FromTo}), - NeededConnections = needed_connections_number( - L, MaxS2SConnectionsNumber, - MaxS2SConnectionsNumberPerNode), - if - NeededConnections > 0 -> - mnesia:write(#s2s{fromto = FromTo, - pid = self(), - key = Key}), - {key, Key}; - true -> - false + NeededConnections = needed_connections_number(L, + MaxS2SConnectionsNumber, + MaxS2SConnectionsNumberPerNode), + if NeededConnections > 0 -> + mnesia:write(#s2s{fromto = FromTo, pid = self(), + key = Key}), + {key, Key}; + 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). @@ -239,15 +261,13 @@ handle_info({mnesia_system_event, {mnesia_down, Node}}, 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 + {'EXIT', Reason} -> + ?ERROR_MSG("~p~nwhen processing: ~p", + [Reason, {From, To, Packet}]); + _ -> ok end, {noreply, State}; -handle_info(_Info, State) -> - {noreply, State}. +handle_info(_Info, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: terminate(Reason, State) -> void() @@ -284,76 +304,79 @@ 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", - [From, To, Packet, 8]), + ?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), - #jid{lserver = MyServer} = From, - ejabberd_hooks:run( - s2s_send_packet, - MyServer, - [From, To, Packet]), - send_element(Pid, {xmlelement, Name, NewAttrs, Els}), - ok; - {aborted, _Reason} -> - case xml:get_tag_attr_s("type", Packet) of - "error" -> ok; - "result" -> ok; - _ -> - Err = jlib:make_error_reply( - Packet, ?ERR_SERVICE_UNAVAILABLE), - ejabberd_router:route(To, From, Err) - end, - false + {atomic, Pid} when is_pid(Pid) -> + ?DEBUG("sending to process ~p~n", [Pid]), + #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, + [From, To, Packet]), + 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; + _ -> + 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}; - [] -> - %% 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 - true -> - NeededConnections = needed_connections_number( - [], MaxS2SConnectionsNumber, - MaxS2SConnectionsNumberPerNode), - open_several_connections( - NeededConnections, MyServer, - Server, From, FromTo, - MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode); - false -> - {aborted, error} - end; - L when is_list(L) -> - NeededConnections = needed_connections_number( - L, MaxS2SConnectionsNumber, - MaxS2SConnectionsNumberPerNode), - if - NeededConnections > 0 -> - %% We establish the missing connections for this pair. - open_several_connections( - NeededConnections, MyServer, - Server, From, FromTo, - MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode); - true -> - %% We choose a connexion from the pool of opened ones. - {atomic, choose_connection(From, L)} - end + {'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 + true -> + NeededConnections = needed_connections_number([], + MaxS2SConnectionsNumber, + MaxS2SConnectionsNumberPerNode), + open_several_connections(NeededConnections, MyServer, + Server, From, FromTo, + MaxS2SConnectionsNumber, + MaxS2SConnectionsNumberPerNode); + false -> {aborted, error} + end; + L when is_list(L) -> + NeededConnections = needed_connections_number(L, + MaxS2SConnectionsNumber, + MaxS2SConnectionsNumberPerNode), + if NeededConnections > 0 -> + %% We establish the missing connections for this pair. + open_several_connections(NeededConnections, MyServer, + Server, From, FromTo, + MaxS2SConnectionsNumber, + MaxS2SConnectionsNumberPerNode); + true -> + %% We choose a connexion from the pool of opened ones. + {atomic, choose_connection(From, L)} + end end. choose_connection(From, Connections) -> @@ -361,29 +384,26 @@ choose_connection(From, Connections) -> choose_pid(From, Pids) -> Pids1 = case [P || P <- Pids, node(P) == node()] of - [] -> Pids; - Ps -> Ps + [] -> 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)), - 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) - || _N <- lists:seq(1, N)], + 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,41 +413,38 @@ new_connection(MyServer, Server, From, FromTo, MyServer, Server, {new, Key}), F = fun() -> L = mnesia:read({s2s, FromTo}), - NeededConnections = needed_connections_number( - L, MaxS2SConnectionsNumber, - MaxS2SConnectionsNumberPerNode), - 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) + NeededConnections = needed_connections_number(L, + MaxS2SConnectionsNumber, + MaxS2SConnectionsNumberPerNode), + 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) 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 - Max when is_integer(Max) -> Max; - _ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER + 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 - Max when is_integer(Max) -> Max; - _ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE + 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. needed_connections_number(Ls, MaxS2SConnectionsNumber, @@ -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 - s2s -> % bypass RFC 3920 10.3 - false; - _ -> - Hosts = ?MYHOSTS, - P = fun(ParentDomain) -> lists:member(ParentDomain, Hosts) end, - lists:any(P, parent_domains(To#jid.lserver)) + case ejabberd_config:get_local_option( + {route_subdomains, LFromDomain}, + fun(s2s) -> s2s end) of + s2s -> % bypass RFC 3920 10.3 + false; + 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]; - (Label, [Head | Tail]) -> - [Label ++ "." ++ Head, Head | Tail] - end, [], lists:reverse(string:tokens(Domain, "."))). - -send_element(Pid, El) -> - Pid ! {send_element, El}. + lists:foldl(fun (Label, []) -> [Label]; + (Label, [Head | Tail]) -> + [<>, 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, - tags = [stats, s2s], - desc = "Number of incoming s2s connections on the node", - module = ?MODULE, function = incoming_s2s_number, - args = [], - result = {s2s_incoming, integer}}, + [#ejabberd_commands{name = incoming_s2s_number, + tags = [stats, s2s], + desc = + "Number of incoming s2s connections on " + "the node", + module = ?MODULE, function = incoming_s2s_number, + args = [], result = {s2s_incoming, integer}}, #ejabberd_commands{name = outgoing_s2s_number, - tags = [stats, s2s], - desc = "Number of outgoing s2s connections on the node", - module = ?MODULE, function = outgoing_s2s_number, - args = [], - result = {s2s_outgoing, integer}} - ]. + tags = [stats, s2s], + desc = + "Number of outgoing s2s connections on " + "the node", + module = ?MODULE, function = outgoing_s2s_number, + 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, node, key] -> + mnesia:transform_table(s2s, ignore, [fromto, pid, key]), + mnesia:clear_table(s2s); + [fromto, pid, key] -> ok; + {'EXIT', _} -> ok end, case lists:member(local_s2s, mnesia:system_info(tables)) of true -> @@ -521,62 +532,74 @@ 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) -> - not lists:member(ParentDomain, Hosts) - end, parent_domains(MyServer)) of - [MyHost|_] -> - allow_host1(MyHost, S2SHost); - [] -> - allow_host1(MyServer, S2SHost) + 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. allow_host1(MyHost, S2SHost) -> - case ejabberd_config:get_local_option({{s2s_host, S2SHost}, MyHost}) of - deny -> false; - allow -> true; - _ -> - case ejabberd_config:get_local_option({s2s_default_policy, MyHost}) of - deny -> false; - _ -> - case ejabberd_hooks:run_fold(s2s_allow_host, MyHost, - allow, [MyHost, S2SHost]) of - deny -> false; - allow -> true; - _ -> true - end - end + case ejabberd_config:get_local_option( + {{s2s_host, S2SHost}, MyHost}, + fun(deny) -> deny; (allow) -> allow end) + of + deny -> false; + allow -> true; + 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 + deny -> false; + allow -> true; + _ -> true + end + end end. %% Get information about S2S connections of the specified type. %% @spec (Type) -> [Info] %% where Type = in | out %% Info = [{InfoName::atom(), InfoValue::any()}] + get_info_s2s_connections(Type) -> ChildType = case Type of - in -> ejabberd_s2s_in_sup; - out -> ejabberd_s2s_out_sup + in -> ejabberd_s2s_in_sup; + out -> ejabberd_s2s_out_sup end, Connections = supervisor:which_children(ChildType), - get_s2s_info(Connections,Type). + get_s2s_info(Connections, Type). -get_s2s_info(Connections,Type)-> - complete_s2s_info(Connections,Type,[]). -complete_s2s_info([],_,Result)-> - Result; -complete_s2s_info([Connection|T],Type,Result)-> - {_,PID,_,_}=Connection, +get_s2s_info(Connections, Type) -> + complete_s2s_info(Connections, Type, []). + +complete_s2s_info([], _, Result) -> Result; +complete_s2s_info([Connection | T], Type, Result) -> + {_, PID, _, _} = Connection, State = get_s2s_state(PID), - complete_s2s_info(T,Type,[State|Result]). + complete_s2s_info(T, Type, [State | Result]). -get_s2s_state(S2sPid)-> - Infos = case gen_fsm:sync_send_all_state_event(S2sPid,get_state_infos) of - {state_infos, Is} -> [{status, open} | Is]; - {noproc,_} -> [{status, closed}]; %% Connection closed - {badrpc,_} -> [{status, error}] +-spec get_s2s_state(pid()) -> [{status, open | closed | error} | {s2s_pid, pid()}]. + +get_s2s_state(S2sPid) -> + Infos = case gen_fsm:sync_send_all_state_event(S2sPid, + get_state_infos) + of + {state_infos, Is} -> [{status, open} | Is]; + {noproc, _} -> [{status, closed}]; %% Connection closed + {badrpc, _} -> [{status, error}] end, [{s2s_pid, S2sPid} | Infos]. diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index 6ae4f3446..2dc7c86b2 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -25,119 +25,110 @@ %%%---------------------------------------------------------------------- -module(ejabberd_s2s_in). + -author('alexey@process-one.net'). -behaviour(p1_fsm). %% External exports --export([start/2, - start_link/2, - match_domain/2, +-export([start/2, start_link/2, match_domain/2, socket_type/0]). %% gen_fsm callbacks --export([init/1, - wait_for_stream/2, - wait_for_feature_request/2, - stream_established/2, - handle_event/3, - handle_sync_event/4, - code_change/4, - handle_info/3, - print_state/1, - terminate/3]). +-export([init/1, wait_for_stream/2, + wait_for_feature_request/2, stream_established/2, + handle_event/3, handle_sync_event/4, code_change/4, + handle_info/3, print_state/1, terminate/3]). -include("ejabberd.hrl"). + -include("jlib.hrl"). --ifdef(SSL40). --include_lib("public_key/include/public_key.hrl"). + +-include_lib("public_key/include/public_key.hrl"). + -define(PKIXEXPLICIT, 'OTP-PUB-KEY'). + -define(PKIXIMPLICIT, 'OTP-PUB-KEY'). --else. --ifdef(SSL39). --include_lib("ssl/include/ssl_pkix.hrl"). --define(PKIXEXPLICIT, 'OTP-PKIX'). --define(PKIXIMPLICIT, 'OTP-PKIX'). --else. --include_lib("ssl/include/PKIX1Explicit88.hrl"). --include_lib("ssl/include/PKIX1Implicit88.hrl"). --define(PKIXEXPLICIT, 'PKIX1Explicit88'). --define(PKIXIMPLICIT, 'PKIX1Implicit88'). --endif. --endif. + -include("XmppAddr.hrl"). -define(DICT, dict). --record(state, {socket, - sockmod, - streamid, - shaper, - tls = false, - tls_enabled = false, - tls_required = false, - tls_certverify = false, - tls_options = [], - server, - authenticated = false, - auth_domain, - connections = ?DICT:new(), - timer}). - +-record(state, + {socket :: ejabberd_socket:socket_state(), + sockmod = ejabberd_socket :: ejabberd_socket | ejabberd_frontend_socket, + streamid = <<"">> :: binary(), + shaper = none :: shaper:shaper(), + tls = false :: boolean(), + tls_enabled = false :: boolean(), + tls_required = false :: boolean(), + tls_certverify = false :: boolean(), + tls_options = [] :: list(), + server = <<"">> :: binary(), + authenticated = false :: boolean(), + auth_domain = <<"">> :: binary(), + connections = (?DICT):new() :: dict(), + timer = make_ref() :: reference()}). %-define(DBGFSM, true). -ifdef(DBGFSM). --define(FSMOPTS, [{debug, [trace]}]). --else. --define(FSMOPTS, []). --endif. --define(FSMLIMITS, [{max_queue, 2000}]). %% if queue grows more than this, we shutdown this connection. +-define(FSMOPTS, [{debug, [trace]}]). + +-else. + +-define(FSMOPTS, []). + +-endif. %% Module start with or without supervisor: -ifdef(NO_TRANSIENT_SUPERVISORS). --define(SUPERVISOR_START, p1_fsm:start(ejabberd_s2s_in, [SockData, Opts], - ?FSMOPTS ++ ?FSMLIMITS)). + +-define(SUPERVISOR_START, + p1_fsm:start(ejabberd_s2s_in, [SockData, Opts], + ?FSMOPTS ++ fsm_limit_opts(Opts)). + -else. --define(SUPERVISOR_START, supervisor:start_child(ejabberd_s2s_in_sup, - [SockData, Opts])). + +-define(SUPERVISOR_START, + supervisor:start_child(ejabberd_s2s_in_sup, + [SockData, Opts])). + -endif. -define(STREAM_HEADER(Version), - ("" - "") - ). + <<"">>). --define(STREAM_TRAILER, ""). +-define(STREAM_TRAILER, <<"">>). -define(INVALID_NAMESPACE_ERR, - xml:element_to_string(?SERR_INVALID_NAMESPACE)). + xml:element_to_binary(?SERR_INVALID_NAMESPACE)). -define(HOST_UNKNOWN_ERR, - xml:element_to_string(?SERR_HOST_UNKNOWN)). + xml:element_to_binary(?SERR_HOST_UNKNOWN)). -define(INVALID_FROM_ERR, - xml:element_to_string(?SERR_INVALID_FROM)). + xml:element_to_binary(?SERR_INVALID_FROM)). -define(INVALID_XML_ERR, - xml:element_to_string(?SERR_XML_NOT_WELL_FORMED)). + xml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- -start(SockData, Opts) -> - ?SUPERVISOR_START. +start(SockData, Opts) -> ?SUPERVISOR_START. start_link(SockData, Opts) -> - p1_fsm:start_link(ejabberd_s2s_in, [SockData, Opts], ?FSMOPTS ++ ?FSMLIMITS). + p1_fsm:start_link(ejabberd_s2s_in, [SockData, Opts], + ?FSMOPTS ++ fsm_limit_opts(Opts)). -socket_type() -> - xml_stream. +socket_type() -> xml_stream. %%%---------------------------------------------------------------------- %%% Callback functions from gen_fsm @@ -153,36 +144,44 @@ socket_type() -> init([{SockMod, Socket}, Opts]) -> ?DEBUG("started: ~p", [{SockMod, Socket}]), Shaper = case lists:keysearch(shaper, 1, Opts) of - {value, {_, S}} -> S; - _ -> none + {value, {_, S}} -> S; + _ -> none end, - {StartTLS, TLSRequired, TLSCertverify} = case ejabberd_config:get_local_option(s2s_use_starttls) of - UseTls when (UseTls==undefined) or (UseTls==false) -> - {false, false, false}; - UseTls when (UseTls==true) or (UseTls==optional) -> - {true, false, false}; - required -> - {true, true, false}; - required_trusted -> - {true, true, true} - end, - TLSOpts = case ejabberd_config:get_local_option(s2s_certfile) of - undefined -> - []; - CertFile -> - [{certfile, CertFile}] + {StartTLS, TLSRequired, TLSCertverify} = + case ejabberd_config:get_local_option( + s2s_use_starttls, + fun(false) -> false; + (true) -> true; + (optional) -> optional; + (required) -> required; + (required_trusted) -> required_trusted + end, + false) of + UseTls + when (UseTls == undefined) or + (UseTls == false) -> + {false, false, false}; + UseTls + when (UseTls == true) or + (UseTls == + optional) -> + {true, false, false}; + required -> {true, true, false}; + required_trusted -> + {true, true, true} + end, + TLSOpts = case ejabberd_config:get_local_option( + s2s_certfile, + fun iolist_to_binary/1) of + undefined -> []; + CertFile -> [{certfile, CertFile}] end, Timer = erlang:start_timer(?S2STIMEOUT, self(), []), {ok, wait_for_stream, - #state{socket = Socket, - sockmod = SockMod, - streamid = new_id(), - shaper = Shaper, - tls = StartTLS, - tls_enabled = false, - tls_required = TLSRequired, - tls_certverify = TLSCertverify, - tls_options = TLSOpts, + #state{socket = Socket, sockmod = SockMod, + streamid = new_id(), shaper = Shaper, tls = StartTLS, + tls_enabled = false, tls_required = TLSRequired, + tls_certverify = TLSCertverify, tls_options = TLSOpts, timer = Timer}}. %%---------------------------------------------------------------------- @@ -192,373 +191,371 @@ init([{SockMod, Socket}, Opts]) -> %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- -wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> - case {xml:get_attr_s("xmlns", Attrs), - xml:get_attr_s("xmlns:db", Attrs), - xml:get_attr_s("to", Attrs), - xml:get_attr_s("version", Attrs) == "1.0"} of - {"jabber:server", _, Server, true} when - StateData#state.tls and (not StateData#state.authenticated) -> - send_text(StateData, ?STREAM_HEADER(" version='1.0'")), - SASL = - if - StateData#state.tls_enabled -> - case (StateData#state.sockmod):get_peer_certificate( - StateData#state.socket) of - {ok, Cert} -> - case (StateData#state.sockmod):get_verify_result(StateData#state.socket) of - 0 -> - [{xmlelement, "mechanisms", - [{"xmlns", ?NS_SASL}], - [{xmlelement, "mechanism", [], - [{xmlcdata, "EXTERNAL"}]}]}]; - CertVerifyRes -> - case StateData#state.tls_certverify of - true -> {error_cert_verif, CertVerifyRes, Cert}; - false -> [] - end - end; - error -> - [] +wait_for_stream({xmlstreamstart, _Name, Attrs}, + StateData) -> + case {xml:get_attr_s(<<"xmlns">>, Attrs), + xml:get_attr_s(<<"xmlns:db">>, Attrs), + xml:get_attr_s(<<"to">>, Attrs), + xml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>} + of + {<<"jabber:server">>, _, Server, true} + when StateData#state.tls and + not StateData#state.authenticated -> + send_text(StateData, + ?STREAM_HEADER(<<" version='1.0'">>)), + SASL = if StateData#state.tls_enabled -> + case + (StateData#state.sockmod):get_peer_certificate(StateData#state.socket) + of + {ok, Cert} -> + case + (StateData#state.sockmod):get_verify_result(StateData#state.socket) + of + 0 -> + [#xmlel{name = <<"mechanisms">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = + [#xmlel{name = <<"mechanism">>, + attrs = [], + children = + [{xmlcdata, + <<"EXTERNAL">>}]}]}]; + CertVerifyRes -> + case StateData#state.tls_certverify of + true -> + {error_cert_verif, CertVerifyRes, + Cert}; + false -> [] + end + end; + error -> [] end; - true -> - [] - end, - StartTLS = if - StateData#state.tls_enabled -> - []; - (not StateData#state.tls_enabled) and (not StateData#state.tls_required) -> - [{xmlelement, "starttls", [{"xmlns", ?NS_TLS}], []}]; - (not StateData#state.tls_enabled) and StateData#state.tls_required -> - [{xmlelement, "starttls", [{"xmlns", ?NS_TLS}], - [{xmlelement, "required", [], []}] - }] - end, - case SASL of - {error_cert_verif, CertVerifyResult, Certificate} -> - CertError = tls:get_cert_verify_string(CertVerifyResult, Certificate), - RemoteServer = xml:get_attr_s("from", Attrs), - ?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)", [StateData#state.server, RemoteServer, CertError]), - send_text(StateData, xml:element_to_string(?SERRT_POLICY_VIOLATION("en", CertError))), - {atomic, Pid} = ejabberd_s2s:find_connection(jlib:make_jid("", Server, ""), jlib:make_jid("", RemoteServer, "")), - ejabberd_s2s_out:stop_connection(Pid), - - {stop, normal, StateData}; - _ -> - send_element(StateData, - {xmlelement, "stream:features", [], - SASL ++ StartTLS ++ - ejabberd_hooks:run_fold( - s2s_stream_features, - Server, - [], [Server])}), - {next_state, wait_for_feature_request, StateData#state{server = Server}} - end; - {"jabber:server", _, Server, true} when - StateData#state.authenticated -> - send_text(StateData, ?STREAM_HEADER(" version='1.0'")), - send_element(StateData, - {xmlelement, "stream:features", [], - ejabberd_hooks:run_fold( - s2s_stream_features, - Server, - [], [Server])}), - {next_state, stream_established, StateData}; - {"jabber:server", "jabber:server:dialback", _Server, _} -> - send_text(StateData, ?STREAM_HEADER("")), - {next_state, stream_established, StateData}; - _ -> - send_text(StateData, ?INVALID_NAMESPACE_ERR), - {stop, normal, StateData} + true -> [] + end, + StartTLS = if StateData#state.tls_enabled -> []; + not StateData#state.tls_enabled and + not StateData#state.tls_required -> + [#xmlel{name = <<"starttls">>, + attrs = [{<<"xmlns">>, ?NS_TLS}], + children = []}]; + not StateData#state.tls_enabled and + StateData#state.tls_required -> + [#xmlel{name = <<"starttls">>, + attrs = [{<<"xmlns">>, ?NS_TLS}], + children = + [#xmlel{name = <<"required">>, + attrs = [], children = []}]}] + end, + case SASL of + {error_cert_verif, CertVerifyResult, Certificate} -> + CertError = tls:get_cert_verify_string(CertVerifyResult, + Certificate), + RemoteServer = xml:get_attr_s(<<"from">>, Attrs), + ?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)", + [StateData#state.server, RemoteServer, CertError]), + send_text(StateData, + xml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>, + CertError))), + {atomic, Pid} = + ejabberd_s2s:find_connection(jlib:make_jid(<<"">>, + Server, <<"">>), + jlib:make_jid(<<"">>, + RemoteServer, + <<"">>)), + ejabberd_s2s_out:stop_connection(Pid), + {stop, normal, StateData}; + _ -> + send_element(StateData, + #xmlel{name = <<"stream:features">>, attrs = [], + children = + SASL ++ + StartTLS ++ + ejabberd_hooks:run_fold(s2s_stream_features, + Server, [], + [Server])}), + {next_state, wait_for_feature_request, + StateData#state{server = Server}} + end; + {<<"jabber:server">>, _, Server, true} + when StateData#state.authenticated -> + send_text(StateData, + ?STREAM_HEADER(<<" version='1.0'">>)), + send_element(StateData, + #xmlel{name = <<"stream:features">>, attrs = [], + children = + ejabberd_hooks:run_fold(s2s_stream_features, + Server, [], + [Server])}), + {next_state, stream_established, StateData}; + {<<"jabber:server">>, <<"jabber:server:dialback">>, + _Server, _} -> + send_text(StateData, ?STREAM_HEADER(<<"">>)), + {next_state, stream_established, StateData}; + _ -> + send_text(StateData, ?INVALID_NAMESPACE_ERR), + {stop, normal, StateData} end; - wait_for_stream({xmlstreamerror, _}, StateData) -> send_text(StateData, - ?STREAM_HEADER("") ++ ?INVALID_XML_ERR ++ ?STREAM_TRAILER), + <<(?STREAM_HEADER(<<"">>))/binary, + (?INVALID_XML_ERR)/binary, (?STREAM_TRAILER)/binary>>), {stop, normal, StateData}; - wait_for_stream(timeout, StateData) -> {stop, normal, StateData}; - wait_for_stream(closed, StateData) -> {stop, normal, StateData}. - -wait_for_feature_request({xmlstreamelement, El}, StateData) -> - {xmlelement, Name, Attrs, Els} = El, +wait_for_feature_request({xmlstreamelement, El}, + StateData) -> + #xmlel{name = Name, attrs = Attrs, children = Els} = El, TLS = StateData#state.tls, TLSEnabled = StateData#state.tls_enabled, - SockMod = (StateData#state.sockmod):get_sockmod(StateData#state.socket), - case {xml:get_attr_s("xmlns", Attrs), Name} of - {?NS_TLS, "starttls"} when TLS == true, - TLSEnabled == false, - SockMod == gen_tcp -> - ?DEBUG("starttls", []), - Socket = StateData#state.socket, - TLSOpts = case ejabberd_config:get_local_option( - {domain_certfile, - StateData#state.server}) of - undefined -> - StateData#state.tls_options; - CertFile -> - [{certfile, CertFile} | - lists:keydelete( - certfile, 1, - StateData#state.tls_options)] - end, - TLSSocket = (StateData#state.sockmod):starttls( - Socket, TLSOpts, - xml:element_to_binary( - {xmlelement, "proceed", [{"xmlns", ?NS_TLS}], []})), - {next_state, wait_for_stream, - StateData#state{socket = TLSSocket, - streamid = new_id(), - tls_enabled = true, - tls_options = TLSOpts - }}; - {?NS_SASL, "auth"} when TLSEnabled -> - Mech = xml:get_attr_s("mechanism", Attrs), - case Mech of - "EXTERNAL" -> - Auth = jlib:decode_base64(xml:get_cdata(Els)), - AuthDomain = jlib:nameprep(Auth), - AuthRes = - case (StateData#state.sockmod):get_peer_certificate( - StateData#state.socket) of + SockMod = + (StateData#state.sockmod):get_sockmod(StateData#state.socket), + case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of + {?NS_TLS, <<"starttls">>} + when TLS == true, TLSEnabled == false, + SockMod == gen_tcp -> + ?DEBUG("starttls", []), + Socket = StateData#state.socket, + TLSOpts = case + ejabberd_config:get_local_option( + {domain_certfile, StateData#state.server}, + fun iolist_to_binary/1) of + undefined -> StateData#state.tls_options; + CertFile -> + [{certfile, CertFile} | lists:keydelete(certfile, 1, + StateData#state.tls_options)] + end, + TLSSocket = (StateData#state.sockmod):starttls(Socket, + TLSOpts, + xml:element_to_binary(#xmlel{name + = + <<"proceed">>, + attrs + = + [{<<"xmlns">>, + ?NS_TLS}], + children + = + []})), + {next_state, wait_for_stream, + StateData#state{socket = TLSSocket, streamid = new_id(), + tls_enabled = true, tls_options = TLSOpts}}; + {?NS_SASL, <<"auth">>} when TLSEnabled -> + Mech = xml:get_attr_s(<<"mechanism">>, Attrs), + case Mech of + <<"EXTERNAL">> -> + Auth = jlib:decode_base64(xml:get_cdata(Els)), + AuthDomain = jlib:nameprep(Auth), + AuthRes = case + (StateData#state.sockmod):get_peer_certificate(StateData#state.socket) + of {ok, Cert} -> - case (StateData#state.sockmod):get_verify_result( - StateData#state.socket) of - 0 -> - case AuthDomain of - error -> - false; - _ -> - case idna:domain_utf8_to_ascii(AuthDomain) of - false -> - false; - PCAuthDomain -> - lists:any( - fun(D) -> - match_domain( - PCAuthDomain, D) - end, get_cert_domains(Cert)) - end - end; - _ -> - false + case + (StateData#state.sockmod):get_verify_result(StateData#state.socket) + of + 0 -> + case AuthDomain of + error -> false; + _ -> + case + idna:domain_utf8_to_ascii(AuthDomain) + of + false -> false; + PCAuthDomain -> + lists:any(fun (D) -> + match_domain(PCAuthDomain, + D) + end, + get_cert_domains(Cert)) + end + end; + _ -> false end; - error -> - false - end, - AllowRemoteHost = ejabberd_s2s:allow_host("", AuthDomain), - if - AuthRes andalso AllowRemoteHost -> - (StateData#state.sockmod):reset_stream( - StateData#state.socket), - send_element(StateData, - {xmlelement, "success", - [{"xmlns", ?NS_SASL}], []}), - ?DEBUG("(~w) Accepted s2s authentication for ~s", - [StateData#state.socket, AuthDomain]), - - %% acess rules are first checked against the globally defined ones, that have precedence over - %% domain-specific ones.. http://www.process-one.net/docs/ejabberd/guide_en.html#AccessRights - %% since there is allways a shaper defined globally for s2s, it doesn't matter the actual - %% local host, since the globall one will be used, even if this domain has a special rule - change_shaper(StateData, "", jlib:make_jid("", AuthDomain, "")), - {next_state, wait_for_stream, - StateData#state{streamid = new_id(), - authenticated = true, - auth_domain = AuthDomain - }}; - true -> - send_element(StateData, - {xmlelement, "failure", - [{"xmlns", ?NS_SASL}], []}), - send_text(StateData, ?STREAM_TRAILER), - {stop, normal, StateData} - end; - _ -> - send_element(StateData, - {xmlelement, "failure", - [{"xmlns", ?NS_SASL}], - [{xmlelement, "invalid-mechanism", [], []}]}), - {stop, normal, StateData} - end; - _ -> - stream_established({xmlstreamelement, El}, StateData) + error -> false + end, + AllowRemoteHost = ejabberd_s2s:allow_host(<<"">>, + AuthDomain), + if AuthRes andalso AllowRemoteHost -> + (StateData#state.sockmod):reset_stream(StateData#state.socket), + send_element(StateData, + #xmlel{name = <<"success">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = []}), + ?DEBUG("(~w) Accepted s2s authentication for ~s", + [StateData#state.socket, AuthDomain]), + change_shaper(StateData, <<"">>, + jlib:make_jid(<<"">>, AuthDomain, <<"">>)), + {next_state, wait_for_stream, + StateData#state{streamid = new_id(), + authenticated = true, + auth_domain = AuthDomain}}; + true -> + send_element(StateData, + #xmlel{name = <<"failure">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = []}), + send_text(StateData, ?STREAM_TRAILER), + {stop, normal, StateData} + end; + _ -> + send_element(StateData, + #xmlel{name = <<"failure">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = + [#xmlel{name = <<"invalid-mechanism">>, + attrs = [], children = []}]}), + {stop, normal, StateData} + end; + _ -> + stream_established({xmlstreamelement, El}, StateData) end; - -wait_for_feature_request({xmlstreamend, _Name}, StateData) -> +wait_for_feature_request({xmlstreamend, _Name}, + StateData) -> send_text(StateData, ?STREAM_TRAILER), {stop, normal, StateData}; - -wait_for_feature_request({xmlstreamerror, _}, StateData) -> - send_text(StateData, ?INVALID_XML_ERR ++ ?STREAM_TRAILER), +wait_for_feature_request({xmlstreamerror, _}, + StateData) -> + send_text(StateData, + <<(?INVALID_XML_ERR)/binary, + (?STREAM_TRAILER)/binary>>), {stop, normal, StateData}; - wait_for_feature_request(closed, StateData) -> {stop, normal, StateData}. - stream_established({xmlstreamelement, El}, StateData) -> cancel_timer(StateData#state.timer), Timer = erlang:start_timer(?S2STIMEOUT, self(), []), case is_key_packet(El) of - {key, To, From, Id, Key} -> - ?DEBUG("GET KEY: ~p", [{To, From, Id, Key}]), - LTo = jlib:nameprep(To), - LFrom = jlib:nameprep(From), - %% Checks if the from domain is allowed and if the to - %% domain is handled by this server: - case {ejabberd_s2s:allow_host(LTo, LFrom), - lists:member(LTo, ejabberd_router:dirty_get_all_domains())} of - {true, true} -> - ejabberd_s2s_out:terminate_if_waiting_delay(LTo, LFrom), - ejabberd_s2s_out:start(LTo, LFrom, - {verify, self(), - Key, StateData#state.streamid}), - Conns = ?DICT:store({LFrom, LTo}, wait_for_verification, - StateData#state.connections), - change_shaper(StateData, LTo, jlib:make_jid("", LFrom, "")), - {next_state, - stream_established, - StateData#state{connections = Conns, - timer = Timer}}; - {_, false} -> - send_text(StateData, ?HOST_UNKNOWN_ERR), - {stop, normal, StateData}; - {false, _} -> - send_text(StateData, ?INVALID_FROM_ERR), - {stop, normal, StateData} - end; - {verify, To, From, Id, Key} -> - ?DEBUG("VERIFY KEY: ~p", [{To, From, Id, Key}]), - LTo = jlib:nameprep(To), - LFrom = jlib:nameprep(From), - Type = case ejabberd_s2s:has_key({LTo, LFrom}, Key) of - true -> "valid"; - _ -> "invalid" - end, - %Type = if Key == Key1 -> "valid"; - % true -> "invalid" - % end, - send_element(StateData, - {xmlelement, - "db:verify", - [{"from", To}, - {"to", From}, - {"id", Id}, - {"type", Type}], - []}), - {next_state, stream_established, StateData#state{timer = Timer}}; - _ -> - NewEl = jlib:remove_attr("xmlns", El), - {xmlelement, Name, Attrs, _Els} = NewEl, - From_s = xml:get_attr_s("from", Attrs), - From = jlib:string_to_jid(From_s), - To_s = xml:get_attr_s("to", Attrs), - To = jlib:string_to_jid(To_s), - if - (To /= error) and (From /= error) -> - LFrom = From#jid.lserver, - LTo = To#jid.lserver, - if - StateData#state.authenticated -> - case (LFrom == StateData#state.auth_domain) - andalso - lists:member( - LTo, - ejabberd_router:dirty_get_all_domains()) of - true -> - if ((Name == "iq") or - (Name == "message") or - (Name == "presence")) -> - ejabberd_hooks:run( - s2s_receive_packet, - LTo, - [From, To, NewEl]), - ejabberd_router:route( - From, To, NewEl); - true -> - error - end; - false -> - error - end; - true -> - case ?DICT:find({LFrom, LTo}, - StateData#state.connections) of - {ok, established} -> - if ((Name == "iq") or - (Name == "message") or - (Name == "presence")) -> - ejabberd_hooks:run( - s2s_receive_packet, - LTo, - [From, To, NewEl]), - ejabberd_router:route( - From, To, NewEl); - true -> - error - end; - _ -> - error - end - end; - true -> - error - end, - ejabberd_hooks:run(s2s_loop_debug, [{xmlstreamelement, El}]), - {next_state, stream_established, StateData#state{timer = Timer}} + {key, To, From, Id, Key} -> + ?DEBUG("GET KEY: ~p", [{To, From, Id, Key}]), + LTo = jlib:nameprep(To), + LFrom = jlib:nameprep(From), + case {ejabberd_s2s:allow_host(LTo, LFrom), + lists:member(LTo, + ejabberd_router:dirty_get_all_domains())} + of + {true, true} -> + ejabberd_s2s_out:terminate_if_waiting_delay(LTo, LFrom), + ejabberd_s2s_out:start(LTo, LFrom, + {verify, self(), Key, + StateData#state.streamid}), + Conns = (?DICT):store({LFrom, LTo}, + wait_for_verification, + StateData#state.connections), + change_shaper(StateData, LTo, + jlib:make_jid(<<"">>, LFrom, <<"">>)), + {next_state, stream_established, + StateData#state{connections = Conns, timer = Timer}}; + {_, false} -> + send_text(StateData, ?HOST_UNKNOWN_ERR), + {stop, normal, StateData}; + {false, _} -> + send_text(StateData, ?INVALID_FROM_ERR), + {stop, normal, StateData} + end; + {verify, To, From, Id, Key} -> + ?DEBUG("VERIFY KEY: ~p", [{To, From, Id, Key}]), + LTo = jlib:nameprep(To), + LFrom = jlib:nameprep(From), + Type = case ejabberd_s2s:has_key({LTo, LFrom}, Key) of + true -> <<"valid">>; + _ -> <<"invalid">> + end, + send_element(StateData, + #xmlel{name = <<"db:verify">>, + attrs = + [{<<"from">>, To}, {<<"to">>, From}, + {<<"id">>, Id}, {<<"type">>, Type}], + children = []}), + {next_state, stream_established, + StateData#state{timer = Timer}}; + _ -> + NewEl = jlib:remove_attr(<<"xmlns">>, El), + #xmlel{name = Name, attrs = Attrs} = NewEl, + From_s = xml:get_attr_s(<<"from">>, Attrs), + From = jlib:string_to_jid(From_s), + To_s = xml:get_attr_s(<<"to">>, Attrs), + To = jlib:string_to_jid(To_s), + if (To /= error) and (From /= error) -> + LFrom = From#jid.lserver, + LTo = To#jid.lserver, + if StateData#state.authenticated -> + case LFrom == StateData#state.auth_domain andalso + lists:member(LTo, + ejabberd_router:dirty_get_all_domains()) + of + true -> + if (Name == <<"iq">>) or (Name == <<"message">>) + or (Name == <<"presence">>) -> + ejabberd_hooks:run(s2s_receive_packet, LTo, + [From, To, NewEl]), + ejabberd_router:route(From, To, NewEl); + true -> error + end; + false -> error + end; + true -> + case (?DICT):find({LFrom, LTo}, + StateData#state.connections) + of + {ok, established} -> + if (Name == <<"iq">>) or (Name == <<"message">>) + or (Name == <<"presence">>) -> + ejabberd_hooks:run(s2s_receive_packet, LTo, + [From, To, NewEl]), + ejabberd_router:route(From, To, NewEl); + true -> error + end; + _ -> error + end + end; + true -> error + end, + ejabberd_hooks:run(s2s_loop_debug, + [{xmlstreamelement, El}]), + {next_state, stream_established, + StateData#state{timer = Timer}} end; - stream_established({valid, From, To}, StateData) -> send_element(StateData, - {xmlelement, - "db:result", - [{"from", To}, - {"to", From}, - {"type", "valid"}], - []}), + #xmlel{name = <<"db:result">>, + attrs = + [{<<"from">>, To}, {<<"to">>, From}, + {<<"type">>, <<"valid">>}], + children = []}), LFrom = jlib:nameprep(From), LTo = jlib:nameprep(To), - NSD = StateData#state{ - connections = ?DICT:store({LFrom, LTo}, established, - StateData#state.connections)}, + NSD = StateData#state{connections = + (?DICT):store({LFrom, LTo}, established, + StateData#state.connections)}, {next_state, stream_established, NSD}; - stream_established({invalid, From, To}, StateData) -> send_element(StateData, - {xmlelement, - "db:result", - [{"from", To}, - {"to", From}, - {"type", "invalid"}], - []}), + #xmlel{name = <<"db:result">>, + attrs = + [{<<"from">>, To}, {<<"to">>, From}, + {<<"type">>, <<"invalid">>}], + children = []}), LFrom = jlib:nameprep(From), LTo = jlib:nameprep(To), - NSD = StateData#state{ - connections = ?DICT:erase({LFrom, LTo}, - StateData#state.connections)}, + NSD = StateData#state{connections = + (?DICT):erase({LFrom, LTo}, + StateData#state.connections)}, {next_state, stream_established, NSD}; - stream_established({xmlstreamend, _Name}, StateData) -> {stop, normal, StateData}; - stream_established({xmlstreamerror, _}, StateData) -> send_text(StateData, - ?INVALID_XML_ERR ++ ?STREAM_TRAILER), + <<(?INVALID_XML_ERR)/binary, + (?STREAM_TRAILER)/binary>>), {stop, normal, StateData}; - stream_established(timeout, StateData) -> {stop, normal, StateData}; - stream_established(closed, StateData) -> {stop, normal, StateData}. - - %%---------------------------------------------------------------------- %% Func: StateName/3 %% Returns: {next_state, NextStateName, NextStateData} | @@ -586,32 +583,30 @@ handle_event(_Event, StateName, StateData) -> %% {reply, Reply, NextStateName, NextStateData} %% Reply = {state_infos, [{InfoName::atom(), InfoValue::any()] %%---------------------------------------------------------------------- -handle_sync_event(get_state_infos, _From, StateName, StateData) -> + +handle_sync_event(get_state_infos, _From, StateName, + StateData) -> SockMod = StateData#state.sockmod, - {Addr,Port} = try SockMod:peername(StateData#state.socket) of - {ok, {A,P}} -> {A,P}; - {error, _} -> {unknown,unknown} - catch - _:_ -> {unknown,unknown} - end, - Domains = get_external_hosts(StateData), - Infos = [ - {direction, in}, - {statename, StateName}, - {addr, Addr}, - {port, Port}, + {Addr, Port} = try + SockMod:peername(StateData#state.socket) + of + {ok, {A, P}} -> {A, P}; + {error, _} -> {unknown, unknown} + catch + _:_ -> {unknown, unknown} + end, + Domains = get_external_hosts(StateData), + Infos = [{direction, in}, {statename, StateName}, + {addr, Addr}, {port, Port}, {streamid, StateData#state.streamid}, {tls, StateData#state.tls}, {tls_enabled, StateData#state.tls_enabled}, {tls_options, StateData#state.tls_options}, {authenticated, StateData#state.authenticated}, - {shaper, StateData#state.shaper}, - {sockmod, SockMod}, - {domains, Domains} - ], + {shaper, StateData#state.shaper}, {sockmod, SockMod}, + {domains, Domains}], Reply = {state_infos, Infos}, - {reply,Reply,StateName,StateData}; - + {reply, Reply, StateName, StateData}; %%---------------------------------------------------------------------- %% Func: handle_sync_event/4 %% Returns: {next_state, NextStateName, NextStateData} | @@ -621,9 +616,9 @@ handle_sync_event(get_state_infos, _From, 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}. @@ -637,15 +632,12 @@ code_change(_OldVsn, StateName, StateData, _Extra) -> handle_info({send_text, Text}, StateName, StateData) -> send_text(StateData, Text), {next_state, StateName, StateData}; - handle_info({timeout, Timer, _}, _StateName, #state{timer = Timer} = StateData) -> {stop, normal, StateData}; - handle_info(_, StateName, StateData) -> {next_state, StateName, StateData}. - %%---------------------------------------------------------------------- %% Func: terminate/3 %% Purpose: Shutdown the fsm @@ -654,21 +646,21 @@ handle_info(_, StateName, StateData) -> terminate(Reason, _StateName, StateData) -> ?DEBUG("terminated: ~p", [Reason]), case Reason of - {process_limit, _} -> - [ejabberd_s2s:external_host_overloaded(Host) || Host <- get_external_hosts(StateData)]; - _ -> - ok + {process_limit, _} -> + [ejabberd_s2s:external_host_overloaded(Host) + || Host <- get_external_hosts(StateData)]; + _ -> ok end, (StateData#state.sockmod):close(StateData#state.socket), ok. get_external_hosts(StateData) -> case StateData#state.authenticated of - true -> - [StateData#state.auth_domain]; - false -> - Connections = StateData#state.connections, - [D || {{D, _}, established} <- dict:to_list(Connections)] + true -> [StateData#state.auth_domain]; + false -> + Connections = StateData#state.connections, + [D + || {{D, _}, established} <- dict:to_list(Connections)] end. %%---------------------------------------------------------------------- @@ -676,168 +668,172 @@ get_external_hosts(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)). - change_shaper(StateData, Host, JID) -> - Shaper = acl:match_rule(Host, StateData#state.shaper, JID), - (StateData#state.sockmod):change_shaper(StateData#state.socket, Shaper). + Shaper = acl:match_rule(Host, StateData#state.shaper, + JID), + (StateData#state.sockmod):change_shaper(StateData#state.socket, + Shaper). - -new_id() -> - randoms:get_string(). +new_id() -> randoms:get_string(). cancel_timer(Timer) -> erlang:cancel_timer(Timer), - receive - {timeout, Timer, _} -> - ok - after 0 -> - ok - end. - - -is_key_packet({xmlelement, Name, Attrs, Els}) when Name == "db:result" -> - {key, - xml:get_attr_s("to", Attrs), - xml:get_attr_s("from", Attrs), - xml:get_attr_s("id", Attrs), - xml:get_cdata(Els)}; -is_key_packet({xmlelement, Name, Attrs, Els}) when Name == "db:verify" -> - {verify, - xml:get_attr_s("to", Attrs), - xml:get_attr_s("from", Attrs), - xml:get_attr_s("id", Attrs), - xml:get_cdata(Els)}; -is_key_packet(_) -> - false. + receive {timeout, Timer, _} -> ok after 0 -> ok end. +is_key_packet(#xmlel{name = Name, attrs = Attrs, + children = Els}) + when Name == <<"db:result">> -> + {key, xml:get_attr_s(<<"to">>, Attrs), + xml:get_attr_s(<<"from">>, Attrs), + xml:get_attr_s(<<"id">>, Attrs), xml:get_cdata(Els)}; +is_key_packet(#xmlel{name = Name, attrs = Attrs, + children = Els}) + when Name == <<"db:verify">> -> + {verify, xml:get_attr_s(<<"to">>, Attrs), + xml:get_attr_s(<<"from">>, Attrs), + xml:get_attr_s(<<"id">>, Attrs), xml:get_cdata(Els)}; +is_key_packet(_) -> false. get_cert_domains(Cert) -> {rdnSequence, Subject} = (Cert#'Certificate'.tbsCertificate)#'TBSCertificate'.subject, Extensions = (Cert#'Certificate'.tbsCertificate)#'TBSCertificate'.extensions, - lists:flatmap( - fun(#'AttributeTypeAndValue'{type = ?'id-at-commonName', - value = Val}) -> - case ?PKIXEXPLICIT:decode('X520CommonName', Val) of - {ok, {_, D1}} -> - D = if - is_list(D1) -> D1; - is_binary(D1) -> binary_to_list(D1); - true -> error - end, - if - D /= error -> - case jlib:string_to_jid(D) of - #jid{luser = "", - lserver = LD, - lresource = ""} -> - [LD]; - _ -> - [] - end; - true -> - [] - end; - _ -> - [] - end; - (_) -> - [] - end, lists:flatten(Subject)) ++ - lists:flatmap( - fun(#'Extension'{extnID = ?'id-ce-subjectAltName', - extnValue = Val}) -> - BVal = if - is_list(Val) -> list_to_binary(Val); - is_binary(Val) -> Val; - true -> Val - end, - case ?PKIXIMPLICIT:decode('SubjectAltName', BVal) of - {ok, SANs} -> - lists:flatmap( - fun({otherName, - #'AnotherName'{'type-id' = ?'id-on-xmppAddr', - value = XmppAddr - }}) -> - case 'XmppAddr':decode( - 'XmppAddr', XmppAddr) of - {ok, D} when is_binary(D) -> - case jlib:string_to_jid( - binary_to_list(D)) of - #jid{luser = "", - lserver = LD, - lresource = ""} -> - case idna:domain_utf8_to_ascii(LD) of - false -> - []; - PCLD -> - [PCLD] - end; - _ -> - [] - end; - _ -> - [] - end; - ({dNSName, D}) when is_list(D) -> - case jlib:string_to_jid(D) of - #jid{luser = "", - lserver = LD, - lresource = ""} -> - [LD]; - _ -> - [] - end; - (_) -> - [] - end, SANs); - _ -> - [] - end; - (_) -> - [] - end, Extensions). + lists:flatmap(fun (#'AttributeTypeAndValue'{type = + ?'id-at-commonName', + value = Val}) -> + case 'OTP-PUB-KEY':decode('X520CommonName', Val) of + {ok, {_, D1}} -> + D = if is_binary(D1) -> D1; + is_binary(D1) -> (D1); + true -> error + end, + if D /= error -> + case jlib:string_to_jid(D) of + #jid{luser = <<"">>, lserver = LD, + lresource = <<"">>} -> + [LD]; + _ -> [] + end; + true -> [] + end; + _ -> [] + end; + (_) -> [] + end, + lists:flatten(Subject)) + ++ + lists:flatmap(fun (#'Extension'{extnID = + ?'id-ce-subjectAltName', + extnValue = Val}) -> + BVal = if is_binary(Val) -> iolist_to_binary(Val); + is_binary(Val) -> Val; + true -> Val + end, + case 'OTP-PUB-KEY':decode('SubjectAltName', BVal) + of + {ok, SANs} -> + lists:flatmap(fun ({otherName, + #'AnotherName'{'type-id' = + ?'id-on-xmppAddr', + value = + XmppAddr}}) -> + case + 'XmppAddr':decode('XmppAddr', + XmppAddr) + of + {ok, D} + when + is_binary(D) -> + case + jlib:string_to_jid((D)) + of + #jid{luser = + <<"">>, + lserver = + LD, + lresource = + <<"">>} -> + case + idna:domain_utf8_to_ascii(LD) + of + false -> + []; + PCLD -> + [PCLD] + end; + _ -> [] + end; + _ -> [] + end; + ({dNSName, D}) + when is_binary(D) -> + case + jlib:string_to_jid(D) + of + #jid{luser = <<"">>, + lserver = LD, + lresource = + <<"">>} -> + [LD]; + _ -> [] + end; + (_) -> [] + end, + SANs); + _ -> [] + end; + (_) -> [] + end, + Extensions). -match_domain(Domain, Domain) -> - true; +match_domain(Domain, Domain) -> true; match_domain(Domain, Pattern) -> - DLabels = string:tokens(Domain, "."), - PLabels = string:tokens(Pattern, "."), + DLabels = str:tokens(Domain, <<".">>), + PLabels = str:tokens(Pattern, <<".">>), match_labels(DLabels, PLabels). -match_labels([], []) -> - true; -match_labels([], [_ | _]) -> - false; -match_labels([_ | _], []) -> - false; +match_labels([], []) -> true; +match_labels([], [_ | _]) -> false; +match_labels([_ | _], []) -> false; match_labels([DL | DLabels], [PL | PLabels]) -> - case lists:all(fun(C) -> (($a =< C) andalso (C =< $z)) - orelse (($0 =< C) andalso (C =< $9)) - orelse (C == $-) orelse (C == $*) - end, PL) of - true -> - Regexp = ejabberd_regexp:sh_to_awk(PL), - case ejabberd_regexp:run(DL, Regexp) of - match -> - match_labels(DLabels, PLabels); - nomatch -> - false - end; - false -> - false + case lists:all(fun (C) -> + $a =< C andalso C =< $z orelse + $0 =< C andalso C =< $9 orelse + C == $- orelse C == $* + end, + binary_to_list(PL)) + of + true -> + Regexp = ejabberd_regexp:sh_to_awk(PL), + case ejabberd_regexp:run(DL, Regexp) of + match -> match_labels(DLabels, PLabels); + nomatch -> false + end; + false -> false + end. + +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, + fun(I) when is_integer(I), I > 0 -> I end) of + undefined -> []; + N -> [{max_queue, N}] + end end. diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl index 0dedb4c26..4bfb0e732 100644 --- a/src/ejabberd_s2s_out.erl +++ b/src/ejabberd_s2s_out.erl @@ -25,6 +25,7 @@ %%%---------------------------------------------------------------------- -module(ejabberd_s2s_out). + -author('alexey@process-one.net'). -behaviour(p1_fsm). @@ -58,30 +59,39 @@ get_addr_port/1]). -include("ejabberd.hrl"). + -include("jlib.hrl"). --record(state, {socket, - streamid, - use_v10, - tls = false, - tls_required = false, - tls_enabled = false, - tls_options = [connect], - authenticated = false, - db_enabled = true, - try_auth = true, - myname, server, queue, - delay_to_retry = undefined_delay, - new = false, verify = false, - bridge, - timer}). +-record(state, + {socket :: ejabberd_socket:socket_state(), + streamid = <<"">> :: binary(), + use_v10 = true :: boolean(), + tls = false :: boolean(), + tls_required = false :: boolean(), + tls_enabled = false :: boolean(), + tls_options = [connect] :: list(), + authenticated = false :: boolean(), + db_enabled = true :: boolean(), + try_auth = true :: boolean(), + myname = <<"">> :: binary(), + server = <<"">> :: binary(), + queue = queue:new() :: queue(), + delay_to_retry = undefined_delay :: undefined_delay | non_neg_integer(), + new = false :: false | binary(), + verify = false :: false | {pid(), binary(), binary()}, + bridge :: {atom(), atom()}, + timer = make_ref() :: reference()}). %%-define(DBGFSM, true). -ifdef(DBGFSM). + -define(FSMOPTS, [{debug, [trace]}]). + -else. + -define(FSMOPTS, []). + -endif. %% Module start with or without supervisor: @@ -103,25 +113,21 @@ -define(MAX_RETRY_DELAY, 300000). -define(STREAM_HEADER, - "" - "" - ). + <<"">>). --define(STREAM_TRAILER, ""). +-define(STREAM_TRAILER, <<"">>). -define(INVALID_NAMESPACE_ERR, - xml:element_to_string(?SERR_INVALID_NAMESPACE)). + xml:element_to_binary(?SERR_INVALID_NAMESPACE)). -define(HOST_UNKNOWN_ERR, - xml:element_to_string(?SERR_HOST_UNKNOWN)). + xml:element_to_binary(?SERR_HOST_UNKNOWN)). -define(INVALID_XML_ERR, - xml:element_to_string(?SERR_XML_NOT_WELL_FORMED)). + xml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)). -define(SOCKET_DEFAULT_RESULT, {error, badarg}). @@ -133,13 +139,11 @@ start(From, Host, Type) -> start_link(From, Host, Type) -> p1_fsm:start_link(ejabberd_s2s_out, [From, Host, Type], - fsm_limit_opts() ++ ?FSMOPTS). + fsm_limit_opts() ++ (?FSMOPTS)). -start_connection(Pid) -> - p1_fsm:send_event(Pid, init). +start_connection(Pid) -> p1_fsm:send_event(Pid, init). -stop_connection(Pid) -> - p1_fsm:send_event(Pid, closed). +stop_connection(Pid) -> p1_fsm:send_event(Pid, closed). %%%---------------------------------------------------------------------- %%% Callback functions from p1_fsm @@ -155,39 +159,47 @@ stop_connection(Pid) -> init([From, Server, Type]) -> process_flag(trap_exit, true), ?DEBUG("started: ~p", [{From, Server, Type}]), - {TLS, TLSRequired} = case ejabberd_config:get_local_option(s2s_use_starttls) of - UseTls when (UseTls==undefined) or (UseTls==false) -> - {false, false}; - UseTls when (UseTls==true) or (UseTls==optional) -> - {true, false}; - UseTls when (UseTls==required) or (UseTls==required_trusted) -> - {true, true} - end, + {TLS, TLSRequired} = case + ejabberd_config:get_local_option( + s2s_use_starttls, + fun(true) -> true; + (false) -> false; + (optional) -> optional; + (required) -> required; + (required_trusted) -> required_trusted + end) + of + UseTls + when (UseTls == undefined) or + (UseTls == false) -> + {false, false}; + UseTls + when (UseTls == true) or (UseTls == optional) -> + {true, false}; + UseTls + when (UseTls == required) or + (UseTls == required_trusted) -> + {true, true} + end, UseV10 = TLS, - TLSOpts = case ejabberd_config:get_local_option(s2s_certfile) of - undefined -> - [connect]; - CertFile -> - [{certfile, CertFile}, connect] + TLSOpts = case + ejabberd_config:get_local_option( + s2s_certfile, fun iolist_to_binary/1) + of + undefined -> [connect]; + CertFile -> [{certfile, CertFile}, connect] end, {New, Verify} = case Type of - {new, Key} -> - {Key, false}; - {verify, Pid, Key, SID} -> - start_connection(self()), - {false, {Pid, Key, SID}} + {new, Key} -> {Key, false}; + {verify, Pid, Key, SID} -> + start_connection(self()), {false, {Pid, Key, SID}} end, Timer = erlang:start_timer(?S2STIMEOUT, self(), []), - {ok, open_socket, #state{use_v10 = UseV10, - tls = TLS, - tls_required = TLSRequired, - tls_options = TLSOpts, - queue = queue:new(), - myname = From, - server = Server, - new = New, - verify = Verify, - timer = Timer}}. + {ok, open_socket, + #state{use_v10 = UseV10, tls = TLS, + tls_required = TLSRequired, tls_options = TLSOpts, + queue = queue:new(), myname = From, server = Server, + new = New, verify = Verify, timer = Timer}}. %%---------------------------------------------------------------------- %% Func: StateName/2 @@ -196,64 +208,64 @@ init([From, Server, Type]) -> %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- open_socket(init, StateData) -> - log_s2s_out(StateData#state.new, - StateData#state.myname, - StateData#state.server, - StateData#state.tls), - ?DEBUG("open_socket: ~p", [{StateData#state.myname, - StateData#state.server, - StateData#state.new, - StateData#state.verify}]), - AddrList = case idna:domain_utf8_to_ascii(StateData#state.server) of - false -> []; - ASCIIAddr -> - get_addr_port(ASCIIAddr) + log_s2s_out(StateData#state.new, StateData#state.myname, + StateData#state.server, StateData#state.tls), + ?DEBUG("open_socket: ~p", + [{StateData#state.myname, StateData#state.server, + StateData#state.new, StateData#state.verify}]), + AddrList = case + idna:domain_utf8_to_ascii(StateData#state.server) + of + false -> []; + ASCIIAddr -> get_addr_port(ASCIIAddr) end, - case lists:foldl(fun({Addr, Port}, Acc) -> + case lists:foldl(fun ({Addr, Port}, Acc) -> case Acc of - {ok, Socket} -> - {ok, Socket}; - _ -> - open_socket1(Addr, Port) + {ok, Socket} -> {ok, Socket}; + _ -> open_socket1(Addr, Port) end - end, ?SOCKET_DEFAULT_RESULT, AddrList) of - {ok, Socket} -> - Version = if - StateData#state.use_v10 -> - " version='1.0'"; - true -> - "" - end, - NewStateData = StateData#state{socket = Socket, - tls_enabled = false, - streamid = new_id()}, - send_text(NewStateData, io_lib:format(?STREAM_HEADER, - [StateData#state.myname, StateData#state.server, - Version])), - {next_state, wait_for_stream, NewStateData, ?FSMTIMEOUT}; - {error, _Reason} -> - ?INFO_MSG("s2s connection: ~s -> ~s (remote server not found)", - [StateData#state.myname, StateData#state.server]), - case ejabberd_hooks:run_fold(find_s2s_bridge, - undefined, - [StateData#state.myname, - StateData#state.server]) of - {Mod, Fun, Type} -> - ?INFO_MSG("found a bridge to ~s for: ~s -> ~s", - [Type, StateData#state.myname, - StateData#state.server]), - NewStateData = StateData#state{bridge={Mod, Fun}}, - {next_state, relay_to_bridge, NewStateData}; - _ -> - wait_before_reconnect(StateData) - end + end, + ?SOCKET_DEFAULT_RESULT, AddrList) + of + {ok, Socket} -> + Version = if StateData#state.use_v10 -> + <<" version='1.0'">>; + true -> <<"">> + end, + NewStateData = StateData#state{socket = Socket, + tls_enabled = false, + streamid = new_id()}, + send_text(NewStateData, + io_lib:format(?STREAM_HEADER, + [StateData#state.myname, + StateData#state.server, Version])), + {next_state, wait_for_stream, NewStateData, + ?FSMTIMEOUT}; + {error, _Reason} -> + ?INFO_MSG("s2s connection: ~s -> ~s (remote server " + "not found)", + [StateData#state.myname, StateData#state.server]), + case ejabberd_hooks:run_fold(find_s2s_bridge, undefined, + [StateData#state.myname, + StateData#state.server]) + of + {Mod, Fun, Type} -> + ?INFO_MSG("found a bridge to ~s for: ~s -> ~s", + [Type, StateData#state.myname, + StateData#state.server]), + NewStateData = StateData#state{bridge = {Mod, Fun}}, + {next_state, relay_to_bridge, NewStateData}; + _ -> wait_before_reconnect(StateData) + end end; open_socket(closed, StateData) -> - ?INFO_MSG("s2s connection: ~s -> ~s (stopped in open socket)", + ?INFO_MSG("s2s connection: ~s -> ~s (stopped in " + "open socket)", [StateData#state.myname, StateData#state.server]), {stop, normal, StateData}; open_socket(timeout, StateData) -> - ?INFO_MSG("s2s connection: ~s -> ~s (timeout in open socket)", + ?INFO_MSG("s2s connection: ~s -> ~s (timeout in " + "open socket)", [StateData#state.myname, StateData#state.server]), {stop, normal, StateData}; open_socket(_, StateData) -> @@ -261,432 +273,483 @@ open_socket(_, StateData) -> %%---------------------------------------------------------------------- %% IPv4 -open_socket1({_,_,_,_} = Addr, Port) -> +open_socket1({_, _, _, _} = Addr, Port) -> open_socket2(inet, Addr, Port); - %% IPv6 -open_socket1({_,_,_,_,_,_,_,_} = Addr, Port) -> +open_socket1({_, _, _, _, _, _, _, _} = Addr, Port) -> open_socket2(inet6, Addr, Port); - %% Hostname open_socket1(Host, Port) -> - lists:foldl(fun(_Family, {ok, _Socket} = R) -> - R; - (Family, _) -> + lists:foldl(fun (_Family, {ok, _Socket} = R) -> R; + (Family, _) -> Addrs = get_addrs(Host, Family), - lists:foldl(fun(_Addr, {ok, _Socket} = R) -> - R; - (Addr, _) -> - open_socket1(Addr, Port) - end, ?SOCKET_DEFAULT_RESULT, Addrs) - end, ?SOCKET_DEFAULT_RESULT, outgoing_s2s_families()). + lists:foldl(fun (_Addr, {ok, _Socket} = R) -> R; + (Addr, _) -> open_socket1(Addr, Port) + end, + ?SOCKET_DEFAULT_RESULT, Addrs) + end, + ?SOCKET_DEFAULT_RESULT, outgoing_s2s_families()). open_socket2(Type, Addr, Port) -> ?DEBUG("s2s_out: connecting to ~p:~p~n", [Addr, Port]), Timeout = outgoing_s2s_timeout(), - SockOpts = try erlang:system_info(otp_release) >= "R13B" of - true -> [{send_timeout_close, true}]; - false -> [] + SockOpts = try erlang:system_info(otp_release) >= "R13B" + of + true -> [{send_timeout_close, true}]; + false -> [] catch - _:_ -> [] + _:_ -> [] end, - case (catch ejabberd_socket:connect(Addr, Port, - [binary, {packet, 0}, - {send_timeout, ?TCP_SEND_TIMEOUT}, - {active, false}, Type | SockOpts], - Timeout)) of - {ok, _Socket} = R -> R; - {error, Reason} = R -> - ?DEBUG("s2s_out: connect return ~p~n", [Reason]), - R; - {'EXIT', Reason} -> - ?DEBUG("s2s_out: connect crashed ~p~n", [Reason]), - {error, Reason} + case catch ejabberd_socket:connect(Addr, Port, + [binary, {packet, 0}, + {send_timeout, ?TCP_SEND_TIMEOUT}, + {active, false}, Type + | SockOpts], + Timeout) + of + {ok, _Socket} = R -> R; + {error, Reason} = R -> + ?DEBUG("s2s_out: connect return ~p~n", [Reason]), R; + {'EXIT', Reason} -> + ?DEBUG("s2s_out: connect crashed ~p~n", [Reason]), + {error, Reason} end. %%---------------------------------------------------------------------- - -wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> - case {xml:get_attr_s("xmlns", Attrs), - xml:get_attr_s("xmlns:db", Attrs), - xml:get_attr_s("version", Attrs) == "1.0"} of - {"jabber:server", "jabber:server:dialback", false} -> - send_db_request(StateData); - {"jabber:server", "jabber:server:dialback", true} when - StateData#state.use_v10 -> - {next_state, wait_for_features, StateData, ?FSMTIMEOUT}; - %% Clause added to handle Tigase's workaround for an old ejabberd bug: - {"jabber:server", "jabber:server:dialback", true} when - not StateData#state.use_v10 -> - send_db_request(StateData); - {"jabber:server", "", true} when StateData#state.use_v10 -> - {next_state, wait_for_features, StateData#state{db_enabled = false}, ?FSMTIMEOUT}; - {NSProvided, DB, _} -> - send_text(StateData, ?INVALID_NAMESPACE_ERR), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid namespace).~n" - "Namespace provided: ~p~nNamespace expected: \"jabber:server\"~n" - "xmlns:db provided: ~p~nAll attributes: ~p", - [StateData#state.myname, StateData#state.server, NSProvided, DB, Attrs]), - {stop, normal, StateData} +wait_for_stream({xmlstreamstart, _Name, Attrs}, + StateData) -> + case {xml:get_attr_s(<<"xmlns">>, Attrs), + xml:get_attr_s(<<"xmlns:db">>, Attrs), + xml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>} + of + {<<"jabber:server">>, <<"jabber:server:dialback">>, + false} -> + send_db_request(StateData); + {<<"jabber:server">>, <<"jabber:server:dialback">>, + true} + when StateData#state.use_v10 -> + {next_state, wait_for_features, StateData, ?FSMTIMEOUT}; + %% Clause added to handle Tigase's workaround for an old ejabberd bug: + {<<"jabber:server">>, <<"jabber:server:dialback">>, + true} + when not StateData#state.use_v10 -> + send_db_request(StateData); + {<<"jabber:server">>, <<"">>, true} + when StateData#state.use_v10 -> + {next_state, wait_for_features, + StateData#state{db_enabled = false}, ?FSMTIMEOUT}; + {NSProvided, DB, _} -> + send_text(StateData, ?INVALID_NAMESPACE_ERR), + ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid " + "namespace).~nNamespace provided: ~p~nNamespac" + "e expected: \"jabber:server\"~nxmlns:db " + "provided: ~p~nAll attributes: ~p", + [StateData#state.myname, StateData#state.server, + NSProvided, DB, Attrs]), + {stop, normal, StateData} end; - wait_for_stream({xmlstreamerror, _}, StateData) -> send_text(StateData, - ?INVALID_XML_ERR ++ ?STREAM_TRAILER), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid xml)", + <<(?INVALID_XML_ERR)/binary, + (?STREAM_TRAILER)/binary>>), + ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid " + "xml)", [StateData#state.myname, StateData#state.server]), {stop, normal, StateData}; - -wait_for_stream({xmlstreamend,_Name}, StateData) -> +wait_for_stream({xmlstreamend, _Name}, StateData) -> ?INFO_MSG("Closing s2s connection: ~s -> ~s (xmlstreamend)", [StateData#state.myname, StateData#state.server]), {stop, normal, StateData}; - wait_for_stream(timeout, StateData) -> - ?INFO_MSG("Closing s2s connection: ~s -> ~s (timeout in wait_for_stream)", + ?INFO_MSG("Closing s2s connection: ~s -> ~s (timeout " + "in wait_for_stream)", [StateData#state.myname, StateData#state.server]), {stop, normal, StateData}; - wait_for_stream(closed, StateData) -> - ?INFO_MSG("Closing s2s connection: ~s -> ~s (close in wait_for_stream)", + ?INFO_MSG("Closing s2s connection: ~s -> ~s (close " + "in wait_for_stream)", [StateData#state.myname, StateData#state.server]), {stop, normal, StateData}. - - -wait_for_validation({xmlstreamelement, El}, StateData) -> +wait_for_validation({xmlstreamelement, El}, + StateData) -> case is_verify_res(El) of - {result, To, From, Id, Type} -> - ?DEBUG("recv result: ~p", [{From, To, Id, Type}]), - case {Type, StateData#state.tls_enabled, StateData#state.tls_required} of - {"valid", Enabled, Required} when (Enabled==true) or (Required==false) -> - send_queue(StateData, StateData#state.queue), - ?INFO_MSG("Connection established: ~s -> ~s with TLS=~p", - [StateData#state.myname, StateData#state.server, StateData#state.tls_enabled]), - ejabberd_hooks:run(s2s_connect_hook, - [StateData#state.myname, - StateData#state.server]), - {next_state, stream_established, - StateData#state{queue = queue:new()}}; - {"valid", Enabled, Required} when (Enabled==false) and (Required==true) -> - %% TODO: bounce packets - ?INFO_MSG("Closing s2s connection: ~s -> ~s (TLS is required but unavailable)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}; - _ -> - %% TODO: bounce packets - ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid dialback key)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData} - end; - {verify, To, From, Id, Type} -> - ?DEBUG("recv verify: ~p", [{From, To, Id, Type}]), - case StateData#state.verify of - false -> - NextState = wait_for_validation, - %% TODO: Should'nt we close the connection here ? - {next_state, NextState, StateData, - get_timeout_interval(NextState)}; - {Pid, _Key, _SID} -> - case Type of - "valid" -> - p1_fsm:send_event( - Pid, {valid, - StateData#state.server, - StateData#state.myname}); - _ -> - p1_fsm:send_event( - Pid, {invalid, - StateData#state.server, - StateData#state.myname}) - end, - if - StateData#state.verify == false -> - {stop, normal, StateData}; - true -> - NextState = wait_for_validation, - {next_state, NextState, StateData, - get_timeout_interval(NextState)} - end - end; - _ -> - {next_state, wait_for_validation, StateData, ?FSMTIMEOUT*3} + {result, To, From, Id, Type} -> + ?DEBUG("recv result: ~p", [{From, To, Id, Type}]), + case {Type, StateData#state.tls_enabled, + StateData#state.tls_required} + of + {<<"valid">>, Enabled, Required} + when (Enabled == true) or (Required == false) -> + send_queue(StateData, StateData#state.queue), + ?INFO_MSG("Connection established: ~s -> ~s with " + "TLS=~p", + [StateData#state.myname, StateData#state.server, + StateData#state.tls_enabled]), + ejabberd_hooks:run(s2s_connect_hook, + [StateData#state.myname, + StateData#state.server]), + {next_state, stream_established, + StateData#state{queue = queue:new()}}; + {<<"valid">>, Enabled, Required} + when (Enabled == false) and (Required == true) -> + ?INFO_MSG("Closing s2s connection: ~s -> ~s (TLS " + "is required but unavailable)", + [StateData#state.myname, StateData#state.server]), + {stop, normal, StateData}; + _ -> + ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid " + "dialback key)", + [StateData#state.myname, StateData#state.server]), + {stop, normal, StateData} + end; + {verify, To, From, Id, Type} -> + ?DEBUG("recv verify: ~p", [{From, To, Id, Type}]), + case StateData#state.verify of + false -> + NextState = wait_for_validation, + {next_state, NextState, StateData, + get_timeout_interval(NextState)}; + {Pid, _Key, _SID} -> + case Type of + <<"valid">> -> + p1_fsm:send_event(Pid, + {valid, StateData#state.server, + StateData#state.myname}); + _ -> + p1_fsm:send_event(Pid, + {invalid, StateData#state.server, + StateData#state.myname}) + end, + if StateData#state.verify == false -> + {stop, normal, StateData}; + true -> + NextState = wait_for_validation, + {next_state, NextState, StateData, + get_timeout_interval(NextState)} + end + end; + _ -> + {next_state, wait_for_validation, StateData, + (?FSMTIMEOUT) * 3} end; - wait_for_validation({xmlstreamend, _Name}, StateData) -> ?INFO_MSG("wait for validation: ~s -> ~s (xmlstreamend)", [StateData#state.myname, StateData#state.server]), {stop, normal, StateData}; - wait_for_validation({xmlstreamerror, _}, StateData) -> ?INFO_MSG("wait for validation: ~s -> ~s (xmlstreamerror)", [StateData#state.myname, StateData#state.server]), send_text(StateData, - ?INVALID_XML_ERR ++ ?STREAM_TRAILER), + <<(?INVALID_XML_ERR)/binary, + (?STREAM_TRAILER)/binary>>), {stop, normal, StateData}; - -wait_for_validation(timeout, #state{verify = {VPid, VKey, SID}} = StateData) - when is_pid(VPid) and is_list(VKey) and is_list(SID) -> - %% This is an auxiliary s2s connection for dialback. - %% This timeout is normal and doesn't represent a problem. - ?DEBUG("wait_for_validation: ~s -> ~s (timeout in verify connection)", +wait_for_validation(timeout, + #state{verify = {VPid, VKey, SID}} = StateData) + when is_pid(VPid) and is_binary(VKey) and + is_binary(SID) -> + ?DEBUG("wait_for_validation: ~s -> ~s (timeout " + "in verify connection)", [StateData#state.myname, StateData#state.server]), {stop, normal, StateData}; - wait_for_validation(timeout, StateData) -> - ?INFO_MSG("wait_for_validation: ~s -> ~s (connect timeout)", + ?INFO_MSG("wait_for_validation: ~s -> ~s (connect " + "timeout)", [StateData#state.myname, StateData#state.server]), {stop, normal, StateData}; - wait_for_validation(closed, StateData) -> ?INFO_MSG("wait for validation: ~s -> ~s (closed)", [StateData#state.myname, StateData#state.server]), {stop, normal, StateData}. - wait_for_features({xmlstreamelement, El}, StateData) -> case El of - {xmlelement, "stream:features", _Attrs, Els} -> - {SASLEXT, StartTLS, StartTLSRequired} = - lists:foldl( - fun({xmlelement, "mechanisms", Attrs1, Els1} = _El1, - {_SEXT, STLS, STLSReq} = Acc) -> - case xml:get_attr_s("xmlns", Attrs1) of - ?NS_SASL -> - NewSEXT = - lists:any( - fun({xmlelement, "mechanism", _, Els2}) -> - case xml:get_cdata(Els2) of - "EXTERNAL" -> true; - _ -> false - end; - (_) -> false - end, Els1), - {NewSEXT, STLS, STLSReq}; - _ -> - Acc - end; - ({xmlelement, "starttls", Attrs1, _Els1} = El1, - {SEXT, _STLS, _STLSReq} = Acc) -> - case xml:get_attr_s("xmlns", Attrs1) of - ?NS_TLS -> - Req = case xml:get_subtag(El1, "required") of - {xmlelement, _, _, _} -> true; - false -> false - end, - {SEXT, true, Req}; - _ -> - Acc - end; - (_, Acc) -> - Acc - end, {false, false, false}, Els), - if - (not SASLEXT) and (not StartTLS) and - StateData#state.authenticated -> - send_queue(StateData, StateData#state.queue), - ?INFO_MSG("Connection established: ~s -> ~s", - [StateData#state.myname, StateData#state.server]), - ejabberd_hooks:run(s2s_connect_hook, - [StateData#state.myname, - StateData#state.server]), - {next_state, stream_established, - StateData#state{queue = queue:new()}}; - SASLEXT and StateData#state.try_auth and - (StateData#state.new /= false) -> - send_element(StateData, - {xmlelement, "auth", - [{"xmlns", ?NS_SASL}, - {"mechanism", "EXTERNAL"}], - [{xmlcdata, - jlib:encode_base64( - StateData#state.myname)}]}), - {next_state, wait_for_auth_result, - StateData#state{try_auth = false}, ?FSMTIMEOUT}; - StartTLS and StateData#state.tls and - (not StateData#state.tls_enabled) -> - send_element(StateData, - {xmlelement, "starttls", - [{"xmlns", ?NS_TLS}], []}), - {next_state, wait_for_starttls_proceed, StateData, - ?FSMTIMEOUT}; - StartTLSRequired and (not StateData#state.tls) -> - ?DEBUG("restarted: ~p", [{StateData#state.myname, - StateData#state.server}]), - ejabberd_socket:close(StateData#state.socket), - {next_state, reopen_socket, - StateData#state{socket = undefined, - use_v10 = false}, ?FSMTIMEOUT}; - StateData#state.db_enabled -> - send_db_request(StateData); - true -> - ?DEBUG("restarted: ~p", [{StateData#state.myname, - StateData#state.server}]), - % TODO: clear message queue - ejabberd_socket:close(StateData#state.socket), - {next_state, reopen_socket, StateData#state{socket = undefined, - use_v10 = false}, ?FSMTIMEOUT} - end; - _ -> - send_text(StateData, - xml:element_to_string(?SERR_BAD_FORMAT) ++ - ?STREAM_TRAILER), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData} + #xmlel{name = <<"stream:features">>, children = Els} -> + {SASLEXT, StartTLS, StartTLSRequired} = lists:foldl(fun + (#xmlel{name = + <<"mechanisms">>, + attrs = + Attrs1, + children + = + Els1} = + _El1, + {_SEXT, STLS, + STLSReq} = + Acc) -> + case + xml:get_attr_s(<<"xmlns">>, + Attrs1) + of + ?NS_SASL -> + NewSEXT = + lists:any(fun + (#xmlel{name + = + <<"mechanism">>, + children + = + Els2}) -> + case + xml:get_cdata(Els2) + of + <<"EXTERNAL">> -> + true; + _ -> + false + end; + (_) -> + false + end, + Els1), + {NewSEXT, + STLS, + STLSReq}; + _ -> Acc + end; + (#xmlel{name = + <<"starttls">>, + attrs = + Attrs1} = + El1, + {SEXT, _STLS, + _STLSReq} = + Acc) -> + case + xml:get_attr_s(<<"xmlns">>, + Attrs1) + of + ?NS_TLS -> + Req = + case + xml:get_subtag(El1, + <<"required">>) + of + #xmlel{} -> + true; + false -> + false + end, + {SEXT, + true, + Req}; + _ -> Acc + end; + (_, Acc) -> Acc + end, + {false, false, + false}, + Els), + if not SASLEXT and not StartTLS and + StateData#state.authenticated -> + send_queue(StateData, StateData#state.queue), + ?INFO_MSG("Connection established: ~s -> ~s", + [StateData#state.myname, StateData#state.server]), + ejabberd_hooks:run(s2s_connect_hook, + [StateData#state.myname, + StateData#state.server]), + {next_state, stream_established, + StateData#state{queue = queue:new()}}; + SASLEXT and StateData#state.try_auth and + (StateData#state.new /= false) -> + send_element(StateData, + #xmlel{name = <<"auth">>, + attrs = + [{<<"xmlns">>, ?NS_SASL}, + {<<"mechanism">>, <<"EXTERNAL">>}], + children = + [{xmlcdata, + jlib:encode_base64(StateData#state.myname)}]}), + {next_state, wait_for_auth_result, + StateData#state{try_auth = false}, ?FSMTIMEOUT}; + StartTLS and StateData#state.tls and + not StateData#state.tls_enabled -> + send_element(StateData, + #xmlel{name = <<"starttls">>, + attrs = [{<<"xmlns">>, ?NS_TLS}], + children = []}), + {next_state, wait_for_starttls_proceed, StateData, + ?FSMTIMEOUT}; + StartTLSRequired and not StateData#state.tls -> + ?DEBUG("restarted: ~p", + [{StateData#state.myname, StateData#state.server}]), + ejabberd_socket:close(StateData#state.socket), + {next_state, reopen_socket, + StateData#state{socket = undefined, use_v10 = false}, + ?FSMTIMEOUT}; + StateData#state.db_enabled -> + send_db_request(StateData); + true -> + ?DEBUG("restarted: ~p", + [{StateData#state.myname, StateData#state.server}]), + ejabberd_socket:close(StateData#state.socket), + {next_state, reopen_socket, + StateData#state{socket = undefined, use_v10 = false}, + ?FSMTIMEOUT} + end; + _ -> + send_text(StateData, + <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary, + (?STREAM_TRAILER)/binary>>), + ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad " + "format)", + [StateData#state.myname, StateData#state.server]), + {stop, normal, StateData} end; - wait_for_features({xmlstreamend, _Name}, StateData) -> ?INFO_MSG("wait_for_features: xmlstreamend", []), {stop, normal, StateData}; - wait_for_features({xmlstreamerror, _}, StateData) -> send_text(StateData, - ?INVALID_XML_ERR ++ ?STREAM_TRAILER), + <<(?INVALID_XML_ERR)/binary, + (?STREAM_TRAILER)/binary>>), ?INFO_MSG("wait for features: xmlstreamerror", []), {stop, normal, StateData}; - wait_for_features(timeout, StateData) -> ?INFO_MSG("wait for features: timeout", []), {stop, normal, StateData}; - wait_for_features(closed, StateData) -> ?INFO_MSG("wait for features: closed", []), {stop, normal, StateData}. - -wait_for_auth_result({xmlstreamelement, El}, StateData) -> +wait_for_auth_result({xmlstreamelement, El}, + StateData) -> case El of - {xmlelement, "success", Attrs, _Els} -> - case xml:get_attr_s("xmlns", Attrs) of - ?NS_SASL -> - ?DEBUG("auth: ~p", [{StateData#state.myname, - StateData#state.server}]), - ejabberd_socket:reset_stream(StateData#state.socket), - send_text(StateData, - io_lib:format(?STREAM_HEADER, - [StateData#state.myname, StateData#state.server, - " version='1.0'"])), - {next_state, wait_for_stream, - StateData#state{streamid = new_id(), - authenticated = true - }, ?FSMTIMEOUT}; - _ -> - send_text(StateData, - xml:element_to_string(?SERR_BAD_FORMAT) ++ - ?STREAM_TRAILER), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData} - end; - {xmlelement, "failure", Attrs, _Els} -> - case xml:get_attr_s("xmlns", Attrs) of - ?NS_SASL -> - ?DEBUG("restarted: ~p", [{StateData#state.myname, - StateData#state.server}]), - ejabberd_socket:close(StateData#state.socket), - {next_state, reopen_socket, - StateData#state{socket = undefined}, ?FSMTIMEOUT}; - _ -> - send_text(StateData, - xml:element_to_string(?SERR_BAD_FORMAT) ++ - ?STREAM_TRAILER), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData} - end; - _ -> - send_text(StateData, - xml:element_to_string(?SERR_BAD_FORMAT) ++ - ?STREAM_TRAILER), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData} + #xmlel{name = <<"success">>, attrs = Attrs} -> + case xml:get_attr_s(<<"xmlns">>, Attrs) of + ?NS_SASL -> + ?DEBUG("auth: ~p", + [{StateData#state.myname, StateData#state.server}]), + ejabberd_socket:reset_stream(StateData#state.socket), + send_text(StateData, + io_lib:format(?STREAM_HEADER, + [StateData#state.myname, + StateData#state.server, + <<" version='1.0'">>])), + {next_state, wait_for_stream, + StateData#state{streamid = new_id(), + authenticated = true}, + ?FSMTIMEOUT}; + _ -> + send_text(StateData, + <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary, + (?STREAM_TRAILER)/binary>>), + ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad " + "format)", + [StateData#state.myname, StateData#state.server]), + {stop, normal, StateData} + end; + #xmlel{name = <<"failure">>, attrs = Attrs} -> + case xml:get_attr_s(<<"xmlns">>, Attrs) of + ?NS_SASL -> + ?DEBUG("restarted: ~p", + [{StateData#state.myname, StateData#state.server}]), + ejabberd_socket:close(StateData#state.socket), + {next_state, reopen_socket, + StateData#state{socket = undefined}, ?FSMTIMEOUT}; + _ -> + send_text(StateData, + <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary, + (?STREAM_TRAILER)/binary>>), + ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad " + "format)", + [StateData#state.myname, StateData#state.server]), + {stop, normal, StateData} + end; + _ -> + send_text(StateData, + <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary, + (?STREAM_TRAILER)/binary>>), + ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad " + "format)", + [StateData#state.myname, StateData#state.server]), + {stop, normal, StateData} end; - -wait_for_auth_result({xmlstreamend, _Name}, StateData) -> +wait_for_auth_result({xmlstreamend, _Name}, + StateData) -> ?INFO_MSG("wait for auth result: xmlstreamend", []), {stop, normal, StateData}; - wait_for_auth_result({xmlstreamerror, _}, StateData) -> send_text(StateData, - ?INVALID_XML_ERR ++ ?STREAM_TRAILER), + <<(?INVALID_XML_ERR)/binary, + (?STREAM_TRAILER)/binary>>), ?INFO_MSG("wait for auth result: xmlstreamerror", []), {stop, normal, StateData}; - wait_for_auth_result(timeout, StateData) -> ?INFO_MSG("wait for auth result: timeout", []), {stop, normal, StateData}; - wait_for_auth_result(closed, StateData) -> ?INFO_MSG("wait for auth result: closed", []), {stop, normal, StateData}. - -wait_for_starttls_proceed({xmlstreamelement, El}, StateData) -> +wait_for_starttls_proceed({xmlstreamelement, El}, + StateData) -> case El of - {xmlelement, "proceed", Attrs, _Els} -> - case xml:get_attr_s("xmlns", Attrs) of - ?NS_TLS -> - ?DEBUG("starttls: ~p", [{StateData#state.myname, - StateData#state.server}]), - Socket = StateData#state.socket, - TLSOpts = case ejabberd_config:get_local_option( - {domain_certfile, - StateData#state.myname}) of - undefined -> - StateData#state.tls_options; - CertFile -> - [{certfile, CertFile} | - lists:keydelete( - certfile, 1, - StateData#state.tls_options)] - end, - TLSSocket = ejabberd_socket:starttls(Socket, TLSOpts), - NewStateData = StateData#state{socket = TLSSocket, - streamid = new_id(), - tls_enabled = true, - tls_options = TLSOpts - }, - send_text(NewStateData, - io_lib:format(?STREAM_HEADER, - [StateData#state.myname, StateData#state.server, - " version='1.0'"])), - {next_state, wait_for_stream, NewStateData, ?FSMTIMEOUT}; - _ -> - send_text(StateData, - xml:element_to_string(?SERR_BAD_FORMAT) ++ - ?STREAM_TRAILER), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData} - end; - _ -> - ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData} + #xmlel{name = <<"proceed">>, attrs = Attrs} -> + case xml:get_attr_s(<<"xmlns">>, Attrs) of + ?NS_TLS -> + ?DEBUG("starttls: ~p", + [{StateData#state.myname, StateData#state.server}]), + Socket = StateData#state.socket, + TLSOpts = case + ejabberd_config:get_local_option( + {domain_certfile, StateData#state.myname}, + fun iolist_to_binary/1) + of + undefined -> StateData#state.tls_options; + CertFile -> + [{certfile, CertFile} + | lists:keydelete(certfile, 1, + StateData#state.tls_options)] + end, + TLSSocket = ejabberd_socket:starttls(Socket, TLSOpts), + NewStateData = StateData#state{socket = TLSSocket, + streamid = new_id(), + tls_enabled = true, + tls_options = TLSOpts}, + send_text(NewStateData, + io_lib:format(?STREAM_HEADER, + [StateData#state.myname, + StateData#state.server, + <<" version='1.0'">>])), + {next_state, wait_for_stream, NewStateData, + ?FSMTIMEOUT}; + _ -> + send_text(StateData, + <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary, + (?STREAM_TRAILER)/binary>>), + ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad " + "format)", + [StateData#state.myname, StateData#state.server]), + {stop, normal, StateData} + end; + _ -> + ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad " + "format)", + [StateData#state.myname, StateData#state.server]), + {stop, normal, StateData} end; - -wait_for_starttls_proceed({xmlstreamend, _Name}, StateData) -> - ?INFO_MSG("wait for starttls proceed: xmlstreamend", []), +wait_for_starttls_proceed({xmlstreamend, _Name}, + StateData) -> + ?INFO_MSG("wait for starttls proceed: xmlstreamend", + []), {stop, normal, StateData}; - -wait_for_starttls_proceed({xmlstreamerror, _}, StateData) -> +wait_for_starttls_proceed({xmlstreamerror, _}, + StateData) -> send_text(StateData, - ?INVALID_XML_ERR ++ ?STREAM_TRAILER), - ?INFO_MSG("wait for starttls proceed: xmlstreamerror", []), + <<(?INVALID_XML_ERR)/binary, + (?STREAM_TRAILER)/binary>>), + ?INFO_MSG("wait for starttls proceed: xmlstreamerror", + []), {stop, normal, StateData}; - wait_for_starttls_proceed(timeout, StateData) -> ?INFO_MSG("wait for starttls proceed: timeout", []), {stop, normal, StateData}; - wait_for_starttls_proceed(closed, StateData) -> ?INFO_MSG("wait for starttls proceed: closed", []), {stop, normal, StateData}. - reopen_socket({xmlstreamelement, _El}, StateData) -> {next_state, reopen_socket, StateData, ?FSMTIMEOUT}; reopen_socket({xmlstreamend, _Name}, StateData) -> @@ -716,54 +779,46 @@ relay_to_bridge(_Event, StateData) -> stream_established({xmlstreamelement, El}, StateData) -> ?DEBUG("s2S stream established", []), case is_verify_res(El) of - {verify, VTo, VFrom, VId, VType} -> - ?DEBUG("recv verify: ~p", [{VFrom, VTo, VId, VType}]), - case StateData#state.verify of - {VPid, _VKey, _SID} -> - case VType of - "valid" -> - p1_fsm:send_event( - VPid, {valid, - StateData#state.server, - StateData#state.myname}); - _ -> - p1_fsm:send_event( - VPid, {invalid, - StateData#state.server, - StateData#state.myname}) - end; - _ -> - ok - end; - _ -> - ok + {verify, VTo, VFrom, VId, VType} -> + ?DEBUG("recv verify: ~p", [{VFrom, VTo, VId, VType}]), + case StateData#state.verify of + {VPid, _VKey, _SID} -> + case VType of + <<"valid">> -> + p1_fsm:send_event(VPid, + {valid, StateData#state.server, + StateData#state.myname}); + _ -> + p1_fsm:send_event(VPid, + {invalid, StateData#state.server, + StateData#state.myname}) + end; + _ -> ok + end; + _ -> ok end, {next_state, stream_established, StateData}; - stream_established({xmlstreamend, _Name}, StateData) -> - ?INFO_MSG("Connection closed in stream established: ~s -> ~s (xmlstreamend)", + ?INFO_MSG("Connection closed in stream established: " + "~s -> ~s (xmlstreamend)", [StateData#state.myname, StateData#state.server]), {stop, normal, StateData}; - stream_established({xmlstreamerror, _}, StateData) -> send_text(StateData, - ?INVALID_XML_ERR ++ ?STREAM_TRAILER), + <<(?INVALID_XML_ERR)/binary, + (?STREAM_TRAILER)/binary>>), ?INFO_MSG("stream established: ~s -> ~s (xmlstreamerror)", [StateData#state.myname, StateData#state.server]), {stop, normal, StateData}; - stream_established(timeout, StateData) -> ?INFO_MSG("stream established: ~s -> ~s (timeout)", [StateData#state.myname, StateData#state.server]), {stop, normal, StateData}; - stream_established(closed, StateData) -> ?INFO_MSG("stream established: ~s -> ~s (closed)", [StateData#state.myname, StateData#state.server]), {stop, normal, StateData}. - - %%---------------------------------------------------------------------- %% Func: StateName/3 %% Returns: {next_state, NextStateName, NextStateData} | @@ -784,27 +839,27 @@ stream_established(closed, StateData) -> %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- handle_event(_Event, StateName, StateData) -> - {next_state, StateName, StateData, get_timeout_interval(StateName)}. - %%---------------------------------------------------------------------- %% Func: handle_sync_event/4 %% Returns: The associated StateData for this connection %% {reply, Reply, NextStateName, NextStateData} %% Reply = {state_infos, [{InfoName::atom(), InfoValue::any()] %%---------------------------------------------------------------------- -handle_sync_event(get_state_infos, _From, StateName, StateData) -> - {Addr,Port} = try ejabberd_socket:peername(StateData#state.socket) of - {ok, {A,P}} -> {A,P}; - {error, _} -> {unknown,unknown} - catch - _:_ -> - {unknown,unknown} - end, - Infos = [ - {direction, out}, - {statename, StateName}, - {addr, Addr}, - {port, Port}, + {next_state, StateName, StateData, + get_timeout_interval(StateName)}. + +handle_sync_event(get_state_infos, _From, StateName, + StateData) -> + {Addr, Port} = try + ejabberd_socket:peername(StateData#state.socket) + of + {ok, {A, P}} -> {A, P}; + {error, _} -> {unknown, unknown} + catch + _:_ -> {unknown, unknown} + end, + Infos = [{direction, out}, {statename, StateName}, + {addr, Addr}, {port, Port}, {streamid, StateData#state.streamid}, {use_v10, StateData#state.use_v10}, {tls, StateData#state.tls}, @@ -817,11 +872,9 @@ handle_sync_event(get_state_infos, _From, StateName, StateData) -> {myname, StateData#state.myname}, {server, StateData#state.server}, {delay_to_retry, StateData#state.delay_to_retry}, - {verify, StateData#state.verify} - ], + {verify, StateData#state.verify}], Reply = {state_infos, Infos}, - {reply,Reply,StateName,StateData}; - + {reply, Reply, StateName, StateData}; %%---------------------------------------------------------------------- %% Func: handle_sync_event/4 %% Returns: {next_state, NextStateName, NextStateData} | @@ -831,9 +884,11 @@ handle_sync_event(get_state_infos, _From, StateName, StateData) -> %% {stop, Reason, NewStateData} | %% {stop, Reason, Reply, NewStateData} %%---------------------------------------------------------------------- -handle_sync_event(_Event, _From, StateName, StateData) -> +handle_sync_event(_Event, _From, StateName, + StateData) -> Reply = ok, - {reply, Reply, StateName, StateData, get_timeout_interval(StateName)}. + {reply, Reply, StateName, StateData, + get_timeout_interval(StateName)}. code_change(_OldVsn, StateName, StateData, _Extra) -> {ok, StateName, StateData}. @@ -850,56 +905,55 @@ handle_info({send_text, Text}, StateName, StateData) -> Timer = erlang:start_timer(?S2STIMEOUT, self(), []), {next_state, StateName, StateData#state{timer = Timer}, get_timeout_interval(StateName)}; - handle_info({send_element, El}, StateName, StateData) -> case StateName of - stream_established -> - cancel_timer(StateData#state.timer), - Timer = erlang:start_timer(?S2STIMEOUT, self(), []), - send_element(StateData, El), - {next_state, StateName, StateData#state{timer = Timer}}; - %% In this state we bounce all message: We are waiting before - %% trying to reconnect - wait_before_retry -> - bounce_element(El, ?ERR_REMOTE_SERVER_NOT_FOUND), - {next_state, StateName, StateData}; - relay_to_bridge -> - %% In this state we relay all outbound messages - %% to a foreign protocol bridge such as SMTP, SIP, etc. - {Mod, Fun} = StateData#state.bridge, - ?DEBUG("relaying stanza via ~p:~p/1", [Mod, Fun]), - case catch Mod:Fun(El) of - {'EXIT', Reason} -> - ?ERROR_MSG("Error while relaying to bridge: ~p", [Reason]), - bounce_element(El, ?ERR_INTERNAL_SERVER_ERROR), - wait_before_reconnect(StateData); - _ -> - {next_state, StateName, StateData} - end; - _ -> - Q = queue:in(El, StateData#state.queue), - {next_state, StateName, StateData#state{queue = Q}, - get_timeout_interval(StateName)} + stream_established -> + cancel_timer(StateData#state.timer), + Timer = erlang:start_timer(?S2STIMEOUT, self(), []), + send_element(StateData, El), + {next_state, StateName, StateData#state{timer = Timer}}; + %% In this state we bounce all message: We are waiting before + %% trying to reconnect + wait_before_retry -> + bounce_element(El, ?ERR_REMOTE_SERVER_NOT_FOUND), + {next_state, StateName, StateData}; + relay_to_bridge -> + {Mod, Fun} = StateData#state.bridge, + ?DEBUG("relaying stanza via ~p:~p/1", [Mod, Fun]), + case catch Mod:Fun(El) of + {'EXIT', Reason} -> + ?ERROR_MSG("Error while relaying to bridge: ~p", + [Reason]), + bounce_element(El, ?ERR_INTERNAL_SERVER_ERROR), + wait_before_reconnect(StateData); + _ -> {next_state, StateName, StateData} + end; + _ -> + Q = queue:in(El, StateData#state.queue), + {next_state, StateName, StateData#state{queue = Q}, + get_timeout_interval(StateName)} end; - handle_info({timeout, Timer, _}, wait_before_retry, #state{timer = Timer} = StateData) -> - ?INFO_MSG("Reconnect delay expired: Will now retry to connect to ~s when needed.", [StateData#state.server]), + ?INFO_MSG("Reconnect delay expired: Will now retry " + "to connect to ~s when needed.", + [StateData#state.server]), {stop, normal, StateData}; - handle_info({timeout, Timer, _}, _StateName, #state{timer = Timer} = StateData) -> - ?INFO_MSG("Closing connection with ~s: timeout", [StateData#state.server]), + ?INFO_MSG("Closing connection with ~s: timeout", + [StateData#state.server]), {stop, normal, StateData}; - -handle_info(terminate_if_waiting_before_retry, wait_before_retry, StateData) -> +handle_info(terminate_if_waiting_before_retry, + wait_before_retry, StateData) -> {stop, normal, StateData}; - -handle_info(terminate_if_waiting_before_retry, StateName, StateData) -> - {next_state, StateName, StateData, get_timeout_interval(StateName)}; - +handle_info(terminate_if_waiting_before_retry, + StateName, StateData) -> + {next_state, StateName, StateData, + get_timeout_interval(StateName)}; handle_info(_, StateName, StateData) -> - {next_state, StateName, StateData, get_timeout_interval(StateName)}. + {next_state, StateName, StateData, + get_timeout_interval(StateName)}. %%---------------------------------------------------------------------- %% Func: terminate/3 @@ -909,20 +963,18 @@ handle_info(_, StateName, StateData) -> terminate(Reason, StateName, StateData) -> ?DEBUG("terminated: ~p", [{Reason, StateName}]), case StateData#state.new of - false -> - ok; - Key -> - ejabberd_s2s:remove_connection( - {StateData#state.myname, StateData#state.server}, self(), Key) + false -> ok; + Key -> + ejabberd_s2s:remove_connection({StateData#state.myname, + StateData#state.server}, + self(), Key) end, - %% bounce queue manage by process and Erlang message queue - bounce_queue(StateData#state.queue, ?ERR_REMOTE_SERVER_NOT_FOUND), + bounce_queue(StateData#state.queue, + ?ERR_REMOTE_SERVER_NOT_FOUND), bounce_messages(?ERR_REMOTE_SERVER_NOT_FOUND), case StateData#state.socket of - undefined -> - ok; - _Socket -> - ejabberd_socket:close(StateData#state.socket) + undefined -> ok; + _Socket -> ejabberd_socket:close(StateData#state.socket) end, ok. @@ -931,8 +983,7 @@ 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 @@ -946,325 +997,298 @@ send_element(StateData, El) -> send_queue(StateData, Q) -> case queue:out(Q) of - {{value, El}, Q1} -> - send_element(StateData, El), - send_queue(StateData, Q1); - {empty, _Q1} -> - ok + {{value, El}, Q1} -> + send_element(StateData, El), send_queue(StateData, Q1); + {empty, _Q1} -> ok end. %% Bounce a single message (xmlelement) bounce_element(El, Error) -> - {xmlelement, _Name, Attrs, _SubTags} = El, - case xml:get_attr_s("type", Attrs) of - "error" -> ok; - "result" -> ok; - _ -> - Err = jlib:make_error_reply(El, Error), - From = jlib:string_to_jid(xml:get_tag_attr_s("from", El)), - To = jlib:string_to_jid(xml:get_tag_attr_s("to", El)), - ejabberd_router:route(To, From, Err) + #xmlel{attrs = Attrs} = El, + case xml:get_attr_s(<<"type">>, Attrs) of + <<"error">> -> ok; + <<"result">> -> ok; + _ -> + Err = jlib:make_error_reply(El, Error), + From = jlib:string_to_jid(xml:get_tag_attr_s(<<"from">>, + El)), + To = jlib:string_to_jid(xml:get_tag_attr_s(<<"to">>, + El)), + ejabberd_router:route(To, From, Err) end. bounce_queue(Q, Error) -> case queue:out(Q) of - {{value, El}, Q1} -> - bounce_element(El, Error), - bounce_queue(Q1, Error); - {empty, _} -> - ok + {{value, El}, Q1} -> + bounce_element(El, Error), bounce_queue(Q1, Error); + {empty, _} -> ok end. -new_id() -> - randoms:get_string(). +new_id() -> randoms:get_string(). cancel_timer(Timer) -> erlang:cancel_timer(Timer), - receive - {timeout, Timer, _} -> - ok - after 0 -> - ok - end. + receive {timeout, Timer, _} -> ok after 0 -> ok end. bounce_messages(Error) -> receive - {send_element, El} -> - bounce_element(El, Error), - bounce_messages(Error) - after 0 -> - ok + {send_element, El} -> + bounce_element(El, Error), bounce_messages(Error) + after 0 -> ok end. - send_db_request(StateData) -> Server = StateData#state.server, New = case StateData#state.new of - false -> - case ejabberd_s2s:try_register( - {StateData#state.myname, Server}) of - {key, Key} -> - Key; - false -> - false - end; - Key -> - Key + false -> + case ejabberd_s2s:try_register({StateData#state.myname, + Server}) + of + {key, Key} -> Key; + false -> false + end; + Key -> Key end, NewStateData = StateData#state{new = New}, - try - case New of - false -> - ok; - Key1 -> - send_element(StateData, - {xmlelement, - "db:result", - [{"from", StateData#state.myname}, - {"to", Server}], - [{xmlcdata, Key1}]}) + try case New of + false -> ok; + Key1 -> + send_element(StateData, + #xmlel{name = <<"db:result">>, + attrs = + [{<<"from">>, StateData#state.myname}, + {<<"to">>, Server}], + children = [{xmlcdata, Key1}]}) end, case StateData#state.verify of - false -> - ok; - {_Pid, Key2, SID} -> - send_element(StateData, - {xmlelement, - "db:verify", - [{"from", StateData#state.myname}, - {"to", StateData#state.server}, - {"id", SID}], - [{xmlcdata, Key2}]}) + false -> ok; + {_Pid, Key2, SID} -> + send_element(StateData, + #xmlel{name = <<"db:verify">>, + attrs = + [{<<"from">>, StateData#state.myname}, + {<<"to">>, StateData#state.server}, + {<<"id">>, SID}], + children = [{xmlcdata, Key2}]}) end, - {next_state, wait_for_validation, NewStateData, ?FSMTIMEOUT*6} + {next_state, wait_for_validation, NewStateData, + (?FSMTIMEOUT) * 6} catch - _:_ -> - {stop, normal, NewStateData} + _:_ -> {stop, normal, NewStateData} end. - -is_verify_res({xmlelement, Name, Attrs, _Els}) when Name == "db:result" -> - {result, - xml:get_attr_s("to", Attrs), - xml:get_attr_s("from", Attrs), - xml:get_attr_s("id", Attrs), - xml:get_attr_s("type", Attrs)}; -is_verify_res({xmlelement, Name, Attrs, _Els}) when Name == "db:verify" -> - {verify, - xml:get_attr_s("to", Attrs), - xml:get_attr_s("from", Attrs), - xml:get_attr_s("id", Attrs), - xml:get_attr_s("type", Attrs)}; -is_verify_res(_) -> - false. - +is_verify_res(#xmlel{name = Name, attrs = Attrs}) + when Name == <<"db:result">> -> + {result, xml:get_attr_s(<<"to">>, Attrs), + xml:get_attr_s(<<"from">>, Attrs), + xml:get_attr_s(<<"id">>, Attrs), + xml:get_attr_s(<<"type">>, Attrs)}; +is_verify_res(#xmlel{name = Name, attrs = Attrs}) + when Name == <<"db:verify">> -> + {verify, xml:get_attr_s(<<"to">>, Attrs), + xml:get_attr_s(<<"from">>, Attrs), + xml:get_attr_s(<<"id">>, Attrs), + xml:get_attr_s(<<"type">>, Attrs)}; +is_verify_res(_) -> false. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% SRV support -include_lib("kernel/include/inet.hrl"). +-spec get_addr_port(binary()) -> [{binary(), inet:port_number()}]. get_addr_port(Server) -> Res = srv_lookup(Server), case Res of - {error, Reason} -> - ?DEBUG("srv lookup of '~s' failed: ~p~n", [Server, Reason]), - [{Server, outgoing_s2s_port()}]; - {ok, HEnt} -> - ?DEBUG("srv lookup of '~s': ~p~n", - [Server, HEnt#hostent.h_addr_list]), - AddrList = HEnt#hostent.h_addr_list, - %% Probabilities are not exactly proportional to weights - %% for simplicity (higher weigths are overvalued) - {A1, A2, A3} = now(), - random:seed(A1, A2, A3), - case (catch lists:map( - fun({Priority, Weight, Port, Host}) -> - N = case Weight of - 0 -> 0; - _ -> (Weight + 1) * random:uniform() - end, - {Priority * 65536 - N, Host, Port} - end, AddrList)) of - SortedList = [_|_] -> - List = lists:map( - fun({_, Host, Port}) -> - {Host, Port} - end, lists:keysort(1, SortedList)), - ?DEBUG("srv lookup of '~s': ~p~n", [Server, List]), - List; - _ -> - [{Server, outgoing_s2s_port()}] - end + {error, Reason} -> + ?DEBUG("srv lookup of '~s' failed: ~p~n", + [Server, Reason]), + [{Server, outgoing_s2s_port()}]; + {ok, HEnt} -> + ?DEBUG("srv lookup of '~s': ~p~n", + [Server, HEnt#hostent.h_addr_list]), + AddrList = HEnt#hostent.h_addr_list, + {A1, A2, A3} = now(), + random:seed(A1, A2, A3), + case catch lists:map(fun ({Priority, Weight, Port, + Host}) -> + N = case Weight of + 0 -> 0; + _ -> + (Weight + 1) * random:uniform() + end, + {Priority * 65536 - N, Host, Port} + end, + AddrList) + of + SortedList = [_ | _] -> + List = lists:map(fun ({_, Host, Port}) -> + {list_to_binary(Host), Port} + end, + lists:keysort(1, SortedList)), + ?DEBUG("srv lookup of '~s': ~p~n", [Server, List]), + List; + _ -> [{Server, outgoing_s2s_port()}] + end end. srv_lookup(Server) -> - Options = case ejabberd_config:get_local_option(s2s_dns_options) of - L when is_list(L) -> L; - _ -> [] - end, - TimeoutMs = timer:seconds(proplists:get_value(timeout, Options, 10)), + Options = case + ejabberd_config:get_local_option( + s2s_dns_options, fun(L) when is_list(L) -> L end) + of + undefined -> []; + L -> L + end, + TimeoutMs = timer:seconds(proplists:get_value(timeout, + Options, 10)), Retries = proplists:get_value(retries, Options, 2), - srv_lookup(Server, TimeoutMs, Retries). + srv_lookup(binary_to_list(Server), TimeoutMs, Retries). %% XXX - this behaviour is suboptimal in the case that the domain %% has a "_xmpp-server._tcp." but not a "_jabber._tcp." record and %% we don't get a DNS reply for the "_xmpp-server._tcp." lookup. In this %% case we'll give up when we get the "_jabber._tcp." nxdomain reply. -srv_lookup(_Server, _Timeout, Retries) when Retries < 1 -> +srv_lookup(_Server, _Timeout, Retries) + when Retries < 1 -> {error, timeout}; srv_lookup(Server, Timeout, Retries) -> - case inet_res:getbyname("_xmpp-server._tcp." ++ Server, srv, Timeout) of - {error, _Reason} -> - case inet_res:getbyname("_jabber._tcp." ++ Server, srv, Timeout) of - {error, timeout} -> - ?ERROR_MSG("The DNS servers~n ~p~ntimed out on request" - " for ~p IN SRV." - " You should check your DNS configuration.", - [inet_db:res_option(nameserver), Server]), - srv_lookup(Server, Timeout, Retries - 1); - R -> R - end; - {ok, _HEnt} = R -> R + case inet_res:getbyname("_xmpp-server._tcp." ++ Server, + srv, Timeout) + of + {error, _Reason} -> + case inet_res:getbyname("_jabber._tcp." ++ Server, srv, + Timeout) + of + {error, timeout} -> + ?ERROR_MSG("The DNS servers~n ~p~ntimed out on " + "request for ~p IN SRV. You should check " + "your DNS configuration.", + [inet_db:res_option(nameserver), Server]), + srv_lookup(Server, Timeout, Retries - 1); + R -> R + end; + {ok, _HEnt} = R -> R end. test_get_addr_port(Server) -> - lists:foldl( - fun(_, Acc) -> - [HostPort | _] = get_addr_port(Server), - case lists:keysearch(HostPort, 1, Acc) of - false -> - [{HostPort, 1} | Acc]; - {value, {_, Num}} -> - lists:keyreplace(HostPort, 1, Acc, {HostPort, Num + 1}) - end - end, [], lists:seq(1, 100000)). + lists:foldl(fun (_, Acc) -> + [HostPort | _] = get_addr_port(Server), + case lists:keysearch(HostPort, 1, Acc) of + false -> [{HostPort, 1} | Acc]; + {value, {_, Num}} -> + lists:keyreplace(HostPort, 1, Acc, + {HostPort, Num + 1}) + end + end, + [], lists:seq(1, 100000)). get_addrs(Host, Family) -> Type = case Family of - inet4 -> inet; - ipv4 -> inet; - inet6 -> inet6; - ipv6 -> inet6 + inet4 -> inet; + ipv4 -> inet; + inet6 -> inet6; + ipv6 -> inet6 end, - case inet:gethostbyname(Host, Type) of - {ok, #hostent{h_addr_list = Addrs}} -> - ?DEBUG("~s of ~s resolved to: ~p~n", [Type, Host, Addrs]), - Addrs; - {error, Reason} -> - ?DEBUG("~s lookup of '~s' failed: ~p~n", [Type, Host, Reason]), - [] + case inet:gethostbyname(binary_to_list(Host), Type) of + {ok, #hostent{h_addr_list = Addrs}} -> + ?DEBUG("~s of ~s resolved to: ~p~n", + [Type, Host, Addrs]), + Addrs; + {error, Reason} -> + ?DEBUG("~s lookup of '~s' failed: ~p~n", + [Type, Host, Reason]), + [] end. - outgoing_s2s_port() -> - case ejabberd_config:get_local_option(outgoing_s2s_port) of - Port when is_integer(Port) -> - Port; - undefined -> - 5269 - end. + ejabberd_config:get_local_option( + outgoing_s2s_port, + fun(I) when is_integer(I), I > 0, I =< 65536 -> I end, + 5269). outgoing_s2s_families() -> - case ejabberd_config:get_local_option(outgoing_s2s_options) of - {Families, _} when is_list(Families) -> - Families; - undefined -> - %% DISCUSSION: Why prefer IPv4 first? - %% - %% IPv4 connectivity will be available for everyone for - %% many years to come. So, there's absolutely no benefit - %% in preferring IPv6 connections which are flaky at best - %% nowadays. - %% - %% On the other hand content providers hesitate putting up - %% AAAA records for their sites due to the mentioned - %% quality of current IPv6 connectivity. Making IPv6 the a - %% `fallback' may avoid these problems elegantly. - [ipv4, ipv6] - end. + ejabberd_config:get_local_option( + outgoing_s2s_options, + fun({Families, _}) -> + true = lists:all( + fun(ipv4) -> true; + (ipv6) -> true + end, Families), + Families + end, [ipv4, ipv6]). outgoing_s2s_timeout() -> - case ejabberd_config:get_local_option(outgoing_s2s_options) of - {_, Timeout} when is_integer(Timeout) -> - Timeout; - {_, infinity} -> - infinity; - undefined -> - %% 10 seconds - 10000 - end. + ejabberd_config:get_local_option( + outgoing_s2s_options, + fun({_, TimeOut}) when is_integer(TimeOut), TimeOut > 0 -> + TimeOut; + ({_, infinity}) -> + infinity + end, 10000). %% Human readable S2S logging: Log only new outgoing connections as INFO %% Do not log dialback log_s2s_out(false, _, _, _) -> ok; %% Log new outgoing connections: log_s2s_out(_, Myname, Server, Tls) -> - ?INFO_MSG("Trying to open s2s connection: ~s -> ~s with TLS=~p", [Myname, Server, Tls]). + ?INFO_MSG("Trying to open s2s connection: ~s -> " + "~s with TLS=~p", + [Myname, Server, Tls]). %% Calculate timeout depending on which state we are in: %% Can return integer > 0 | infinity get_timeout_interval(StateName) -> case StateName of - %% Validation implies dialback: Networking can take longer: - wait_for_validation -> - ?FSMTIMEOUT*6; - %% When stream is established, we only rely on S2S Timeout timer: - stream_established -> - infinity; - _ -> - ?FSMTIMEOUT + %% Validation implies dialback: Networking can take longer: + wait_for_validation -> (?FSMTIMEOUT) * 6; + %% When stream is established, we only rely on S2S Timeout timer: + stream_established -> infinity; + _ -> ?FSMTIMEOUT end. %% This function is intended to be called at the end of a state %% function that want to wait for a reconnect delay before stopping. wait_before_reconnect(StateData) -> - %% bounce queue manage by process and Erlang message queue - bounce_queue(StateData#state.queue, ?ERR_REMOTE_SERVER_NOT_FOUND), + bounce_queue(StateData#state.queue, + ?ERR_REMOTE_SERVER_NOT_FOUND), bounce_messages(?ERR_REMOTE_SERVER_NOT_FOUND), cancel_timer(StateData#state.timer), Delay = case StateData#state.delay_to_retry of - undefined_delay -> - %% The initial delay is random between 1 and 15 seconds - %% Return a random integer between 1000 and 15000 - {_, _, MicroSecs} = now(), - (MicroSecs rem 14000) + 1000; - D1 -> - %% Duplicate the delay with each successive failed - %% reconnection attempt, but don't exceed the max - lists:min([D1 * 2, get_max_retry_delay()]) + undefined_delay -> + {_, _, MicroSecs} = now(), MicroSecs rem 14000 + 1000; + D1 -> lists:min([D1 * 2, get_max_retry_delay()]) end, Timer = erlang:start_timer(Delay, self(), []), - {next_state, wait_before_retry, StateData#state{timer=Timer, - delay_to_retry = Delay, - queue = queue:new()}}. - %% @doc Get the maximum allowed delay for retry to reconnect (in miliseconds). %% The default value is 5 minutes. %% The option {s2s_max_retry_delay, Seconds} can be used (in seconds). %% @spec () -> integer() + {next_state, wait_before_retry, + StateData#state{timer = Timer, delay_to_retry = Delay, + queue = queue:new()}}. + get_max_retry_delay() -> - case ejabberd_config:get_local_option(s2s_max_retry_delay) of - Seconds when is_integer(Seconds) -> - Seconds*1000; - _ -> - ?MAX_RETRY_DELAY + case ejabberd_config:get_local_option( + s2s_max_retry_delay, + fun(I) when is_integer(I), I > 0 -> I end) of + undefined -> ?MAX_RETRY_DELAY; + Seconds -> Seconds * 1000 end. %% Terminate s2s_out connections that are in state wait_before_retry terminate_if_waiting_delay(From, To) -> FromTo = {From, To}, Pids = ejabberd_s2s:get_connections_pids(FromTo), - lists:foreach( - fun(Pid) -> - Pid ! terminate_if_waiting_before_retry - end, - Pids). + lists:foreach(fun (Pid) -> + Pid ! terminate_if_waiting_before_retry + end, + Pids). fsm_limit_opts() -> - 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. diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl index 449cba68c..5a80fcd90 100644 --- a/src/ejabberd_service.erl +++ b/src/ejabberd_service.erl @@ -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, - "" - "" - ). + <<"">>). --define(STREAM_TRAILER, ""). +-define(STREAM_TRAILER, <<"">>). -define(INVALID_HEADER_ERR, - "" - "Invalid Stream Header" - "" - ). + <<"Invalid " + "Stream Header">>). -define(INVALID_HANDSHAKE_ERR, - "" - "" - "" - "Invalid Handshake" - "" - "" - ). + <<"Invalid Handshake">>). -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 @@ -123,51 +120,47 @@ socket_type() -> init([{SockMod, Socket}, Opts]) -> ?INFO_MSG("(~w) External service connected", [Socket]), Access = case lists:keysearch(access, 1, Opts) of - {value, {_, A}} -> A; - _ -> all + {value, {_, A}} -> A; + _ -> all end, - {Hosts, Password} = - case lists:keysearch(hosts, 1, Opts) of - {value, {_, Hs, HOpts}} -> - case lists:keysearch(password, 1, HOpts) of - {value, {_, P}} -> - {Hs, P}; - _ -> - % TODO: generate error - false - end; - _ -> - case lists:keysearch(host, 1, Opts) of - {value, {_, H, HOpts}} -> - case lists:keysearch(password, 1, HOpts) of - {value, {_, P}} -> - {[H], P}; - _ -> - % TODO: generate error - false - end; - _ -> - % TODO: generate error - false - end - end, + {Hosts, Password} = case lists:keysearch(hosts, 1, Opts) + of + {value, {_, Hs, HOpts}} -> + case lists:keysearch(password, 1, HOpts) of + {value, {_, P}} -> {Hs, P}; + _ -> + % TODO: generate error + false + end; + _ -> + case lists:keysearch(host, 1, Opts) of + {value, {_, H, HOpts}} -> + case lists:keysearch(password, 1, HOpts) of + {value, {_, P}} -> {[H], P}; + _ -> + % TODO: generate error + false + end; + _ -> + % TODO: generate error + false + end + end, Shaper = case lists:keysearch(shaper_rule, 1, Opts) of - {value, {_, S}} -> S; - _ -> none - end, - CheckFrom = case lists:keysearch(service_check_from, 1, Opts) of - {value, {_, CF}} -> CF; - _ -> true + {value, {_, S}} -> S; + _ -> none end, + 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,120 +169,109 @@ 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), - Header = io_lib:format(?STREAM_HEADER, - [StateData#state.streamid, xml:crypt(To)]), - send_text(StateData, Header), - {next_state, wait_for_handshake, StateData}; - _ -> - send_text(StateData, ?INVALID_HEADER_ERR), - {stop, normal, StateData} +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), + {next_state, wait_for_handshake, 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 - Digest -> - send_text(StateData, ""), - lists:foreach( - fun(H) -> - ejabberd_router:register_route(H), - ?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} + {<<"handshake">>, Digest} -> + case sha:sha(<<(StateData#state.streamid)/binary, + (StateData#state.password)/binary>>) + of + Digest -> + send_text(StateData, <<"">>), + lists:foreach(fun (H) -> + ejabberd_router:register_route(H), + ?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} 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. - %% In this case, the component can send packet of - %% behalf of the server users. - false -> jlib:string_to_jid(From); - %% The default is the standard behaviour in XEP-0114 - _ -> - FromJID1 = jlib:string_to_jid(From), - case FromJID1 of - #jid{lserver = Server} -> - case lists:member(Server, StateData#state.hosts) of - true -> FromJID1; - false -> error - end; - _ -> error - end + %% If the admin does not want to check the from field + %% when accept packets from any address. + %% In this case, the component can send packet of + %% behalf of the server users. + false -> jlib:string_to_jid(From); + %% The default is the standard behaviour in XEP-0114 + _ -> + FromJID1 = jlib:string_to_jid(From), + case FromJID1 of + #jid{lserver = Server} -> + case lists:member(Server, StateData#state.hosts) of + true -> FromJID1; + false -> error + end; + _ -> error + end end, - To = xml:get_attr_s("to", Attrs), + To = xml:get_attr_s(<<"to">>, Attrs), ToJID = case To of - "" -> error; - _ -> jlib:string_to_jid(To) + <<"">> -> error; + _ -> jlib:string_to_jid(To) end, - if ((Name == "iq") or - (Name == "message") or - (Name == "presence")) and - (ToJID /= error) and (FromJID /= error) -> - ejabberd_router:route(FromJID, ToJID, NewEl); + 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), - send_element(StateData, Err), - error + Err = jlib:make_error_reply(NewEl, ?ERR_BAD_REQUEST), + send_element(StateData, Err), + 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,25 +322,29 @@ 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 - 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}), - send_text(StateData, Text); - deny -> - Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED), - ejabberd_router:route_error(To, From, Err, Packet) +handle_info({route, From, To, Packet}, StateName, + StateData) -> + case acl:match_rule(global, StateData#state.access, + From) + of + allow -> + #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), + ejabberd_router:route_error(To, From, Err, Packet) end, {next_state, StateName, StateData}; handle_info(Info, StateName, StateData) -> ?ERROR_MSG("Unexpected info: ~p", [Info]), {next_state, StateName, StateData}. - %%---------------------------------------------------------------------- %% Func: terminate/3 %% Purpose: Shutdown the fsm @@ -367,13 +353,12 @@ handle_info(Info, StateName, StateData) -> terminate(Reason, StateName, StateData) -> ?INFO_MSG("terminated: ~p", [Reason]), case StateName of - stream_established -> - lists:foreach( - fun(H) -> - ejabberd_router:unregister_route(H) - end, StateData#state.hosts); - _ -> - ok + stream_established -> + lists:foreach(fun (H) -> + ejabberd_router:unregister_route(H) + 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}]; - _ -> - [] - end + {value, {_, 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. diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index 6a41be0ae..4ab5975fa 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -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 + {'EXIT', Reason} -> + ?ERROR_MSG("~p~nwhen processing: ~p", + [Reason, {From, To, Packet}]); + _ -> 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, - [{#session{usr = '$1', _ = '_'}, - [{'==', {element, 2, '$1'}, LServer}], - ['$1']}]). + mnesia:dirty_select(session, + [{#session{usr = '$1', _ = '_'}, + [{'==', {element, 2, '$1'}, LServer}], ['$1']}]). + +-spec get_vh_session_list(binary()) -> [ljid()]. get_vh_session_number(Server) -> LServer = jlib:nameprep(Server), @@ -234,12 +279,18 @@ get_vh_session_number(Server) -> Count; _ -> 0 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 + [{_, Module, Function, Opts}] -> + gen_iq_handler:stop_iq_handler(Module, Function, Opts); + _ -> ok end, ets:delete(sm_iqtable, {XMLNS, Host}), {noreply, State}; -handle_info(_Info, State) -> - {noreply, State}. +handle_info(_Info, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: terminate(Reason, State) -> void() @@ -356,8 +407,7 @@ terminate(_Reason, _State) -> %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} %% Description: Convert process state when code is changed %%-------------------------------------------------------------------- -code_change(_OldVsn, State, _Extra) -> - {ok, State}. +code_change(_OldVsn, State, _Extra) -> {ok, State}. %%-------------------------------------------------------------------- %%% Internal functions @@ -369,12 +419,9 @@ set_session(SID, User, Server, Resource, Priority, Info) -> LResource = jlib:resourceprep(Resource), US = {LUser, LServer}, USR = {LUser, LServer, LResource}, - F = fun() -> - mnesia:write(#session{sid = SID, - usr = USR, - us = US, - priority = Priority, - info = Info}) + F = fun () -> + mnesia:write(#session{sid = SID, usr = USR, us = US, + priority = Priority, info = Info}) end, mnesia:sync_dirty(F). @@ -408,108 +455,107 @@ 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, - LServer, - false, - [User, Server, From, subscribe, Reason]), - true}; - "subscribed" -> - {is_privacy_allow(From, To, Packet) andalso - ejabberd_hooks:run_fold( - roster_in_subscription, - LServer, - false, - [User, Server, From, subscribed, ""]), - true}; - "unsubscribe" -> - {is_privacy_allow(From, To, Packet) andalso - ejabberd_hooks:run_fold( - roster_in_subscription, - LServer, - false, - [User, Server, From, unsubscribe, ""]), - true}; - "unsubscribed" -> - {is_privacy_allow(From, To, Packet) andalso - ejabberd_hooks:run_fold( - roster_in_subscription, - LServer, - false, - [User, Server, From, unsubscribed, ""]), - true}; - _ -> - {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) -> - do_route(From, - jlib:jid_replace_resource(To, R), - Packet) - end, get_user_resources(User, Server)); - _ -> - ok - end; - _ -> - USR = {LUser, LServer, LResource}, - 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; - _ -> - Err = - jlib:make_error_reply( - Packet, ?ERR_SERVICE_UNAVAILABLE), - ejabberd_router:route(To, From, Err) - end; + <<"">> -> + 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, + LServer, + false, + [User, Server, + From, + subscribe, + Reason]), + true}; + <<"subscribed">> -> + {is_privacy_allow(From, To, Packet) + andalso + ejabberd_hooks:run_fold(roster_in_subscription, + LServer, + false, + [User, Server, + From, + subscribed, + <<"">>]), + true}; + <<"unsubscribe">> -> + {is_privacy_allow(From, To, Packet) + andalso + ejabberd_hooks:run_fold(roster_in_subscription, + LServer, + false, + [User, Server, + From, + unsubscribe, + <<"">>]), + true}; + <<"unsubscribed">> -> + {is_privacy_allow(From, To, Packet) + andalso + ejabberd_hooks:run_fold(roster_in_subscription, + LServer, + false, + [User, Server, + From, + unsubscribed, + <<"">>]), + true}; + _ -> {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); + _ -> ok + end; + _ -> + USR = {LUser, LServer, LResource}, + 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; _ -> - ?DEBUG("packet droped~n", []) - end; - Ss -> - Session = lists:max(Ss), - Pid = element(2, Session#session.sid), - ?DEBUG("sending to process ~p~n", [Pid]), - Pid ! {route, From, To, Packet} - end + Err = jlib:make_error_reply(Packet, + ?ERR_SERVICE_UNAVAILABLE), + ejabberd_router:route(To, From, Err) + end; + _ -> ?DEBUG("packet droped~n", []) + end; + Ss -> + Session = lists:max(Ss), + Pid = element(2, Session#session.sid), + ?DEBUG("sending to process ~p~n", [Pid]), + Pid ! {route, From, To, Packet} + end end. %% The default list applies to the user as a whole, @@ -519,8 +565,9 @@ 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, - #userlist{}, [User, Server]), + PrivacyList = + ejabberd_hooks:run_fold(privacy_get_user_list, Server, + #userlist{}, [User, Server]), is_privacy_allow(From, To, Packet, PrivacyList). %% Check if privacy rules allow this delivery @@ -528,102 +575,89 @@ 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, - [User, - Server, - PrivacyList, - {From, To, Packet}, - in]). + allow == + ejabberd_hooks:run_fold(privacy_check_packet, Server, + allow, + [User, Server, PrivacyList, {From, To, Packet}, + in]). route_message(From, To, Packet) -> LUser = To#jid.luser, 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 -> - LResource = jlib:resourceprep(R), - USR = {LUser, LServer, LResource}, - case mnesia:dirty_index_read(session, USR, #session.usr) of - [] -> - ok; % Race condition - Ss -> - Session = lists:max(Ss), - Pid = element(2, Session#session.sid), - ?DEBUG("sending to process ~p~n", [Pid]), - Pid ! {route, From, To, Packet} - end; - %% Ignore other priority: - ({_Prio, _Res}) -> - ok - end, - PrioRes); - _ -> - case xml:get_tag_attr_s("type", Packet) of - "error" -> - ok; - "groupchat" -> - bounce_offline_message(From, To, Packet); - "headline" -> - bounce_offline_message(From, To, Packet); - _ -> - case ejabberd_auth:is_user_exists(LUser, LServer) of + {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 + [] -> + ok; % Race condition + Ss -> + Session = lists:max(Ss), + Pid = element(2, Session#session.sid), + ?DEBUG("sending to process ~p~n", [Pid]), + Pid ! {route, From, To, Packet} + end; + %% Ignore other priority: + ({_Prio, _Res}) -> ok + end, + PrioRes); + _ -> + case xml:get_tag_attr_s(<<"type">>, Packet) of + <<"error">> -> ok; + <<"groupchat">> -> + bounce_offline_message(From, To, Packet); + <<"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 -> - case is_privacy_allow(From, To, Packet) of - true -> - ejabberd_hooks:run(offline_message_hook, - LServer, - [From, To, Packet]); - false -> - ok - end; - _ -> - Err = jlib:make_error_reply( - Packet, ?ERR_SERVICE_UNAVAILABLE), - ejabberd_router:route(To, From, Err) - end - end + ejabberd_hooks:run(offline_message_hook, LServer, + [From, To, Packet]); + false -> ok + end; + _ -> + 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 -> - clean_session_list([S1 | Rest], Res); - true -> - clean_session_list([S2 | Rest], Res) - end; - true -> - clean_session_list([S2 | Rest], [S1 | Res]) + 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) + end; + 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} -> - []; - Ss -> - [{S#session.priority, element(3, S#session.usr)} || - S <- clean_session_list(Ss), is_integer(S#session.priority)] + 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)] end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -633,62 +667,54 @@ 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; - true -> - %% A connection exist with the same resource. We replace it: - MaxSID = lists:max(SIDs), - lists:foreach( - fun({_, Pid} = S) when S /= MaxSID -> - Pid ! replaced; - (_) -> ok - end, SIDs) + if SIDs == [] -> ok; + true -> + MaxSID = lists:max(SIDs), + lists:foreach(fun ({_, Pid} = S) when S /= MaxSID -> + Pid ! replaced; + (_) -> ok + 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 - Max when is_integer(Max) -> Max; - infinity -> infinity; - _ -> ?MAX_USER_SESSIONS + 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 end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -696,80 +722,78 @@ get_max_user_sessions(LUser, Host) -> process_iq(From, To, Packet) -> IQ = jlib:iq_query_info(Packet), case IQ of - #iq{xmlns = XMLNS} -> - Host = To#jid.lserver, - 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 - end; - [{_, Module, Function, Opts}] -> - gen_iq_handler:handle(Host, Module, Function, Opts, - From, To, IQ); - [] -> - Err = jlib:make_error_reply( - Packet, ?ERR_SERVICE_UNAVAILABLE), - ejabberd_router:route(To, From, Err) - end; - reply -> - ok; - _ -> - Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST), - ejabberd_router:route(To, From, Err), - ok + #iq{xmlns = XMLNS} -> + Host = To#jid.lserver, + 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 + end; + [{_, Module, Function, Opts}] -> + gen_iq_handler:handle(Host, Module, Function, Opts, + From, To, IQ); + [] -> + Err = jlib:make_error_reply(Packet, + ?ERR_SERVICE_UNAVAILABLE), + ejabberd_router:route(To, From, Err) + end; + 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; - Ss -> - lists:foreach(fun(#session{sid = {_, Pid}}) -> - Pid ! {force_update_presence, LUser} - end, Ss) + case catch mnesia:dirty_index_read(session, US, + #session.us) + of + {'EXIT', _Reason} -> ok; + Ss -> + lists:foreach(fun (#session{sid = {_, Pid}}) -> + Pid ! {force_update_presence, LUser} + end, + Ss) end. - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% ejabberd commands commands() -> - [ - #ejabberd_commands{name = connected_users, - tags = [session], - desc = "List all established sessions", - module = ?MODULE, function = connected_users, - args = [], - result = {connected_users, {list, {sessions, string}}}}, + [#ejabberd_commands{name = connected_users, + tags = [session], + desc = "List all established sessions", + 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}}, + tags = [session, stats], + desc = "Get the number of established sessions", + module = ?MODULE, function = connected_users_number, + 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}}}} - ]. + tags = [session], + desc = "List user's connected resources", + module = ?MODULE, function = user_resources, + args = [{user, string}, {host, 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}) -> <> end, + SUSRs). connected_users_number() -> length(dirty_get_sessions_list()). user_resources(User, Server) -> - Resources = get_user_resources(User, Server), + Resources = get_user_resources(User, Server), lists:sort(Resources). @@ -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); - [sid, usr, us, priority] -> - mnesia:delete_table(session); - [sid, usr, us, priority, info] -> - ok; - {'EXIT', _} -> - ok + [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 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 -> diff --git a/src/ejabberd_socket.erl b/src/ejabberd_socket.erl index 836e7d9f9..189718a35 100644 --- a/src/ejabberd_socket.erl +++ b/src/ejabberd_socket.erl @@ -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 @@ -59,56 +81,53 @@ %%-------------------------------------------------------------------- start(Module, SockMod, Socket, Opts) -> case Module:socket_type() of - xml_stream -> - MaxStanzaSize = - case lists:keysearch(max_stanza_size, 1, Opts) of - {value, {_, Size}} -> Size; - _ -> infinity + xml_stream -> + 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 + {receiver, RecMod, RecPid} -> + {RecMod, RecPid, RecMod}; + _ -> + RecPid = + ejabberd_receiver:start(Socket, + SockMod, + none, + MaxStanzaSize), + {ejabberd_receiver, RecPid, + RecPid} + end, + SocketData = #socket_state{sockmod = SockMod, + 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) end, - {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} - end, - SocketData = #socket_state{sockmod = SockMod, - 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) - end, - ReceiverMod:become_controller(Receiver, Pid); - {error, _Reason} -> - SockMod:close(Socket), - case ReceiverMod of - ejabberd_receiver -> - ReceiverMod:close(Receiver); - _ -> - ok - end - end; - 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) - end; - {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 + end + end; + 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) + end; + {error, _Reason} -> SockMod:close(Socket) + end end. connect(Addr, Port, Opts) -> @@ -116,22 +135,19 @@ 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), - SocketData = #socket_state{sockmod = gen_tcp, - 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 - end; - {error, _Reason} = Error -> - Error + {ok, Socket} -> + Receiver = ejabberd_receiver:start(Socket, gen_tcp, + none), + SocketData = #socket_state{sockmod = gen_tcp, + 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 + end; + {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); + when is_pid(SocketData#socket_state.receiver) -> + 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). + when is_atom(SocketData#socket_state.receiver) -> + (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 %%==================================================================== +%==================================================================== diff --git a/src/ejabberd_sup.erl b/src/ejabberd_sup.erl index 7dcd29232..80d845fcb 100644 --- a/src/ejabberd_sup.erl +++ b/src/ejabberd_sup.erl @@ -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, diff --git a/src/ejabberd_system_monitor.erl b/src/ejabberd_system_monitor.erl index d55fdb84b..1273226c4 100644 --- a/src/ejabberd_system_monitor.erl +++ b/src/ejabberd_system_monitor.erl @@ -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, - case Name of - "message" -> - LFrom = jlib:jid_tolower(jlib:jid_remove_resource(From)), - case lists:member(LFrom, get_admin_jids()) of - true -> - Body = xml:get_path_s( - Packet, [{elem, "body"}, cdata]), - spawn(fun() -> - process_flag(priority, high), - process_command1(From, To, Body) - end), - stop; - false -> - ok - end; - _ -> - ok - end; - _ -> - ok + #jid{luser = <<"">>, lresource = <<"watchdog">>} -> + #xmlel{name = Name} = Packet, + case Name of + <<"message">> -> + LFrom = + jlib:jid_tolower(jlib:jid_remove_resource(From)), + case lists:member(LFrom, get_admin_jids()) of + true -> + Body = xml:get_path_s(Packet, + [{elem, <<"body">>}, cdata]), + spawn(fun () -> + process_flag(priority, high), + process_command1(From, To, Body) + end), + stop; + false -> ok + end; + _ -> ok + end; + _ -> 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) -> - ejabberd_hooks:add(local_send_to_resource_hook, Host, - ?MODULE, process_command, 50) - end, ?MYHOSTS), + lists:foreach(fun (Host) -> + ejabberd_hooks:add(local_send_to_resource_hook, Host, + ?MODULE, process_command, 50) + end, + ?MYHOSTS), {ok, #state{}}. %%-------------------------------------------------------------------- @@ -117,18 +117,20 @@ init(Opts) -> %%-------------------------------------------------------------------- handle_call({get, large_heap}, _From, State) -> {reply, get_large_heap(), State}; -handle_call({set, large_heap, NewValue}, _From, State) -> - MonSettings = erlang:system_monitor(self(), [{large_heap, NewValue}]), +handle_call({set, large_heap, NewValue}, _From, + State) -> + MonSettings = erlang:system_monitor(self(), + [{large_heap, NewValue}]), OldLH = get_large_heap(MonSettings), NewLH = get_large_heap(), {reply, {lh_changed, OldLH, NewLH}, State}; handle_call(_Request, _From, State) -> - Reply = ok, - {reply, Reply, State}. + Reply = ok, {reply, Reply, State}. get_large_heap() -> MonSettings = erlang:system_monitor(), get_large_heap(MonSettings). + get_large_heap(MonSettings) -> {_MonitorPid, Options} = MonSettings, proplists:get_value(large_heap, Options). @@ -139,8 +141,7 @@ get_large_heap(MonSettings) -> %% {stop, Reason, State} %% Description: Handling cast messages %%-------------------------------------------------------------------- -handle_cast(_Msg, State) -> - {noreply, State}. +handle_cast(_Msg, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: handle_info(Info, State) -> {noreply, State} | @@ -149,13 +150,12 @@ handle_cast(_Msg, State) -> %% Description: Handling all non call/cast messages %%-------------------------------------------------------------------- handle_info({monitor, Pid, large_heap, Info}, State) -> - spawn(fun() -> + spawn(fun () -> process_flag(priority, high), process_large_heap(Pid, Info) end), {noreply, State}; -handle_info(_Info, State) -> - {noreply, State}. +handle_info(_Info, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: terminate(Reason, State) -> void() @@ -164,204 +164,171 @@ 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 /= [] -> - 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. + Host = (?MYNAME), + JIDs = get_admin_jids(), + DetailedInfo = detailed_info(Pid), + 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 - {dictionary, Dict} -> - 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) - end; - _ -> - detailed_info1(Pid) - end; - _ -> - detailed_info1(Pid) + {dictionary, Dict} -> + 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) + end; + _ -> detailed_info1(Pid) + end; + _ -> detailed_info1(Pid) end. detailed_info1(Pid) -> - 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, heap_size), - process_info(Pid, stack_size) - ]]). + 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, heap_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]); - _ -> - "" + [{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 - {{current_function, MFA}, {message_queue_len, MLen}} -> - if - MLen > 100 -> - case MFA of - {prim_inet, send, 2} -> - "\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. ", - io_lib:format("Current function is ~w:~w/~w", - [M, F, A]) - ] - end; - true -> - "" - end; - _ -> - "" + process_info(Pid, message_queue_len)} + of + {{current_function, MFA}, {message_queue_len, MLen}} -> + if MLen > 100 -> + case MFA of + {prim_inet, send, 2} -> + <<"\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. ">>, + io_lib:format("Current function is ~w:~w/~w", + [M, F, A])] + end; + 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 \n" - " showlh \n" - " setlh ". - + <<"Commands:\n kill \n showlh " + "\n setlh ">>. remote_command(Node, Args, From, To) -> - Message = - case rpc:call(Node, ?MODULE, process_remote_command, [Args]) of - {badrpc, Reason} -> - io_lib:format("Command failed:~n~p", [Reason]); - Result -> - Result - end, - send_message(To, From, Message). + Message = case rpc:call(Node, ?MODULE, + process_remote_command, [Args]) + of + {badrpc, Reason} -> + io_lib:format("Command failed:~n~p", [Reason]); + Result -> Result + end, + 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). diff --git a/src/ejabberd_tmp_sup.erl b/src/ejabberd_tmp_sup.erl index 04855ce48..c3d2a186e 100644 --- a/src/ejabberd_tmp_sup.erl +++ b/src/ejabberd_tmp_sup.erl @@ -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]}]}}. diff --git a/src/ejabberd_update.erl b/src/ejabberd_update.erl index 43dcf31a0..6a7f8bc9a 100644 --- a/src/ejabberd_update.erl +++ b/src/ejabberd_update.erl @@ -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() -> diff --git a/src/ejabberd_zlib/Makefile.in b/src/ejabberd_zlib/Makefile.in index 9b8ac7658..b572c1169 100644 --- a/src/ejabberd_zlib/Makefile.in +++ b/src/ejabberd_zlib/Makefile.in @@ -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 diff --git a/src/ejabberd_zlib/ejabberd_zlib.erl b/src/ejabberd_zlib/ejabberd_zlib.erl index e894a5c33..3dee8d687 100644 --- a/src/ejabberd_zlib/ejabberd_zlib.erl +++ b/src/ejabberd_zlib/ejabberd_zlib.erl @@ -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 - ok -> ok; - {error, already_loaded} -> ok + 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 - ok -> ok; - {error, already_loaded} -> ok + 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); - _ -> - case SockMod:recv_data(Socket, Packet) of - {ok, Packet2} -> - recv_data2(ZlibSock, Packet2); - Error -> - Error - end + gen_tcp -> recv_data2(ZlibSock, Packet); + _ -> + case SockMod:recv_data(Socket, Packet) of + {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). diff --git a/src/ejabberdctl.template b/src/ejabberdctl.template index 461ec1a6f..b6dfd4fc4 100644 --- a/src/ejabberdctl.template +++ b/src/ejabberdctl.template @@ -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 diff --git a/src/ejd2odbc.erl b/src/ejd2odbc.erl index 68ebdfd5f..b96503dbe 100644 --- a/src/ejd2odbc.erl +++ b/src/ejd2odbc.erl @@ -25,59 +25,12 @@ %%%---------------------------------------------------------------------- -module(ejd2odbc). + -author('alexey@process-one.net'). -%% External exports --export([export_passwd/2, - export_roster/2, - export_offline/2, - export_last/2, - export_vcard/2, - export_vcard_search/2, - export_vcard_xupdate/2, - export_private_storage/2, - export_privacy/2, - export_motd/2, - export_motd_users/2, - export_irc_custom/2, - export_sr_group/2, - export_sr_user/2, - export_muc_room/2, - export_muc_registered/2]). +-export([export/2, export/3]). --include("ejabberd.hrl"). --include("jlib.hrl"). --include("mod_roster.hrl"). --include("mod_privacy.hrl"). - --record(offline_msg, {us, timestamp, expire, from, to, packet}). --record(last_activity, {us, timestamp, status}). --record(vcard, {us, vcard}). --record(vcard_xupdate, {us, hash}). --record(vcard_search, {us, - user, luser, - fn, lfn, - family, lfamily, - given, lgiven, - middle, lmiddle, - nickname, lnickname, - bday, lbday, - ctry, lctry, - locality, llocality, - email, lemail, - orgname, lorgname, - orgunit, lorgunit - }). --record(private_storage, {usns, xml}). --record(irc_custom, {us_host, data}). --record(muc_room, {name_host, opts}). --record(muc_registered, {us_host, nick}). --record(sr_group, {group_host, opts}). --record(sr_user, {us, group_host}). --record(motd, {server, packet}). --record(motd_users, {us, dummy = []}). - --define(MAX_RECORDS_PER_TRANSACTION, 1000). +-define(MAX_RECORDS_PER_TRANSACTION, 100). %%%---------------------------------------------------------------------- %%% API @@ -89,481 +42,98 @@ %%% - 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(LServer, Table, IO, ConvertFun) -> + F = fun () -> + mnesia:read_lock_table(Table), + {_N, SQLs} = + mnesia:foldl( + fun(R, {N, SQLs} = Acc) -> + case ConvertFun(LServer, R) of + [] -> + Acc; + SQL -> + if N < (?MAX_RECORDS_PER_TRANSACTION) - 1 -> + {N + 1, [SQL | SQLs]}; + true -> + output(LServer, + Table, IO, + flatten([SQL | SQLs])), + {0, []} + end + end + end, + {0, []}, Table), + output(LServer, Table, IO, flatten(SQLs)) + end, + mnesia:transaction(F). -export_common(Server, Table, Output, ConvertFun) -> - IO = case Output of - odbc -> - odbc; - _ -> - {ok, IODevice} = file:open(Output, [write, raw]), - IODevice - end, - mnesia:transaction( - fun() -> - mnesia:read_lock_table(Table), - LServer = jlib:nameprep(Server), - {_N, SQLs} = - mnesia:foldl( - fun(R, {N, SQLs} = Acc) -> - case ConvertFun(LServer, R) of - [] -> - Acc; - SQL -> - 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"]), - {0, []} - end - end - end, {0, []}, Table), - %% Execute SQL transaction with remaining records - output(LServer, IO, - ["begin;", - lists:reverse(SQLs), - "commit"]) - end). +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]). -output(LServer, IO, SQL) -> - case IO of - odbc -> - catch ejabberd_odbc:sql_query(LServer, SQL); - _ -> - file:write(IO, [SQL, $;, $\n]) - end. +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. -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" - 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')"]. +close_output(FileName, Fd) when FileName /= Fd -> + file:close(Fd), + ok; +close_output(_, _) -> + ok. -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]. +flatten(SQLs) -> + flatten(SQLs, []). -get_id() -> - ID = get(id), - put(id, ID+1), - ID+1. +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. diff --git a/src/eldap/Makefile.in b/src/eldap/Makefile.in index 8a0a0d768..a44bee595 100644 --- a/src/eldap/Makefile.in +++ b/src/eldap/Makefile.in @@ -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 diff --git a/src/eldap/eldap.erl b/src/eldap/eldap.erl index e18d2e22a..4df7d00eb 100644 --- a/src/eldap/eldap.erl +++ b/src/eldap/eldap.erl @@ -7,6 +7,7 @@ %%% %%% Copyright (C) 2000 Torbjorn Tornkvist, tnt@home.se %%% +%%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or @@ -21,7 +22,6 @@ %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - %%% Modified by Sean Hinde 7th Dec 2000 %%% Turned into gen_fsm, made non-blocking, added timers etc to support this. %%% Now has the concept of a name (string() or atom()) per instance which allows @@ -30,7 +30,6 @@ %%% Can be configured with start_link parameters or use a config file to get %%% host to connect to, dn, password, log function etc. - %%% Modified by Alexey Shchepin %%% Modified by Evgeniy Khramtsov @@ -55,7 +54,6 @@ %%% -------------------------------------------------------------------- -vc('$Id$ '). - %%%---------------------------------------------------------------------- %%% LDAP Client state machine. %%% Possible states are: @@ -72,68 +70,95 @@ %% External exports -export([start_link/1, start_link/6]). --export([baseObject/0,singleLevel/0,wholeSubtree/0,close/1, - equalityMatch/2,greaterOrEqual/2,lessOrEqual/2, - approxMatch/2,search/2,substrings/2,present/1,extensibleMatch/2, - 'and'/1,'or'/1,'not'/1,modify/3, mod_add/2, mod_delete/2, - mod_replace/2, add/3, delete/2, modify_dn/5, modify_passwd/3, bind/3]). +-export([baseObject/0, singleLevel/0, wholeSubtree/0, + close/1, equalityMatch/2, greaterOrEqual/2, + lessOrEqual/2, approxMatch/2, search/2, substrings/2, + present/1, extensibleMatch/2, 'and'/1, 'or'/1, 'not'/1, + modify/3, mod_add/2, mod_delete/2, mod_replace/2, add/3, + delete/2, modify_dn/5, modify_passwd/3, bind/3]). + -export([get_status/1]). %% gen_fsm callbacks --export([init/1, connecting/2, - connecting/3, wait_bind_response/3, active/3, active_bind/3, handle_event/3, - handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). +-export([init/1, connecting/2, connecting/3, + wait_bind_response/3, active/3, active_bind/3, + handle_event/3, handle_sync_event/4, handle_info/3, + terminate/3, code_change/4]). - --import(lists,[concat/1]). +-export_type([filter/0]). -include("ELDAPv3.hrl"). + -include("eldap.hrl"). -define(LDAP_VERSION, 3). + -define(RETRY_TIMEOUT, 500). + -define(BIND_TIMEOUT, 10000). + -define(CMD_TIMEOUT, 100000). %% Used in gen_fsm sync calls. --define(CALL_TIMEOUT, ?CMD_TIMEOUT + ?BIND_TIMEOUT + ?RETRY_TIMEOUT). %% Used as a timeout for gen_tcp:send/2 + +-define(CALL_TIMEOUT, + (?CMD_TIMEOUT) + (?BIND_TIMEOUT) + (?RETRY_TIMEOUT)). + -define(SEND_TIMEOUT, 30000). + -define(MAX_TRANSACTION_ID, 65535). + -define(MIN_TRANSACTION_ID, 0). %% Grace period after "soft" LDAP bind errors: + -define(GRACEFUL_RETRY_TIMEOUT, 5000). --define(SUPPORTEDEXTENSION, "1.3.6.1.4.1.1466.101.120.7"). --define(SUPPORTEDEXTENSIONSYNTAX, "1.3.6.1.4.1.1466.115.121.1.38"). --define(STARTTLS, "1.3.6.1.4.1.1466.20037"). +-define(SUPPORTEDEXTENSION, + <<"1.3.6.1.4.1.1466.101.120.7">>). --record(eldap, {version = ?LDAP_VERSION, - hosts, % Possible hosts running LDAP servers - host = null, % Connected Host LDAP server - port = 389, % The LDAP server port - sockmod, % SockMod (gen_tcp|tls) - tls = none, % LDAP/LDAPS (none|tls) - tls_options = [], - fd = null, % Socket filedescriptor. - rootdn = "", % Name of the entry to bind as - passwd, % Password for (above) entry - id = 0, % LDAP Request ID - bind_timer, % Ref to bind timeout - dict, % dict holding operation params and results - req_q % Queue for requests - }). +-define(SUPPORTEDEXTENSIONSYNTAX, + <<"1.3.6.1.4.1.1466.115.121.1.38">>). + +-define(STARTTLS, <<"1.3.6.1.4.1.1466.20037">>). + +-type handle() :: pid() | atom() | binary(). + +-record(eldap, + {version = ?LDAP_VERSION :: non_neg_integer(), + hosts = [] :: [binary()], + host :: binary(), + port = 389 :: inet:port_number(), + sockmod = gen_tcp :: ssl | gen_tcp, + tls = none :: none | tls, + tls_options = [] :: [{cacertfile, string()} | + {depth, non_neg_integer()} | + {verify, non_neg_integer()}], + fd, + rootdn = <<"">> :: binary(), + passwd = <<"">> :: binary(), + id = 0 :: non_neg_integer(), + bind_timer = make_ref() :: reference(), + dict = dict:new() :: dict(), + req_q = queue:new() :: queue()}). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- start_link(Name) -> - Reg_name = list_to_atom("eldap_" ++ Name), + Reg_name = jlib:binary_to_atom(<<"eldap_", + Name/binary>>), gen_fsm:start_link({local, Reg_name}, ?MODULE, [], []). +-spec start_link(binary(), [binary()], inet:port_number(), binary(), + binary(), tlsopts()) -> any(). + start_link(Name, Hosts, Port, Rootdn, Passwd, Opts) -> - Reg_name = list_to_atom("eldap_" ++ Name), + Reg_name = jlib:binary_to_atom(<<"eldap_", + Name/binary>>), gen_fsm:start_link({local, Reg_name}, ?MODULE, - {Hosts, Port, Rootdn, Passwd, Opts}, []). + [Hosts, Port, Rootdn, Passwd, Opts], []). + +-spec get_status(handle()) -> any(). %%% -------------------------------------------------------------------- %%% Get status of connection. @@ -145,6 +170,8 @@ get_status(Handle) -> %%% -------------------------------------------------------------------- %%% Shutdown connection (and process) asynchronous. %%% -------------------------------------------------------------------- +-spec close(handle()) -> any(). + close(Handle) -> Handle1 = get_handle(Handle), gen_fsm:send_all_state_event(Handle1, close). @@ -162,23 +189,21 @@ close(Handle) -> %%% {"telephoneNumber", ["545 555 00"]}] %%% ) %%% -------------------------------------------------------------------- -add(Handle, Entry, Attributes) when is_list(Entry), is_list(Attributes) -> +add(Handle, Entry, Attributes) -> Handle1 = get_handle(Handle), - gen_fsm:sync_send_event(Handle1, {add, Entry, add_attrs(Attributes)}, - ?CALL_TIMEOUT). + gen_fsm:sync_send_event(Handle1, + {add, Entry, add_attrs(Attributes)}, ?CALL_TIMEOUT). %%% Do sanity check ! add_attrs(Attrs) -> - F = fun({Type,Vals}) when is_list(Type), is_list(Vals) -> - %% Confused ? Me too... :-/ - {'AddRequest_attributes',Type, Vals} + F = fun ({Type, Vals}) -> + {'AddRequest_attributes', Type, Vals} end, case catch lists:map(F, Attrs) of - {'EXIT', _} -> throw({error, attribute_values}); - Else -> Else + {'EXIT', _} -> throw({error, attribute_values}); + Else -> Else end. - %%% -------------------------------------------------------------------- %%% Delete an entry. The entry consists of the DN of %%% the entry to be deleted. @@ -188,9 +213,10 @@ add_attrs(Attrs) -> %%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com" %%% ) %%% -------------------------------------------------------------------- -delete(Handle, Entry) when is_list(Entry) -> +delete(Handle, Entry) -> Handle1 = get_handle(Handle), - gen_fsm:sync_send_event(Handle1, {delete, Entry}, ?CALL_TIMEOUT). + gen_fsm:sync_send_event(Handle1, {delete, Entry}, + ?CALL_TIMEOUT). %%% -------------------------------------------------------------------- %%% Modify an entry. Given an entry a number of modification @@ -203,25 +229,23 @@ delete(Handle, Entry) when is_list(Entry) -> %%% add("description", ["LDAP hacker"])] %%% ) %%% -------------------------------------------------------------------- -modify(Handle, Object, Mods) when is_list(Object), is_list(Mods) -> +-spec modify(handle(), any(), [add | delete | replace]) -> any(). + +modify(Handle, Object, Mods) -> Handle1 = get_handle(Handle), - gen_fsm:sync_send_event(Handle1, {modify, Object, Mods}, ?CALL_TIMEOUT). + gen_fsm:sync_send_event(Handle1, {modify, Object, Mods}, + ?CALL_TIMEOUT). %%% %%% Modification operations. %%% Example: %%% replace("telephoneNumber", ["555 555 00"]) %%% -mod_add(Type, Values) when is_list(Type), is_list(Values) -> m(add, Type, Values). -mod_delete(Type, Values) when is_list(Type), is_list(Values) -> m(delete, Type, Values). -mod_replace(Type, Values) when is_list(Type), is_list(Values) -> m(replace, Type, Values). +mod_add(Type, Values) -> + m(add, Type, Values). -m(Operation, Type, Values) -> - #'ModifyRequest_modification_SEQOF'{ - operation = Operation, - modification = #'AttributeTypeAndValues'{ - type = Type, - vals = Values}}. +mod_delete(Type, Values) -> + m(delete, Type, Values). %%% -------------------------------------------------------------------- %%% Modify an entry. Given an entry a number of modification @@ -235,18 +259,31 @@ m(Operation, Type, Values) -> %%% "" %%% ) %%% -------------------------------------------------------------------- -modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) - when is_list(Entry), is_list(NewRDN), is_atom(DelOldRDN), is_list(NewSup) -> - Handle1 = get_handle(Handle), - gen_fsm:sync_send_event( - Handle1, - {modify_dn, Entry, NewRDN, bool_p(DelOldRDN), optional(NewSup)}, - ?CALL_TIMEOUT). +mod_replace(Type, Values) -> + m(replace, Type, Values). -modify_passwd(Handle, DN, Passwd) when is_list(DN), is_list(Passwd) -> +m(Operation, Type, Values) -> + #'ModifyRequest_modification_SEQOF'{operation = + Operation, + modification = + #'AttributeTypeAndValues'{type = + Type, + vals = + Values}}. + +modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) -> Handle1 = get_handle(Handle), - gen_fsm:sync_send_event( - Handle1, {modify_passwd, DN, Passwd}, ?CALL_TIMEOUT). + gen_fsm:sync_send_event(Handle1, + {modify_dn, Entry, NewRDN, bool_p(DelOldRDN), + optional(NewSup)}, + ?CALL_TIMEOUT). + +-spec modify_passwd(handle(), binary(), binary()) -> any(). + +modify_passwd(Handle, DN, Passwd) -> + Handle1 = get_handle(Handle), + gen_fsm:sync_send_event(Handle1, + {modify_passwd, DN, Passwd}, ?CALL_TIMEOUT). %%% -------------------------------------------------------------------- %%% Bind. @@ -256,16 +293,18 @@ modify_passwd(Handle, DN, Passwd) when is_list(DN), is_list(Passwd) -> %%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com", %%% "secret") %%% -------------------------------------------------------------------- -bind(Handle, RootDN, Passwd) - when is_list(RootDN), is_list(Passwd) -> +-spec bind(handle(), binary(), binary()) -> any(). + +bind(Handle, RootDN, Passwd) -> Handle1 = get_handle(Handle), - gen_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd}, ?CALL_TIMEOUT). + gen_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd}, + ?CALL_TIMEOUT). %%% Sanity checks ! -bool_p(Bool) when Bool==true;Bool==false -> Bool. +bool_p(Bool) when Bool == true; Bool == false -> Bool. -optional([]) -> asn1_NOVALUE; +optional([]) -> asn1_NOVALUE; optional(Value) -> Value. %%% -------------------------------------------------------------------- @@ -293,82 +332,149 @@ optional(Value) -> Value. %%% []}} %%% %%% -------------------------------------------------------------------- +-type search_args() :: [{base, binary()} | + {filter, filter()} | + {scope, scope()} | + {attributes, [binary()]} | + {types_only, boolean()} | + {timeout, non_neg_integer()} | + {limit, non_neg_integer()} | + {deref_aliases, never | searching | finding | always}]. + +-spec search(handle(), eldap_search() | search_args()) -> any(). + search(Handle, A) when is_record(A, eldap_search) -> call_search(Handle, A); search(Handle, L) when is_list(L) -> case catch parse_search_args(L) of - {error, Emsg} -> {error, Emsg}; - {'EXIT', Emsg} -> {error, Emsg}; - A when is_record(A, eldap_search) -> call_search(Handle, A) + {error, Emsg} -> {error, Emsg}; + {'EXIT', Emsg} -> {error, Emsg}; + A when is_record(A, eldap_search) -> + call_search(Handle, A) end. call_search(Handle, A) -> Handle1 = get_handle(Handle), - gen_fsm:sync_send_event(Handle1, {search, A}, ?CALL_TIMEOUT). + gen_fsm:sync_send_event(Handle1, {search, A}, + ?CALL_TIMEOUT). + +-spec parse_search_args(search_args()) -> eldap_search(). parse_search_args(Args) -> - parse_search_args(Args, #eldap_search{scope = wholeSubtree}). + parse_search_args(Args, + #eldap_search{scope = wholeSubtree}). -parse_search_args([{base, Base}|T],A) -> - parse_search_args(T,A#eldap_search{base = Base}); -parse_search_args([{filter, Filter}|T],A) -> - parse_search_args(T,A#eldap_search{filter = Filter}); -parse_search_args([{scope, Scope}|T],A) -> - parse_search_args(T,A#eldap_search{scope = Scope}); -parse_search_args([{attributes, Attrs}|T],A) -> - parse_search_args(T,A#eldap_search{attributes = Attrs}); -parse_search_args([{types_only, TypesOnly}|T],A) -> - parse_search_args(T,A#eldap_search{types_only = TypesOnly}); -parse_search_args([{timeout, Timeout}|T],A) when is_integer(Timeout) -> - parse_search_args(T,A#eldap_search{timeout = Timeout}); -parse_search_args([{limit, Limit}|T],A) when is_integer(Limit) -> - parse_search_args(T,A#eldap_search{limit = Limit}); -parse_search_args([{deref_aliases, never}|T],A) -> - parse_search_args(T,A#eldap_search{deref_aliases = neverDerefAliases}); -parse_search_args([{deref_aliases, searching}|T],A) -> - parse_search_args(T,A#eldap_search{deref_aliases = derefInSearching}); -parse_search_args([{deref_aliases, finding}|T],A) -> - parse_search_args(T,A#eldap_search{deref_aliases = derefFindingBaseObj}); -parse_search_args([{deref_aliases, always}|T],A) -> - parse_search_args(T,A#eldap_search{deref_aliases = derefAlways}); -parse_search_args([H|_],_) -> - throw({error,{unknown_arg, H}}); -parse_search_args([],A) -> - A. +parse_search_args([{base, Base} | T], A) -> + parse_search_args(T, A#eldap_search{base = Base}); +parse_search_args([{filter, Filter} | T], A) -> + parse_search_args(T, A#eldap_search{filter = Filter}); +parse_search_args([{scope, Scope} | T], A) -> + parse_search_args(T, A#eldap_search{scope = Scope}); +parse_search_args([{attributes, Attrs} | T], A) -> + parse_search_args(T, + A#eldap_search{attributes = Attrs}); +parse_search_args([{types_only, TypesOnly} | T], A) -> + parse_search_args(T, + A#eldap_search{types_only = TypesOnly}); +parse_search_args([{timeout, Timeout} | T], A) + when is_integer(Timeout) -> + parse_search_args(T, A#eldap_search{timeout = Timeout}); +parse_search_args([{limit, Limit} | T], A) + when is_integer(Limit) -> + parse_search_args(T, A#eldap_search{limit = Limit}); +parse_search_args([{deref_aliases, never} | T], A) -> + parse_search_args(T, + A#eldap_search{deref_aliases = neverDerefAliases}); +parse_search_args([{deref_aliases, searching} | T], + A) -> + parse_search_args(T, + A#eldap_search{deref_aliases = derefInSearching}); +parse_search_args([{deref_aliases, finding} | T], A) -> + parse_search_args(T, + A#eldap_search{deref_aliases = derefFindingBaseObj}); +parse_search_args([{deref_aliases, always} | T], A) -> + parse_search_args(T, + A#eldap_search{deref_aliases = derefAlways}); +parse_search_args([H | _], _) -> + throw({error, {unknown_arg, H}}); +parse_search_args([], A) -> A. + +baseObject() -> baseObject. + +singleLevel() -> singleLevel. %%% %%% The Scope parameter %%% -baseObject() -> baseObject. -singleLevel() -> singleLevel. wholeSubtree() -> wholeSubtree. %%% %%% Boolean filter operations %%% -'and'(ListOfFilters) when is_list(ListOfFilters) -> {'and',ListOfFilters}. -'or'(ListOfFilters) when is_list(ListOfFilters) -> {'or', ListOfFilters}. -'not'(Filter) when is_tuple(Filter) -> {'not',Filter}. +-type filter() :: 'and'() | 'or'() | 'not'() | equalityMatch() | + greaterOrEqual() | lessOrEqual() | approxMatch() | + present() | substrings() | extensibleMatch(). %%% %%% The following Filter parameters consist of an attribute %%% and an attribute value. Example: F("uid","tobbe") %%% -equalityMatch(Desc, Value) -> {equalityMatch, av_assert(Desc, Value)}. -greaterOrEqual(Desc, Value) -> {greaterOrEqual, av_assert(Desc, Value)}. -lessOrEqual(Desc, Value) -> {lessOrEqual, av_assert(Desc, Value)}. -approxMatch(Desc, Value) -> {approxMatch, av_assert(Desc, Value)}. +-type 'and'() :: {'and', [filter()]}. +-spec 'and'([filter()]) -> 'and'(). + +'and'(ListOfFilters) when is_list(ListOfFilters) -> + {'and', ListOfFilters}. + +-type 'or'() :: {'or', [filter()]}. +-spec 'or'([filter()]) -> 'or'(). + +'or'(ListOfFilters) when is_list(ListOfFilters) -> + {'or', ListOfFilters}. + +-type 'not'() :: {'not', filter()}. +-spec 'not'(filter()) -> 'not'(). + +'not'(Filter) when is_tuple(Filter) -> {'not', Filter}. + +-type equalityMatch() :: {equalityMatch, 'AttributeValueAssertion'()}. +-spec equalityMatch(binary(), binary()) -> equalityMatch(). + +equalityMatch(Desc, Value) -> + {equalityMatch, av_assert(Desc, Value)}. + +-type greaterOrEqual() :: {greaterOrEqual, 'AttributeValueAssertion'()}. +-spec greaterOrEqual(binary(), binary()) -> greaterOrEqual(). + +greaterOrEqual(Desc, Value) -> + {greaterOrEqual, av_assert(Desc, Value)}. + +-type lessOrEqual() :: {lessOrEqual, 'AttributeValueAssertion'()}. +-spec lessOrEqual(binary(), binary()) -> lessOrEqual(). + +lessOrEqual(Desc, Value) -> + {lessOrEqual, av_assert(Desc, Value)}. + +-type approxMatch() :: {approxMatch, 'AttributeValueAssertion'()}. +-spec approxMatch(binary(), binary()) -> approxMatch(). + +approxMatch(Desc, Value) -> + {approxMatch, av_assert(Desc, Value)}. + +-type 'AttributeValueAssertion'() :: + #'AttributeValueAssertion'{attributeDesc :: binary(), + assertionValue :: binary()}. + +-spec av_assert(binary(), binary()) -> 'AttributeValueAssertion'(). av_assert(Desc, Value) -> - #'AttributeValueAssertion'{attributeDesc = Desc, + #'AttributeValueAssertion'{attributeDesc = Desc, assertionValue = Value}. %%% %%% Filter to check for the presence of an attribute %%% -present(Attribute) when is_list(Attribute) -> - {present, Attribute}. - +-type present() :: {present, binary()}. +-spec present(binary()) -> present(). %%% %%% A substring filter seem to be based on a pattern: @@ -385,10 +491,8 @@ present(Attribute) when is_list(Attribute) -> %%% Example: substrings("sn",[{initial,"To"},{any,"kv"},{final,"st"}]) %%% will match entries containing: 'sn: Tornkvist' %%% -substrings(Type, SubStr) when is_list(Type), is_list(SubStr) -> - Ss = {'SubstringFilter_substrings',v_substr(SubStr)}, - {substrings,#'SubstringFilter'{type = Type, - substrings = Ss}}. +present(Attribute) -> + {present, Attribute}. %%% %%% extensibleMatch filter. @@ -399,24 +503,56 @@ substrings(Type, SubStr) when is_list(Type), is_list(SubStr) -> %%% %%% Example: extensibleMatch("Fred", [{matchingRule, "1.2.3.4.5"}, {type, "cn"}]). %%% -extensibleMatch(Value, Opts) when is_list(Value), is_list(Opts) -> - MRA = #'MatchingRuleAssertion'{matchValue=Value}, - {extensibleMatch, extensibleMatch_opts(Opts, MRA)}. +-type substr() :: [{initial | any | final, binary()}]. +-type 'SubstringFilter'() :: + #'SubstringFilter'{type :: binary(), + substrings :: {'SubstringFilter_substrings', + substr()}}. -extensibleMatch_opts([{matchingRule, Rule} | Opts], MRA) when is_list(Rule) -> - extensibleMatch_opts(Opts, MRA#'MatchingRuleAssertion'{matchingRule=Rule}); -extensibleMatch_opts([{type, Desc} | Opts], MRA) when is_list(Desc) -> - extensibleMatch_opts(Opts, MRA#'MatchingRuleAssertion'{type=Desc}); -extensibleMatch_opts([{dnAttributes, true} | Opts], MRA) -> - extensibleMatch_opts(Opts, MRA#'MatchingRuleAssertion'{dnAttributes=true}); +-type substrings() :: {substrings, 'SubstringFilter'()}. +-spec substrings(binary(), substr()) -> substrings(). + +substrings(Type, SubStr) -> + Ss = {'SubstringFilter_substrings', SubStr}, + {substrings, + #'SubstringFilter'{type = Type, substrings = Ss}}. + +-type match_opts() :: [{matchingRule | type, binary()} | + {dnAttributes, boolean()}]. + +-type 'MatchingRuleAssertion'() :: + #'MatchingRuleAssertion'{matchValue :: binary(), + type :: asn1_NOVALUE | binary(), + matchingRule :: asn1_NOVALUE | binary(), + dnAttributes :: asn1_DEFAULT | true}. + +-type extensibleMatch() :: {extensibleMatch, 'MatchingRuleAssertion'()}. +-spec extensibleMatch(binary(), match_opts()) -> extensibleMatch(). + +extensibleMatch(Value, Opts) -> + MRA = #'MatchingRuleAssertion'{matchValue = Value}, + {extensibleMatch, extensibleMatch_opts(Opts, MRA)}. + +extensibleMatch_opts([{matchingRule, Rule} | Opts], MRA) -> + extensibleMatch_opts(Opts, + MRA#'MatchingRuleAssertion'{matchingRule = Rule}); +extensibleMatch_opts([{type, Desc} | Opts], MRA) -> + extensibleMatch_opts(Opts, + MRA#'MatchingRuleAssertion'{type = Desc}); +extensibleMatch_opts([{dnAttributes, true} | Opts], + MRA) -> + extensibleMatch_opts(Opts, + MRA#'MatchingRuleAssertion'{dnAttributes = true}); extensibleMatch_opts([_ | Opts], MRA) -> - extensibleMatch_opts(Opts, MRA); -extensibleMatch_opts([], MRA) -> - MRA. + extensibleMatch_opts(Opts, MRA); +extensibleMatch_opts([], MRA) -> MRA. -get_handle(Pid) when is_pid(Pid) -> Pid; +get_handle(Pid) when is_pid(Pid) -> Pid; get_handle(Atom) when is_atom(Atom) -> Atom; -get_handle(Name) when is_list(Name) -> list_to_atom("eldap_" ++ Name). +get_handle(Name) when is_binary(Name) -> + jlib:binary_to_atom(<<"eldap_", + Name/binary>>). + %%%---------------------------------------------------------------------- %%% Callback functions from gen_fsm %%%---------------------------------------------------------------------- @@ -430,63 +566,71 @@ get_handle(Name) when is_list(Name) -> list_to_atom("eldap_" ++ Name). %% I use the trick of setting a timeout of 0 to pass control into the %% process. %%---------------------------------------------------------------------- -init([]) -> - case get_config() of - {ok, Hosts, Port, Rootdn, Passwd, Opts} -> - init({Hosts, Port, Rootdn, Passwd, Opts}); - {error, Reason} -> - {stop, Reason} - end; -init({Hosts, Port, Rootdn, Passwd, Opts}) -> +init([Hosts, Port, Rootdn, Passwd, Opts]) -> catch ssl:start(), - %% ssl:seed was removed in OTP R14B04, newer Dialyzer will complain - catch ssl:seed(randoms:get_string()), - Encrypt = case proplists:get_value(encrypt, Opts) of - tls -> tls; - _ -> none + Encrypt = case gen_mod:get_opt(encrypt, Opts, + fun(tls) -> tls; + (starttls) -> starttls; + (none) -> none + end) of + tls -> tls; + _ -> none end, PortTemp = case Port of - undefined -> - case Encrypt of - tls -> - ?LDAPS_PORT; - _ -> - ?LDAP_PORT - end; - PT -> PT + undefined -> + case Encrypt of + tls -> ?LDAPS_PORT; + _ -> ?LDAP_PORT + end; + PT -> PT end, - CacertOpts = case proplists:get_value(tls_cacertfile, Opts) of - [_|_] = Path -> [{cacertfile, Path}]; - _ -> [] + CacertOpts = case gen_mod:get_opt( + tls_cacertfile, Opts, + fun(S) when is_binary(S) -> + binary_to_list(S); + (undefined) -> + undefined + end) of + undefined -> + []; + Path -> + [{cacertfile, Path}] end, - DepthOpts = case proplists:get_value(tls_depth, Opts) of - Depth when is_integer(Depth), Depth >= 0 -> - [{depth, Depth}]; - _ -> [] + DepthOpts = case gen_mod:get_opt( + tls_depth, Opts, + fun(I) when is_integer(I), I>=0 -> + I; + (undefined) -> + undefined + end) of + undefined -> + []; + Depth -> + [{depth, Depth}] end, - Verify = proplists:get_value(tls_verify, Opts), + Verify = gen_mod:get_opt(tls_verify, Opts, + fun(hard) -> hard; + (soft) -> soft; + (false) -> false + end, false), TLSOpts = if (Verify == hard orelse Verify == soft) - andalso CacertOpts == [] -> - ?WARNING_MSG("TLS verification is enabled " - "but no CA certfiles configured, so " - "verification is disabled.", []), - []; - Verify == soft -> - [{verify, 1}] ++ CacertOpts ++ DepthOpts; - Verify == hard -> - [{verify, 2}] ++ CacertOpts ++ DepthOpts; - true -> - [] - end, - {ok, connecting, #eldap{hosts = Hosts, - port = PortTemp, - rootdn = Rootdn, - passwd = Passwd, - tls = Encrypt, - tls_options = TLSOpts, - id = 0, - dict = dict:new(), - req_q = queue:new()}, 0}. + andalso CacertOpts == [] -> + ?WARNING_MSG("TLS verification is enabled but no CA " + "certfiles configured, so verification " + "is disabled.", + []), + []; + Verify == soft -> + [{verify, 1}] ++ CacertOpts ++ DepthOpts; + Verify == hard -> + [{verify, 2}] ++ CacertOpts ++ DepthOpts; + true -> [] + end, + {ok, connecting, + #eldap{hosts = Hosts, port = PortTemp, rootdn = Rootdn, + passwd = Passwd, tls = Encrypt, tls_options = TLSOpts, + id = 0, dict = dict:new(), req_q = queue:new()}, + 0}. %%---------------------------------------------------------------------- %% Func: StateName/2 @@ -511,15 +655,15 @@ connecting(timeout, S) -> %%---------------------------------------------------------------------- connecting(Event, From, S) -> Q = queue:in({Event, From}, S#eldap.req_q), - {next_state, connecting, S#eldap{req_q=Q}}. + {next_state, connecting, S#eldap{req_q = Q}}. wait_bind_response(Event, From, S) -> Q = queue:in({Event, From}, S#eldap.req_q), - {next_state, wait_bind_response, S#eldap{req_q=Q}}. + {next_state, wait_bind_response, S#eldap{req_q = Q}}. active_bind(Event, From, S) -> Q = queue:in({Event, From}, S#eldap.req_q), - {next_state, active_bind, S#eldap{req_q=Q}}. + {next_state, active_bind, S#eldap{req_q = Q}}. active(Event, From, S) -> process_command(S, Event, From). @@ -534,7 +678,6 @@ active(Event, From, S) -> handle_event(close, _StateName, S) -> catch (S#eldap.sockmod):close(S#eldap.fd), {stop, normal, S}; - handle_event(_Event, StateName, S) -> {next_state, StateName, S}. @@ -556,6 +699,7 @@ handle_sync_event(_Event, _From, StateName, S) -> %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} +%% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- %% @@ -563,83 +707,78 @@ handle_sync_event(_Event, _From, StateName, S) -> %% handle_info({Tag, _Socket, Data}, connecting, S) when Tag == tcp; Tag == ssl -> - ?DEBUG("tcp packet received when disconnected!~n~p", [Data]), + ?DEBUG("tcp packet received when disconnected!~n~p", + [Data]), {next_state, connecting, S}; - handle_info({Tag, _Socket, Data}, wait_bind_response, S) when Tag == tcp; Tag == ssl -> cancel_timer(S#eldap.bind_timer), case catch recvd_wait_bind_response(Data, S) of - bound -> - dequeue_commands(S); - {fail_bind, Reason} -> - report_bind_failure(S#eldap.host, S#eldap.port, Reason), - {next_state, connecting, close_and_retry(S, ?GRACEFUL_RETRY_TIMEOUT)}; - {'EXIT', Reason} -> - report_bind_failure(S#eldap.host, S#eldap.port, Reason), - {next_state, connecting, close_and_retry(S)}; - {error, Reason} -> - report_bind_failure(S#eldap.host, S#eldap.port, Reason), - {next_state, connecting, close_and_retry(S)} + bound -> dequeue_commands(S); + {fail_bind, Reason} -> + report_bind_failure(S#eldap.host, S#eldap.port, Reason), + {next_state, connecting, + close_and_retry(S, ?GRACEFUL_RETRY_TIMEOUT)}; + {'EXIT', Reason} -> + report_bind_failure(S#eldap.host, S#eldap.port, Reason), + {next_state, connecting, close_and_retry(S)}; + {error, Reason} -> + report_bind_failure(S#eldap.host, S#eldap.port, Reason), + {next_state, connecting, close_and_retry(S)} end; - handle_info({Tag, _Socket, Data}, StateName, S) - when (StateName == active orelse StateName == active_bind) andalso - (Tag == tcp orelse Tag == ssl) -> + when (StateName == active orelse + StateName == active_bind) + andalso (Tag == tcp orelse Tag == ssl) -> case catch recvd_packet(Data, S) of - {response, Response, RequestType} -> - NewS = case Response of - {reply, Reply, To, S1} -> - gen_fsm:reply(To, Reply), - S1; - {ok, S1} -> - S1 - end, - if (StateName == active_bind andalso - RequestType == bindRequest) orelse - (StateName == active) -> - dequeue_commands(NewS); - true -> - {next_state, StateName, NewS} - end; - _ -> - {next_state, StateName, S} + {response, Response, RequestType} -> + NewS = case Response of + {reply, Reply, To, S1} -> gen_fsm:reply(To, Reply), S1; + {ok, S1} -> S1 + end, + if StateName == active_bind andalso + RequestType == bindRequest + orelse StateName == active -> + dequeue_commands(NewS); + true -> {next_state, StateName, NewS} + end; + _ -> {next_state, StateName, S} end; - handle_info({Tag, _Socket}, Fsm_state, S) when Tag == tcp_closed; Tag == ssl_closed -> - ?WARNING_MSG("LDAP server closed the connection: ~s:~p~nIn State: ~p", - [S#eldap.host, S#eldap.port ,Fsm_state]), + ?WARNING_MSG("LDAP server closed the connection: ~s:~p~nIn " + "State: ~p", + [S#eldap.host, S#eldap.port, Fsm_state]), {next_state, connecting, close_and_retry(S)}; - handle_info({Tag, _Socket, Reason}, Fsm_state, S) when Tag == tcp_error; Tag == ssl_error -> - ?DEBUG("eldap received tcp_error: ~p~nIn State: ~p", [Reason, Fsm_state]), + ?DEBUG("eldap received tcp_error: ~p~nIn State: ~p", + [Reason, Fsm_state]), {next_state, connecting, close_and_retry(S)}; - %% %% Timers %% -handle_info({timeout, Timer, {cmd_timeout, Id}}, StateName, S) -> +handle_info({timeout, Timer, {cmd_timeout, Id}}, + StateName, S) -> case cmd_timeout(Timer, Id, S) of - {reply, To, Reason, NewS} -> gen_fsm:reply(To, Reason), - {next_state, StateName, NewS}; - {error, _Reason} -> {next_state, StateName, S} + {reply, To, Reason, NewS} -> + gen_fsm:reply(To, Reason), + {next_state, StateName, NewS}; + {error, _Reason} -> {next_state, StateName, S} end; - handle_info({timeout, retry_connect}, connecting, S) -> - {ok, NextState, NewS} = connect_bind(S), + {ok, NextState, NewS} = connect_bind(S), {next_state, NextState, NewS}; - -handle_info({timeout, _Timer, bind_timeout}, wait_bind_response, S) -> +handle_info({timeout, _Timer, bind_timeout}, + wait_bind_response, S) -> {next_state, connecting, close_and_retry(S)}; - %% %% Make sure we don't fill the message queue with rubbish %% handle_info(Info, StateName, S) -> - ?DEBUG("eldap. Unexpected Info: ~p~nIn state: ~p~n when StateData is: ~p", - [Info, StateName, S]), + ?DEBUG("eldap. Unexpected Info: ~p~nIn state: " + "~p~n when StateData is: ~p", + [Info, StateName, S]), {next_state, StateName, S}. %%---------------------------------------------------------------------- @@ -647,8 +786,7 @@ handle_info(Info, StateName, S) -> %% Purpose: Shutdown the fsm %% Returns: any %%---------------------------------------------------------------------- -terminate(_Reason, _StateName, _StatData) -> - ok. +terminate(_Reason, _StateName, _StatData) -> ok. %%---------------------------------------------------------------------- %% Func: code_change/4 @@ -663,91 +801,79 @@ code_change(_OldVsn, StateName, S, _Extra) -> %%%---------------------------------------------------------------------- dequeue_commands(S) -> case queue:out(S#eldap.req_q) of - {{value, {Event, From}}, Q} -> - case process_command(S#eldap{req_q=Q}, Event, From) of - {_, active, NewS} -> - dequeue_commands(NewS); - Res -> - Res - end; - {empty, _} -> - {next_state, active, S} + {{value, {Event, From}}, Q} -> + case process_command(S#eldap{req_q = Q}, Event, From) of + {_, active, NewS} -> dequeue_commands(NewS); + Res -> Res + end; + {empty, _} -> {next_state, active, S} end. process_command(S, Event, From) -> case send_command(Event, From, S) of - {ok, NewS} -> - case Event of - {bind, _, _} -> - {next_state, active_bind, NewS}; - _ -> - {next_state, active, NewS} - end; - {error, _Reason} -> - Q = queue:in_r({Event, From}, S#eldap.req_q), - NewS = close_and_retry(S#eldap{req_q=Q}), - {next_state, connecting, NewS} + {ok, NewS} -> + case Event of + {bind, _, _} -> {next_state, active_bind, NewS}; + _ -> {next_state, active, NewS} + end; + {error, _Reason} -> + Q = queue:in_r({Event, From}, S#eldap.req_q), + NewS = close_and_retry(S#eldap{req_q = Q}), + {next_state, connecting, NewS} end. send_command(Command, From, S) -> Id = bump_id(S), {Name, Request} = gen_req(Command), - Message = #'LDAPMessage'{messageID = Id, + Message = #'LDAPMessage'{messageID = Id, protocolOp = {Name, Request}}, - ?DEBUG("~p~n",[{Name, Request}]), - {ok, Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message), + ?DEBUG("~p~n", [{Name, Request}]), + {ok, Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', + Message), case (S#eldap.sockmod):send(S#eldap.fd, Bytes) of - ok -> - Timer = erlang:start_timer(?CMD_TIMEOUT, self(), {cmd_timeout, Id}), - New_dict = dict:store(Id, [{Timer, Command, From, Name}], S#eldap.dict), - {ok, S#eldap{id = Id, dict = New_dict}}; - Error -> - Error + ok -> + Timer = erlang:start_timer(?CMD_TIMEOUT, self(), + {cmd_timeout, Id}), + New_dict = dict:store(Id, + [{Timer, Command, From, Name}], S#eldap.dict), + {ok, S#eldap{id = Id, dict = New_dict}}; + Error -> Error end. gen_req({search, A}) -> {searchRequest, - #'SearchRequest'{baseObject = A#eldap_search.base, - scope = v_scope(A#eldap_search.scope), + #'SearchRequest'{baseObject = A#eldap_search.base, + scope = A#eldap_search.scope, derefAliases = A#eldap_search.deref_aliases, - sizeLimit = A#eldap_search.limit, - timeLimit = v_timeout(A#eldap_search.timeout), - typesOnly = v_bool(A#eldap_search.types_only), - filter = v_filter(A#eldap_search.filter), - attributes = v_attributes(A#eldap_search.attributes) - }}; + sizeLimit = A#eldap_search.limit, + timeLimit = A#eldap_search.timeout, + typesOnly = A#eldap_search.types_only, + filter = A#eldap_search.filter, + attributes = A#eldap_search.attributes}}; gen_req({add, Entry, Attrs}) -> {addRequest, - #'AddRequest'{entry = Entry, - attributes = Attrs}}; -gen_req({delete, Entry}) -> - {delRequest, Entry}; + #'AddRequest'{entry = Entry, attributes = Attrs}}; +gen_req({delete, Entry}) -> {delRequest, Entry}; gen_req({modify, Obj, Mod}) -> - v_modifications(Mod), - {modifyRequest, - #'ModifyRequest'{object = Obj, - modification = Mod}}; -gen_req({modify_dn, Entry, NewRDN, DelOldRDN, NewSup}) -> + {modifyRequest, + #'ModifyRequest'{object = Obj, modification = Mod}}; +gen_req({modify_dn, Entry, NewRDN, DelOldRDN, + NewSup}) -> {modDNRequest, - #'ModifyDNRequest'{entry = Entry, - newrdn = NewRDN, - deleteoldrdn = DelOldRDN, - newSuperior = NewSup}}; - + #'ModifyDNRequest'{entry = Entry, newrdn = NewRDN, + deleteoldrdn = DelOldRDN, newSuperior = NewSup}}; gen_req({modify_passwd, DN, Passwd}) -> - {ok, ReqVal} = asn1rt:encode( - 'ELDAPv3', 'PasswdModifyRequestValue', - #'PasswdModifyRequestValue'{ - userIdentity = DN, - newPasswd = Passwd}), + {ok, ReqVal} = asn1rt:encode('ELDAPv3', + 'PasswdModifyRequestValue', + #'PasswdModifyRequestValue'{userIdentity = DN, + newPasswd = + Passwd}), {extendedReq, #'ExtendedRequest'{requestName = ?passwdModifyOID, - requestValue = list_to_binary(ReqVal)}}; - + requestValue = iolist_to_binary(ReqVal)}}; gen_req({bind, RootDN, Passwd}) -> {bindRequest, - #'BindRequest'{version = ?LDAP_VERSION, - name = RootDN, + #'BindRequest'{version = ?LDAP_VERSION, name = RootDN, authentication = {simple, Passwd}}}. %%----------------------------------------------------------------------- @@ -762,105 +888,111 @@ gen_req({bind, RootDN, Passwd}) -> %%----------------------------------------------------------------------- recvd_packet(Pkt, S) -> case asn1rt:decode('ELDAPv3', 'LDAPMessage', Pkt) of - {ok,Msg} -> - Op = Msg#'LDAPMessage'.protocolOp, - ?DEBUG("~p",[Op]), - Dict = S#eldap.dict, - Id = Msg#'LDAPMessage'.messageID, - {Timer, From, Name, Result_so_far} = get_op_rec(Id, Dict), - Answer = - case {Name, Op} of - {searchRequest, {searchResEntry, R}} when - is_record(R,'SearchResultEntry') -> - New_dict = dict:append(Id, R, Dict), - {ok, S#eldap{dict = New_dict}}; - {searchRequest, {searchResDone, Result}} -> - Reason = Result#'LDAPResult'.resultCode, - if - Reason==success; Reason=='sizeLimitExceeded' -> - {Res, Ref} = polish(Result_so_far), - New_dict = dict:erase(Id, Dict), - cancel_timer(Timer), - {reply, #eldap_search_result{entries = Res, - referrals = Ref}, From, - S#eldap{dict = New_dict}}; - true -> - New_dict = dict:erase(Id, Dict), - cancel_timer(Timer), - {reply, {error, Reason}, From, S#eldap{dict = New_dict}} - end; - {searchRequest, {searchResRef, R}} -> - New_dict = dict:append(Id, R, Dict), - {ok, S#eldap{dict = New_dict}}; - {addRequest, {addResponse, Result}} -> - New_dict = dict:erase(Id, Dict), - cancel_timer(Timer), - Reply = check_reply(Result, From), - {reply, Reply, From, S#eldap{dict = New_dict}}; - {delRequest, {delResponse, Result}} -> - New_dict = dict:erase(Id, Dict), - cancel_timer(Timer), - Reply = check_reply(Result, From), - {reply, Reply, From, S#eldap{dict = New_dict}}; - {modifyRequest, {modifyResponse, Result}} -> - New_dict = dict:erase(Id, Dict), - cancel_timer(Timer), - Reply = check_reply(Result, From), - {reply, Reply, From, S#eldap{dict = New_dict}}; - {modDNRequest, {modDNResponse, Result}} -> - New_dict = dict:erase(Id, Dict), - cancel_timer(Timer), - Reply = check_reply(Result, From), - {reply, Reply, From, S#eldap{dict = New_dict}}; - {bindRequest, {bindResponse, Result}} -> - New_dict = dict:erase(Id, Dict), - cancel_timer(Timer), - Reply = check_bind_reply(Result, From), - {reply, Reply, From, S#eldap{dict = New_dict}}; - {extendedReq, {extendedResp, Result}} -> - New_dict = dict:erase(Id, Dict), - cancel_timer(Timer), - Reply = check_extended_reply(Result, From), - {reply, Reply, From, S#eldap{dict = New_dict}}; - {OtherName, OtherResult} -> - New_dict = dict:erase(Id, Dict), - cancel_timer(Timer), - {reply, {error, {invalid_result, OtherName, OtherResult}}, - From, S#eldap{dict = New_dict}} - end, - {response, Answer, Name}; - Error -> Error + {ok, Msg} -> + Op = Msg#'LDAPMessage'.protocolOp, + ?DEBUG("~p", [Op]), + Dict = S#eldap.dict, + Id = Msg#'LDAPMessage'.messageID, + {Timer, From, Name, Result_so_far} = get_op_rec(Id, + Dict), + Answer = case {Name, Op} of + {searchRequest, {searchResEntry, R}} + when is_record(R, 'SearchResultEntry') -> + New_dict = dict:append(Id, R, Dict), + {ok, S#eldap{dict = New_dict}}; + {searchRequest, {searchResDone, Result}} -> + Reason = Result#'LDAPResult'.resultCode, + if Reason == success; Reason == sizeLimitExceeded -> + {Res, Ref} = polish(Result_so_far), + New_dict = dict:erase(Id, Dict), + cancel_timer(Timer), + {reply, + #eldap_search_result{entries = Res, + referrals = Ref}, + From, S#eldap{dict = New_dict}}; + true -> + New_dict = dict:erase(Id, Dict), + cancel_timer(Timer), + {reply, {error, Reason}, From, + S#eldap{dict = New_dict}} + end; + {searchRequest, {searchResRef, R}} -> + New_dict = dict:append(Id, R, Dict), + {ok, S#eldap{dict = New_dict}}; + {addRequest, {addResponse, Result}} -> + New_dict = dict:erase(Id, Dict), + cancel_timer(Timer), + Reply = check_reply(Result, From), + {reply, Reply, From, S#eldap{dict = New_dict}}; + {delRequest, {delResponse, Result}} -> + New_dict = dict:erase(Id, Dict), + cancel_timer(Timer), + Reply = check_reply(Result, From), + {reply, Reply, From, S#eldap{dict = New_dict}}; + {modifyRequest, {modifyResponse, Result}} -> + New_dict = dict:erase(Id, Dict), + cancel_timer(Timer), + Reply = check_reply(Result, From), + {reply, Reply, From, S#eldap{dict = New_dict}}; + {modDNRequest, {modDNResponse, Result}} -> + New_dict = dict:erase(Id, Dict), + cancel_timer(Timer), + Reply = check_reply(Result, From), + {reply, Reply, From, S#eldap{dict = New_dict}}; + {bindRequest, {bindResponse, Result}} -> + New_dict = dict:erase(Id, Dict), + cancel_timer(Timer), + Reply = check_bind_reply(Result, From), + {reply, Reply, From, S#eldap{dict = New_dict}}; + {extendedReq, {extendedResp, Result}} -> + New_dict = dict:erase(Id, Dict), + cancel_timer(Timer), + Reply = check_extended_reply(Result, From), + {reply, Reply, From, S#eldap{dict = New_dict}}; + {OtherName, OtherResult} -> + New_dict = dict:erase(Id, Dict), + cancel_timer(Timer), + {reply, + {error, {invalid_result, OtherName, OtherResult}}, + From, S#eldap{dict = New_dict}} + end, + {response, Answer, Name}; + Error -> Error end. -check_reply(#'LDAPResult'{resultCode = success}, _From) -> +check_reply(#'LDAPResult'{resultCode = success}, + _From) -> ok; -check_reply(#'LDAPResult'{resultCode = Reason}, _From) -> +check_reply(#'LDAPResult'{resultCode = Reason}, + _From) -> {error, Reason}; -check_reply(Other, _From) -> - {error, Other}. +check_reply(Other, _From) -> {error, Other}. -check_bind_reply(#'BindResponse'{resultCode = success}, _From) -> +check_bind_reply(#'BindResponse'{resultCode = success}, + _From) -> ok; -check_bind_reply(#'BindResponse'{resultCode = Reason}, _From) -> +check_bind_reply(#'BindResponse'{resultCode = Reason}, + _From) -> {error, Reason}; -check_bind_reply(Other, _From) -> - {error, Other}. +check_bind_reply(Other, _From) -> {error, Other}. %% TODO: process reply depending on requestName: %% this requires BER-decoding of #'ExtendedResponse'.response -check_extended_reply(#'ExtendedResponse'{resultCode = success}, _From) -> +check_extended_reply(#'ExtendedResponse'{resultCode = + success}, + _From) -> ok; -check_extended_reply(#'ExtendedResponse'{resultCode = Reason}, _From) -> +check_extended_reply(#'ExtendedResponse'{resultCode = + Reason}, + _From) -> {error, Reason}; -check_extended_reply(Other, _From) -> - {error, Other}. +check_extended_reply(Other, _From) -> {error, Other}. get_op_rec(Id, Dict) -> case dict:find(Id, Dict) of - {ok, [{Timer, _Command, From, Name}|Res]} -> - {Timer, From, Name, Res}; - error -> - throw({error, unkown_id}) + {ok, [{Timer, _Command, From, Name} | Res]} -> + {Timer, From, Name, Res}; + error -> throw({error, unkown_id}) end. %%----------------------------------------------------------------------- @@ -874,22 +1006,21 @@ get_op_rec(Id, Dict) -> %%----------------------------------------------------------------------- recvd_wait_bind_response(Pkt, S) -> case asn1rt:decode('ELDAPv3', 'LDAPMessage', Pkt) of - {ok,Msg} -> - ?DEBUG("~p", [Msg]), - check_id(S#eldap.id, Msg#'LDAPMessage'.messageID), - case Msg#'LDAPMessage'.protocolOp of - {bindResponse, Result} -> - case Result#'BindResponse'.resultCode of - success -> bound; - Error -> {fail_bind, Error} - end - end; - Else -> - {fail_bind, Else} + {ok, Msg} -> + ?DEBUG("~p", [Msg]), + check_id(S#eldap.id, Msg#'LDAPMessage'.messageID), + case Msg#'LDAPMessage'.protocolOp of + {bindResponse, Result} -> + case Result#'BindResponse'.resultCode of + success -> bound; + Error -> {fail_bind, Error} + end + end; + Else -> {fail_bind, Else} end. check_id(Id, Id) -> ok; -check_id(_, _) -> throw({error, wrong_bind_id}). +check_id(_, _) -> throw({error, wrong_bind_id}). %%----------------------------------------------------------------------- %% General Helpers @@ -897,32 +1028,28 @@ check_id(_, _) -> throw({error, wrong_bind_id}). cancel_timer(Timer) -> erlang:cancel_timer(Timer), - receive - {timeout, Timer, _} -> - ok - after 0 -> - ok - end. + receive {timeout, Timer, _} -> ok after 0 -> ok end. close_and_retry(S, Timeout) -> catch (S#eldap.sockmod):close(S#eldap.fd), - Queue = dict:fold( - fun(_Id, [{Timer, Command, From, _Name}|_], Q) -> - cancel_timer(Timer), - queue:in_r({Command, From}, Q); - (_, _, Q) -> - Q - end, S#eldap.req_q, S#eldap.dict), - erlang:send_after(Timeout, self(), {timeout, retry_connect}), - S#eldap{fd=null, req_q=Queue, dict=dict:new()}. + Queue = dict:fold(fun (_Id, + [{Timer, Command, From, _Name} | _], Q) -> + cancel_timer(Timer), + queue:in_r({Command, From}, Q); + (_, _, Q) -> Q + end, + S#eldap.req_q, S#eldap.dict), + erlang:send_after(Timeout, self(), + {timeout, retry_connect}), + S#eldap{fd = undefined, req_q = Queue, dict = dict:new()}. close_and_retry(S) -> close_and_retry(S, ?RETRY_TIMEOUT). report_bind_failure(Host, Port, Reason) -> ?WARNING_MSG("LDAP bind failed on ~s:~p~nReason: ~p", - [Host, Port, Reason]). + [Host, Port, Reason]). %%----------------------------------------------------------------------- %% Sort out timed out commands @@ -930,21 +1057,21 @@ report_bind_failure(Host, Port, Reason) -> cmd_timeout(Timer, Id, S) -> Dict = S#eldap.dict, case dict:find(Id, Dict) of - {ok, [{Timer, _Command, From, Name}|Res]} -> - case Name of - searchRequest -> - {Res1, Ref1} = polish(Res), - New_dict = dict:erase(Id, Dict), - {reply, From, {timeout, - #eldap_search_result{entries = Res1, - referrals = Ref1}}, - S#eldap{dict = New_dict}}; - _ -> - New_dict = dict:erase(Id, Dict), - {reply, From, {error, timeout}, S#eldap{dict = New_dict}} - end; - error -> - {error, timed_out_cmd_not_in_dict} + {ok, [{Timer, _Command, From, Name} | Res]} -> + case Name of + searchRequest -> + {Res1, Ref1} = polish(Res), + New_dict = dict:erase(Id, Dict), + {reply, From, + {timeout, + #eldap_search_result{entries = Res1, referrals = Ref1}}, + S#eldap{dict = New_dict}}; + _ -> + New_dict = dict:erase(Id, Dict), + {reply, From, {error, timeout}, + S#eldap{dict = New_dict}} + end; + error -> {error, timed_out_cmd_not_in_dict} end. %%----------------------------------------------------------------------- @@ -954,191 +1081,97 @@ cmd_timeout(Timer, Id, S) -> %%% Polish the returned search result %%% -polish(Entries) -> - polish(Entries, [], []). +polish(Entries) -> polish(Entries, [], []). -polish([H|T], Res, Ref) when is_record(H, 'SearchResultEntry') -> +polish([H | T], Res, Ref) + when is_record(H, 'SearchResultEntry') -> ObjectName = H#'SearchResultEntry'.objectName, - F = fun({_,A,V}) -> {A,V} end, + F = fun ({_, A, V}) -> {A, V} end, Attrs = lists:map(F, H#'SearchResultEntry'.attributes), - polish(T, [#eldap_entry{object_name = ObjectName, - attributes = Attrs}|Res], Ref); -polish([H|T], Res, Ref) -> % No special treatment of referrals at the moment. - polish(T, Res, [H|Ref]); -polish([], Res, Ref) -> - {Res, Ref}. + polish(T, + [#eldap_entry{object_name = ObjectName, + attributes = Attrs} + | Res], + Ref); +polish([H | T], Res, + Ref) -> % No special treatment of referrals at the moment. + polish(T, Res, [H | Ref]); +polish([], Res, Ref) -> {Res, Ref}. %%----------------------------------------------------------------------- %% Connect to next server in list and attempt to bind to it. %%----------------------------------------------------------------------- connect_bind(S) -> Host = next_host(S#eldap.host, S#eldap.hosts), - ?INFO_MSG("LDAP connection on ~s:~p", [Host, S#eldap.port]), + ?INFO_MSG("LDAP connection on ~s:~p", + [Host, S#eldap.port]), Opts = if S#eldap.tls == tls -> - [{packet, asn1}, {active, true}, {keepalive, true}, - binary | S#eldap.tls_options]; - true -> - [{packet, asn1}, {active, true}, {keepalive, true}, - {send_timeout, ?SEND_TIMEOUT}, binary] - end, + [{packet, asn1}, {active, true}, {keepalive, true}, + binary + | S#eldap.tls_options]; + true -> + [{packet, asn1}, {active, true}, {keepalive, true}, + {send_timeout, ?SEND_TIMEOUT}, binary] + end, + HostS = binary_to_list(Host), SocketData = case S#eldap.tls of - tls -> - SockMod = ssl, - ssl:connect(Host, S#eldap.port, Opts); - %% starttls -> %% TODO: Implement STARTTLS; - _ -> - SockMod = gen_tcp, - gen_tcp:connect(Host, S#eldap.port, Opts) + tls -> + SockMod = ssl, ssl:connect(HostS, S#eldap.port, Opts); + %% starttls -> %% TODO: Implement STARTTLS; + _ -> + SockMod = gen_tcp, + gen_tcp:connect(HostS, S#eldap.port, Opts) end, case SocketData of - {ok, Socket} -> - case bind_request(Socket, S#eldap{sockmod = SockMod}) of - {ok, NewS} -> - Timer = erlang:start_timer(?BIND_TIMEOUT, self(), - {timeout, bind_timeout}), - {ok, wait_bind_response, NewS#eldap{fd = Socket, - sockmod = SockMod, - host = Host, - bind_timer = Timer}}; - {error, Reason} -> - report_bind_failure(Host, S#eldap.port, Reason), - NewS = close_and_retry(S), - {ok, connecting, NewS#eldap{host = Host}} - end; - {error, Reason} -> - ?ERROR_MSG("LDAP connection failed:~n" - "** Server: ~s:~p~n" - "** Reason: ~p~n" - "** Socket options: ~p", - [Host, S#eldap.port, Reason, Opts]), - NewS = close_and_retry(S), - {ok, connecting, NewS#eldap{host = Host}} + {ok, Socket} -> + case bind_request(Socket, S#eldap{sockmod = SockMod}) of + {ok, NewS} -> + Timer = erlang:start_timer(?BIND_TIMEOUT, self(), + {timeout, bind_timeout}), + {ok, wait_bind_response, + NewS#eldap{fd = Socket, sockmod = SockMod, host = Host, + bind_timer = Timer}}; + {error, Reason} -> + report_bind_failure(Host, S#eldap.port, Reason), + NewS = close_and_retry(S), + {ok, connecting, NewS#eldap{host = Host}} + end; + {error, Reason} -> + ?ERROR_MSG("LDAP connection failed:~n** Server: " + "~s:~p~n** Reason: ~p~n** Socket options: ~p", + [Host, S#eldap.port, Reason, Opts]), + NewS = close_and_retry(S), + {ok, connecting, NewS#eldap{host = Host}} end. bind_request(Socket, S) -> Id = bump_id(S), - Req = #'BindRequest'{version = S#eldap.version, - name = S#eldap.rootdn, + Req = #'BindRequest'{version = S#eldap.version, + name = S#eldap.rootdn, authentication = {simple, S#eldap.passwd}}, - Message = #'LDAPMessage'{messageID = Id, + Message = #'LDAPMessage'{messageID = Id, protocolOp = {bindRequest, Req}}, - ?DEBUG("Bind Request Message:~p~n",[Message]), - {ok, Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message), + ?DEBUG("Bind Request Message:~p~n", [Message]), + {ok, Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', + Message), case (S#eldap.sockmod):send(Socket, Bytes) of - ok -> {ok, S#eldap{id = Id}}; - Error -> Error + ok -> {ok, S#eldap{id = Id}}; + Error -> Error end. %% Given last tried Server, find next one to try -next_host(null, [H|_]) -> H; % First time, take first -next_host(Host, Hosts) -> % Find next in turn +next_host(undefined, [H | _]) -> + H; % First time, take first +next_host(Host, + Hosts) -> % Find next in turn next_host(Host, Hosts, Hosts). -next_host(Host, [Host], Hosts) -> hd(Hosts); % Wrap back to first -next_host(Host, [Host|Tail], _Hosts) -> hd(Tail); % Take next -next_host(_Host, [], Hosts) -> hd(Hosts); % Never connected before? (shouldn't happen) -next_host(Host, [_|T], Hosts) -> next_host(Host, T, Hosts). - - %%% -------------------------------------------------------------------- %%% Verify the input data %%% -------------------------------------------------------------------- - -v_filter({'and',L}) -> {'and',L}; -v_filter({'or', L}) -> {'or',L}; -v_filter({'not',L}) -> {'not',L}; -v_filter({equalityMatch,AV}) -> {equalityMatch,AV}; -v_filter({greaterOrEqual,AV}) -> {greaterOrEqual,AV}; -v_filter({lessOrEqual,AV}) -> {lessOrEqual,AV}; -v_filter({approxMatch,AV}) -> {approxMatch,AV}; -v_filter({present,A}) -> {present,A}; -v_filter({substrings,S}) when is_record(S,'SubstringFilter') -> {substrings,S}; -v_filter({extensibleMatch, S}) when is_record(S, 'MatchingRuleAssertion') -> - {extensibleMatch, S}; -v_filter(_Filter) -> throw({error,concat(["unknown filter: ",_Filter])}). - -v_modifications(Mods) -> - F = fun({_,Op,_}) -> - case lists:member(Op,[add,delete,replace]) of - true -> true; - _ -> throw({error,{mod_operation,Op}}) - end - end, - lists:foreach(F, Mods). - -v_substr([{Key,Str}|T]) when is_list(Str),Key==initial;Key==any;Key==final -> - [{Key,Str}|v_substr(T)]; -v_substr([H|_]) -> - throw({error,{substring_arg,H}}); -v_substr([]) -> - []. -v_scope(baseObject) -> baseObject; -v_scope(singleLevel) -> singleLevel; -v_scope(wholeSubtree) -> wholeSubtree; -v_scope(_Scope) -> throw({error,concat(["unknown scope: ",_Scope])}). - -v_bool(true) -> true; -v_bool(false) -> false; -v_bool(_Bool) -> throw({error,concat(["not Boolean: ",_Bool])}). - -v_timeout(I) when is_integer(I), I>=0 -> I; -v_timeout(_I) -> throw({error,concat(["timeout not positive integer: ",_I])}). - -v_attributes(Attrs) -> - F = fun(A) when is_list(A) -> A; - (A) -> throw({error,concat(["attribute not String: ",A])}) - end, - lists:map(F,Attrs). - - %%% -------------------------------------------------------------------- %%% Get and Validate the initial configuration %%% -------------------------------------------------------------------- -get_config() -> - Priv_dir = code:priv_dir(eldap), - File = filename:join(Priv_dir, "eldap.conf"), - case file:consult(File) of - {ok, Entries} -> - case catch parse(Entries) of - {ok, Hosts, Port, Rootdn, Passwd, Opts} -> - {ok, Hosts, Port, Rootdn, Passwd, Opts}; - {error, Reason} -> - {error, Reason}; - {'EXIT', Reason} -> - {error, Reason} - end; - {error, Reason} -> - {error, Reason} - end. - -parse(Entries) -> - {ok, - get_hosts(host, Entries), - get_integer(port, Entries), - get_list(rootdn, Entries), - get_list(passwd, Entries), - get_list(options, Entries)}. - -get_integer(Key, List) -> - case lists:keysearch(Key, 1, List) of - {value, {Key, Value}} when is_integer(Value) -> - Value; - {value, {Key, _Value}} -> - throw({error, "Bad Value in Config for " ++ atom_to_list(Key)}); - false -> - throw({error, "No Entry in Config for " ++ atom_to_list(Key)}) - end. - -get_list(Key, List) -> - case lists:keysearch(Key, 1, List) of - {value, {Key, Value}} when is_list(Value) -> - Value; - {value, {Key, _Value}} -> - throw({error, "Bad Value in Config for " ++ atom_to_list(Key)}); - false -> - throw({error, "No Entry in Config for " ++ atom_to_list(Key)}) - end. - %% get_atom(Key, List) -> %% case lists:keysearch(Key, 1, List) of %% {value, {Key, Value}} when is_atom(Value) -> @@ -1148,25 +1181,19 @@ get_list(Key, List) -> %% false -> %% throw({error, "No Entry in Config for " ++ atom_to_list(Key)}) %% end. - -get_hosts(Key, List) -> - lists:map(fun({Key1, {A,B,C,D}}) when is_integer(A), - is_integer(B), - is_integer(C), - is_integer(D), - Key == Key1-> - {A,B,C,D}; - ({Key1, Value}) when is_list(Value), - Key == Key1-> - Value; - ({_Else, _Value}) -> - throw({error, "Bad Hostname in config"}) - end, List). - %%% -------------------------------------------------------------------- %%% Other Stuff %%% -------------------------------------------------------------------- -bump_id(#eldap{id = Id}) when Id > ?MAX_TRANSACTION_ID -> +next_host(Host, [Host], Hosts) -> + hd(Hosts); % Wrap back to first +next_host(Host, [Host | Tail], _Hosts) -> + hd(Tail); % Take next +next_host(_Host, [], Hosts) -> + hd(Hosts); % Never connected before? (shouldn't happen) +next_host(Host, [_ | T], Hosts) -> + next_host(Host, T, Hosts). + +bump_id(#eldap{id = Id}) + when Id > (?MAX_TRANSACTION_ID) -> ?MIN_TRANSACTION_ID; -bump_id(#eldap{id = Id}) -> - Id + 1. +bump_id(#eldap{id = Id}) -> Id + 1. diff --git a/src/eldap/eldap.hrl b/src/eldap/eldap.hrl index 90d794fb8..30ec0e954 100644 --- a/src/eldap/eldap.hrl +++ b/src/eldap/eldap.hrl @@ -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{}. diff --git a/src/eldap/eldap_filter.erl b/src/eldap/eldap_filter.erl index 11a37fe20..6771fc2af 100644 --- a/src/eldap/eldap_filter.erl +++ b/src/eldap/eldap_filter.erl @@ -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, <>, 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) -> - lists:flatmap( - fun($&) -> "\\&"; - ($\\) -> "\\\\"; - (Chr) -> [Chr] - end, String). +replace_amps(Bin) -> + list_to_binary( + lists:flatmap( + fun($&) -> "\\&"; + ($\\) -> "\\\\"; + (Chr) -> [Chr] + end, binary_to_list(Bin))). diff --git a/src/eldap/eldap_filter_yecc.yrl b/src/eldap/eldap_filter_yecc.yrl index a8f7970bf..a70ea3e74 100644 --- a/src/eldap/eldap_filter_yecc.yrl +++ b/src/eldap/eldap_filter_yecc.yrl @@ -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). diff --git a/src/eldap/eldap_pool.erl b/src/eldap/eldap_pool.erl index d256ca0a9..1f52999ef 100644 --- a/src/eldap/eldap_pool.erl +++ b/src/eldap/eldap_pool.erl @@ -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,40 +46,41 @@ 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); - _ -> - error - end - end, Hosts). + 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). %%==================================================================== %% Internal functions %%==================================================================== do_request(Name, {F, Args}) -> - case ?PG2:get_closest_pid(make_id(Name)) of - Pid when is_pid(Pid) -> - case catch apply(eldap, F, [Pid | Args]) of - {'EXIT', {timeout, _}} -> - ?ERROR_MSG("LDAP request failed: timed out", []); - {'EXIT', Reason} -> - ?ERROR_MSG("LDAP request failed: eldap:~p(~p)~nReason: ~p", - [F, Args, Reason]), - {error, Reason}; - Reply -> - Reply - end; - Err -> - Err + case pg2:get_closest_pid(make_id(Name)) of + Pid when is_pid(Pid) -> + case catch apply(eldap, F, [Pid | Args]) of + {'EXIT', {timeout, _}} -> + ?ERROR_MSG("LDAP request failed: timed out", []); + {'EXIT', Reason} -> + ?ERROR_MSG("LDAP request failed: eldap:~p(~p)~nReason: ~p", + [F, Args, Reason]), + {error, Reason}; + Reply -> Reply + end; + Err -> Err end. make_id(Name) -> - list_to_atom("eldap_pool_" ++ Name). + jlib:binary_to_atom(<<"eldap_pool_", Name/binary>>). diff --git a/src/eldap/eldap_utils.erl b/src/eldap/eldap_utils.erl index 6c1c43e90..2e149d8b6 100644 --- a/src/eldap/eldap_utils.erl +++ b/src/eldap/eldap_utils.erl @@ -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,<>}|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,<>}|Rest],Acc,Uacc) -> + collect_parts_bit(Rest,[Bits|Acc],Unused+Uacc); +collect_parts_bit([],Acc,Uacc) -> + list_to_binary([Uacc|lists:reverse(Acc)]). diff --git a/src/expat_erl.c b/src/expat_erl.c index b2462243c..02d41ed33 100644 --- a/src/expat_erl.c +++ b/src/expat_erl.c @@ -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); diff --git a/src/extauth.erl b/src/extauth.erl index 32398f7e5..6a7f8d7b9 100644 --- a/src/extauth.erl +++ b/src/extauth.erl @@ -25,30 +25,24 @@ %%%---------------------------------------------------------------------- -module(extauth). + -author('leifj@it.su.se'). --export([start/2, - stop/1, - init/2, - check_password/3, - set_password/3, - try_register/3, - remove_user/2, - remove_user/3, - is_user_exists/2]). +-export([start/2, stop/1, init/2, check_password/3, + set_password/3, try_register/3, remove_user/2, + remove_user/3, is_user_exists/2]). -include("ejabberd.hrl"). --define(INIT_TIMEOUT, 60000). % Timeout is in milliseconds: 60 seconds == 60000 --define(CALL_TIMEOUT, 10000). % Timeout is in milliseconds: 10 seconds == 10000 +-define(INIT_TIMEOUT, 60000). + +-define(CALL_TIMEOUT, 10000). start(Host, ExtPrg) -> - lists:foreach( - fun(This) -> - start_instance(get_process_name(Host, This), ExtPrg) - end, - lists:seq(0, get_instances(Host)-1) - ). + lists:foreach(fun (This) -> + start_instance(get_process_name(Host, This), ExtPrg) + end, + lists:seq(0, get_instances(Host) - 1)). start_instance(ProcessName, ExtPrg) -> spawn(?MODULE, init, [ProcessName, ExtPrg]). @@ -59,20 +53,20 @@ restart_instance(ProcessName, ExtPrg) -> init(ProcessName, ExtPrg) -> register(ProcessName, self()), - process_flag(trap_exit,true), - Port = open_port({spawn, ExtPrg}, [{packet,2}]), + process_flag(trap_exit, true), + Port = open_port({spawn, ExtPrg}, [{packet, 2}]), loop(Port, ?INIT_TIMEOUT, ProcessName, ExtPrg). stop(Host) -> - lists:foreach( - fun(This) -> - get_process_name(Host, This) ! stop - end, - lists:seq(0, get_instances(Host)-1) - ). + lists:foreach(fun (This) -> + get_process_name(Host, This) ! stop + end, + 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,90 +78,88 @@ 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 - true -> {atomic, ok}; - false -> {error, not_allowed} + case call_port(Server, + ["tryregister", User, Server, Password]) + of + true -> {atomic, ok}; + false -> {error, not_allowed} end. remove_user(User, Server) -> call_port(Server, ["removeuser", User, Server]). remove_user(User, Server, Password) -> - call_port(Server, ["removeuser3", User, Server, Password]). + call_port(Server, + ["removeuser3", User, Server, Password]). call_port(Server, Msg) -> LServer = jlib:nameprep(Server), - ProcessName = get_process_name(LServer, random_instance(get_instances(LServer))), + ProcessName = get_process_name(LServer, + random_instance(get_instances(LServer))), ProcessName ! {call, self(), Msg}, - receive - {eauth,Result} -> - Result - end. + receive {eauth, Result} -> Result end. random_instance(MaxNum) -> - {A1,A2,A3} = now(), + {A1, A2, A3} = now(), random:seed(A1, A2, A3), random:uniform(MaxNum) - 1. get_instances(Server) -> - case ejabberd_config:get_local_option({extauth_instances, Server}) of - Num when is_integer(Num) -> Num; - _ -> 1 - end. + ejabberd_config:get_local_option( + {extauth_instances, Server}, + fun(V) when is_integer(V), V > 0 -> + V + end, 1). loop(Port, Timeout, ProcessName, ExtPrg) -> receive - {call, Caller, Msg} -> - port_command(Port, encode(Msg)), - receive - {Port, {data, 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]), - Caller ! {eauth, false}, - loop(Port, ?CALL_TIMEOUT, ProcessName, ExtPrg) - 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), - exit(port_terminated) - end; - stop -> - Port ! {self(), close}, - receive - {Port, closed} -> - exit(normal) - end; - {'EXIT', Port, 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) + {call, Caller, Msg} -> + port_command(Port, encode(Msg)), + receive + {Port, {data, 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]), + Caller ! {eauth, false}, + loop(Port, ?CALL_TIMEOUT, ProcessName, ExtPrg) + 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), + exit(port_terminated) + end; + stop -> + Port ! {self(), close}, + receive {Port, closed} -> exit(normal) end; + {'EXIT', Port, Reason} -> + ?CRITICAL_MSG("extauth script has exitted abruptly " + "with reason '~p'", + [Reason]), + Pid = restart_instance(ProcessName, ExtPrg), + flush_buffer_and_forward_messages(Pid), + exit(port_terminated) end. flush_buffer_and_forward_messages(Pid) -> receive - Message -> - Pid ! Message, - flush_buffer_and_forward_messages(Pid) - after 0 -> - true + Message -> + 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). + lists:foldl(fun (A, "") -> A; + (A, Acc) -> Acc ++ Sep ++ A + 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. diff --git a/src/gen_iq_handler.erl b/src/gen_iq_handler.erl index e04f53afe..20efee7bc 100644 --- a/src/gen_iq_handler.erl +++ b/src/gen_iq_handler.erl @@ -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); - one_queue -> - {ok, Pid} = supervisor:start_child(ejabberd_iq_sup, - [Host, Module, Function]), - 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]), - Pid - 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) + 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}); + {queues, N} -> + 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}); + parallel -> + Component:register_iq_handler(Host, NS, Module, + Function, parallel) end. remove_iq_handler(Component, Host, NS) -> @@ -86,44 +86,38 @@ remove_iq_handler(Component, Host, NS) -> stop_iq_handler(_Module, _Function, Opts) -> case Opts of - {one_queue, Pid} -> - gen_server:call(Pid, stop); - {queues, Pids} -> - lists:foreach(fun(Pid) -> - catch gen_server:call(Pid, stop) - end, Pids); - _ -> - ok + {one_queue, Pid} -> gen_server:call(Pid, stop); + {queues, Pids} -> + lists:foreach(fun (Pid) -> + catch gen_server:call(Pid, stop) + 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}; - {queues, Pids} -> - Pid = lists:nth(erlang:phash(now(), length(Pids)), Pids), - Pid ! {process_iq, From, To, IQ}; - parallel -> - spawn(?MODULE, process_iq, [Host, Module, Function, From, To, IQ]); - _ -> - todo + no_queue -> + process_iq(Host, Module, Function, From, To, IQ); + {one_queue, Pid} -> Pid ! {process_iq, From, To, IQ}; + {queues, 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 end. - process_iq(_Host, Module, Function, From, To, IQ) -> case catch Module:Function(From, To, IQ) of - {'EXIT', Reason} -> - ?ERROR_MSG("~p", [Reason]); - ResIQ -> - if - ResIQ /= ignore -> - ejabberd_router:route(To, From, - jlib:iq_to_xml(ResIQ)); - true -> - ok - end + {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); + ResIQ -> + if ResIQ /= ignore -> + ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)); + true -> ok + end end. %%==================================================================== @@ -138,9 +132,9 @@ process_iq(_Host, Module, Function, From, To, IQ) -> %% Description: Initiates the server %%-------------------------------------------------------------------- init([Host, Module, Function]) -> - {ok, #state{host = Host, - module = Module, - function = Function}}. + {ok, + #state{host = Host, module = Module, + function = Function}}. %%-------------------------------------------------------------------- %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | @@ -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 %%-------------------------------------------------------------------- + diff --git a/src/gen_mod.erl b/src/gen_mod.erl index c9fdceb15..f824ff740 100644 --- a/src/gen_mod.erl +++ b/src/gen_mod.erl @@ -2,6 +2,7 @@ %%% File : gen_mod.erl %%% Author : Alexey Shchepin %%% Purpose : +%%% Purpose : %%% Created : 24 Jan 2003 by Alexey Shchepin %%% %%% @@ -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, - {keypos, #ejabberd_module.module_host}]), + 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 -> - 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]), - ?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.", []), - timer:sleep(3000), - erlang:halt(string:substr(lists:flatten(ErrorText), 1, 199)) - end + 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~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.", + []), + 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,22 +108,20 @@ 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; - {wait, ProcList} when is_list(ProcList) -> - lists:foreach(fun wait_for_process/1, ProcList), - ets:delete(ejabberd_modules, {Module, Host}), - ok; - {wait, Process} -> - wait_for_process(Process), - ets:delete(ejabberd_modules, {Module, Host}), - ok; - _ -> - ets:delete(ejabberd_modules, {Module, Host}), - ok + {'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}), + ok; + {wait, Process} -> + wait_for_process(Process), + ets:delete(ejabberd_modules, {Module, Host}), + ok; + _ -> ets:delete(ejabberd_modules, {Module, Host}), ok end. wait_for_process(Process) -> @@ -128,136 +130,153 @@ wait_for_process(Process) -> wait_for_stop(Process, MonitorReference) -> receive - {'DOWN', MonitorReference, _Type, _Object, _Info} -> - ok - after 5000 -> - catch exit(whereis(Process), kill), - wait_for_stop1(MonitorReference) + {'DOWN', MonitorReference, _Type, _Object, _Info} -> ok + after 5000 -> + catch exit(whereis(Process), kill), + wait_for_stop1(MonitorReference) end. 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) -> +-type check_fun() :: fun((any()) -> any()) | {module(), atom()}. + +-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 -> - % TODO: replace with more appropriate function - throw({undefined_option, Opt}); - {value, {_, Val}} -> - Val + false -> + Default; + {value, {_, Val}} -> + ejabberd_config:prepare_opt_val(Opt, Val, F, Default) end. -get_opt(Opt, Opts, Default) -> - case lists:keysearch(Opt, 1, Opts) of - false -> - Default; - {value, {_, Val}} -> - Val - end. +-spec get_module_opt(global | binary(), atom(), atom(), check_fun(), any()) -> any(). -get_module_opt(global, Module, Opt, Default) -> - Hosts = ?MYHOSTS, - [Value | Values] = lists:map( - fun(Host) -> - get_module_opt(Host, Module, Opt, Default) - end, - Hosts), - Same_all = lists:all( - fun(Other_value) -> - Other_value == Value - end, - Values), - case Same_all of - true -> Value; - false -> Default - end; - -get_module_opt(Host, Module, Opt, Default) -> +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) -> + Other_value == Value + end, + Values), + case Same_all of + true -> Value; + false -> Default + end; +get_module_opt(Host, Module, Opt, F, Default) -> OptsList = ets:lookup(ejabberd_modules, {Module, Host}), case OptsList of - [] -> - Default; - [#ejabberd_module{opts = Opts} | _] -> - get_opt(Opt, Opts, Default) + [] -> Default; + [#ejabberd_module{opts = Opts} | _] -> + 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]; - Host -> - [Host] - end; - Hosts -> - Hosts + 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 -> + [<> || Host <- ?MYHOSTS]; + Host -> + [Host] + end; + Hosts -> + 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}). - diff --git a/src/idna.erl b/src/idna.erl index a73724812..ee4c8cb4a 100644 --- a/src/idna.erl +++ b/src/idna.erl @@ -25,172 +25,182 @@ %%%---------------------------------------------------------------------- -module(idna). + -author('alexey@process-one.net'). %%-compile(export_all). -export([domain_utf8_to_ascii/1, - domain_ucs2_to_ascii/1]). + domain_ucs2_to_ascii/1, + utf8_to_ucs2/1]). +-spec domain_utf8_to_ascii(binary()) -> false | binary(). domain_utf8_to_ascii(Domain) -> domain_ucs2_to_ascii(utf8_to_ucs2(Domain)). utf8_to_ucs2(S) -> - utf8_to_ucs2(S, ""). + list_to_binary(utf8_to_ucs2(binary_to_list(S), "")). -utf8_to_ucs2([], R) -> - lists:reverse(R); -utf8_to_ucs2([C | S], R) when C < 16#80 -> +utf8_to_ucs2([], R) -> lists:reverse(R); +utf8_to_ucs2([C | S], R) when C < 128 -> utf8_to_ucs2(S, [C | R]); -utf8_to_ucs2([C1, C2 | S], R) when C1 < 16#E0 -> - utf8_to_ucs2(S, [((C1 band 16#1F) bsl 6) bor - (C2 band 16#3F) | R]); -utf8_to_ucs2([C1, C2, C3 | S], R) when C1 < 16#F0 -> - utf8_to_ucs2(S, [((C1 band 16#0F) bsl 12) bor - ((C2 band 16#3F) bsl 6) bor - (C3 band 16#3F) | R]). +utf8_to_ucs2([C1, C2 | S], R) when C1 < 224 -> + utf8_to_ucs2(S, [C1 band 31 bsl 6 bor C2 band 63 | R]); +utf8_to_ucs2([C1, C2, C3 | S], R) when C1 < 240 -> + utf8_to_ucs2(S, + [C1 band 15 bsl 12 bor (C2 band 63 bsl 6) bor C3 band 63 + | R]). +-spec domain_ucs2_to_ascii(binary()) -> false | binary(). domain_ucs2_to_ascii(Domain) -> - case catch domain_ucs2_to_ascii1(Domain) of - {'EXIT', _Reason} -> - false; - Res -> - Res + case catch domain_ucs2_to_ascii1(binary_to_list(Domain)) of + {'EXIT', _Reason} -> false; + Res -> iolist_to_binary(Res) end. domain_ucs2_to_ascii1(Domain) -> - Parts = string:tokens(Domain, [16#002E, 16#3002, 16#FF0E, 16#FF61]), - ASCIIParts = lists:map(fun(P) -> - to_ascii(P) - end, Parts), - string:strip(lists:flatmap(fun(P) -> [$. | P] end, ASCIIParts), + Parts = string:tokens(Domain, + [46, 12290, 65294, 65377]), + ASCIIParts = lists:map(fun (P) -> to_ascii(P) end, + Parts), + string:strip(lists:flatmap(fun (P) -> [$. | P] end, + ASCIIParts), left, $.). %% Domain names are already nameprep'ed in ejabberd, so we skiping this step to_ascii(Name) -> - false = lists:any( - fun(C) when - ( 0 =< C) and (C =< 16#2C) or - (16#2E =< C) and (C =< 16#2F) or - (16#3A =< C) and (C =< 16#40) or - (16#5B =< C) and (C =< 16#60) or - (16#7B =< C) and (C =< 16#7F) -> - true; - (_) -> - false - end, Name), + 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) /= $- + [H | _] when H /= $- -> true = lists:last(Name) /= $- end, - ASCIIName = case lists:any(fun(C) -> C > 16#7F end, Name) of - true -> - true = case Name of - "xn--" ++ _ -> false; - _ -> true - end, - "xn--" ++ punycode_encode(Name); - false -> - Name + 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 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(BASE, 36). + +-define(TMIN, 1). + +-define(TMAX, 26). + +-define(SKEW, 38). + +-define(DAMP, 700). + -define(INITIAL_BIAS, 72). --define(INITIAL_N, 128). + +-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 ++ "-"; - true -> "" + 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) - when H < L -> +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}; - C == M -> - NewOut = punycode_encode_delta(ADelta, ABias, AOut), - NewBias = adapt(ADelta, H + 1, H == B), - {0, NewBias, AH + 1, NewOut}; - true -> - {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) -> + % let n = m + {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}; + true -> + {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) -> 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; - true -> K - Bias + T = if K =< Bias -> ?TMIN; + K >= Bias + (?TMAX) -> ?TMAX; + true -> K - Bias end, - 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) + 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)) end. - adapt(Delta, NumPoints, FirstTime) -> - Delta1 = if - FirstTime -> Delta div ?DAMP; - true -> Delta div 2 + 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); - true -> - K + (((?BASE - ?TMIN + 1) * Delta) div (Delta + ?SKEW)) + if Delta > ((?BASE) - (?TMIN)) * (?TMAX) div 2 -> + adapt1(Delta div ((?BASE) - (?TMIN)), K + (?BASE)); + true -> + 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. diff --git a/src/jd2ejd.erl b/src/jd2ejd.erl index 233c1b034..a5d2f9961 100644 --- a/src/jd2ejd.erl +++ b/src/jd2ejd.erl @@ -25,17 +25,16 @@ %%%---------------------------------------------------------------------- -module(jd2ejd). + -author('alexey@process-one.net'). %% External exports --export([import_file/1, - import_dir/1]). +-export([import_file/1, import_dir/1]). -include("ejabberd.hrl"). + -include("jlib.hrl"). - - %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- @@ -43,140 +42,135 @@ 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 - true -> - case file:read_file(File) of - {ok, Text} -> - case xml_stream:parse_element(Text) of - El when element(1, El) == xmlelement -> - case catch process_xdb(User, Server, El) of - {'EXIT', Reason} -> - ?ERROR_MSG( - "Error while processing file \"~s\": ~p~n", + 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 is_record(El, xmlel) -> + case catch process_xdb(User, Server, El) of + {'EXIT', Reason} -> + ?ERROR_MSG("Error while processing file \"~s\": " + "~p~n", [File, Reason]), - {error, Reason}; - _ -> - ok - end; - {error, Reason} -> - ?ERROR_MSG("Can't parse file \"~s\": ~p~n", - [File, Reason]), - {error, Reason} - end; - {error, 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, Reason}; + _ -> ok + end; + {error, Reason} -> + ?ERROR_MSG("Can't parse file \"~s\": ~p~n", + [File, Reason]), + {error, Reason} + end; + {error, 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">>} end. - import_dir(Dir) -> {ok, Files} = file:list_dir(Dir), - MsgFiles = lists:filter( - fun(FN) -> - case string:len(FN) > 4 of - true -> - string:substr(FN, - string:len(FN) - 3) == ".xml"; - _ -> - false - end - 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"}; - _ -> A - end - end, ok, MsgFiles). + MsgFiles = lists:filter(fun (FN) -> + case length(FN) > 4 of + true -> + string:substr(FN, length(FN) - 3) == + ".xml"; + _ -> false + end + 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">>}; + _ -> A + end + 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 - ?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; - ?NS_LAST -> - 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), - 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), - 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))]}}); - _ -> - ?DEBUG("jd2ejd: Unknown namespace \"~s\"~n", [XMLNS]) - end, - ok +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; + ?NS_LAST -> + TimeStamp = xml:get_attr_s(<<"last">>, Attrs), + Status = xml:get_tag_cdata(El), + 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), 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 = + #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; - _ -> - ejabberd_hooks:run(offline_message_hook, - LServer, - [From, To, El]) + error -> ok; + _ -> + ejabberd_hooks:run(offline_message_hook, + LServer, [From, To, El]) end - end, Els). - + end, + Els). diff --git a/src/jlib.erl b/src/jlib.erl index ce99f95eb..bf08a476d 100644 --- a/src/jlib.erl +++ b/src/jlib.erl @@ -25,238 +25,248 @@ %%%---------------------------------------------------------------------- -module(jlib). + -author('alexey@process-one.net'). --export([make_result_iq_reply/1, - make_error_reply/3, - make_error_reply/2, - make_error_element/2, - make_correct_from_to_attrs/3, - replace_from_to_attrs/3, - replace_from_to/3, - replace_from_attrs/2, - replace_from/2, - remove_attr/2, - make_jid/3, - make_jid/1, - string_to_jid/1, - jid_to_string/1, - is_nodename/1, - tolower/1, - nodeprep/1, - nameprep/1, - resourceprep/1, - jid_tolower/1, - jid_remove_resource/1, - jid_replace_resource/2, - get_iq_namespace/1, - iq_query_info/1, - iq_query_or_response_info/1, - is_iq_request_type/1, - iq_to_xml/1, - parse_xdata_submit/1, - timestamp_to_iso/1, % TODO: Remove once XEP-0091 is Obsolete - timestamp_to_iso/2, - timestamp_to_xml/4, - timestamp_to_xml/1, % TODO: Remove once XEP-0091 is Obsolete - now_to_utc_string/1, - now_to_local_string/1, - datetime_string_to_timestamp/1, - decode_base64/1, - encode_base64/1, - ip_to_list/1, - rsm_encode/1, - rsm_encode/2, - rsm_decode/1]). +-compile({no_auto_import, [{atom_to_binary, 2}]}). + +-export([make_result_iq_reply/1, make_error_reply/3, + make_error_reply/2, make_error_element/2, + make_correct_from_to_attrs/3, replace_from_to_attrs/3, + replace_from_to/3, replace_from_attrs/2, replace_from/2, + remove_attr/2, make_jid/3, make_jid/1, string_to_jid/1, + jid_to_string/1, is_nodename/1, tolower/1, nodeprep/1, + nameprep/1, resourceprep/1, jid_tolower/1, + jid_remove_resource/1, jid_replace_resource/2, + get_iq_namespace/1, iq_query_info/1, + iq_query_or_response_info/1, is_iq_request_type/1, + iq_to_xml/1, parse_xdata_submit/1, timestamp_to_iso/1, + timestamp_to_iso/2, timestamp_to_xml/4, + timestamp_to_xml/1, now_to_utc_string/1, + now_to_local_string/1, datetime_string_to_timestamp/1, + decode_base64/1, encode_base64/1, ip_to_list/1, + rsm_encode/1, rsm_encode/2, rsm_decode/1, + binary_to_integer/1, binary_to_integer/2, + integer_to_binary/1, integer_to_binary/2, + atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1]). + +%% TODO: Remove once XEP-0091 is Obsolete +%% TODO: Remove once XEP-0091 is Obsolete -include("jlib.hrl"). +-export_type([jid/0]). + %send_iq(From, To, ID, SubTags) -> % ok. -make_result_iq_reply({xmlelement, Name, Attrs, SubTags}) -> +-spec make_result_iq_reply(xmlel()) -> xmlel(). + +make_result_iq_reply(#xmlel{name = Name, attrs = Attrs, + children = SubTags}) -> NewAttrs = make_result_iq_reply_attrs(Attrs), - {xmlelement, Name, NewAttrs, SubTags}. + #xmlel{name = Name, attrs = NewAttrs, + children = SubTags}. + +-spec make_result_iq_reply_attrs([attr()]) -> [attr()]. make_result_iq_reply_attrs(Attrs) -> - To = xml:get_attr("to", Attrs), - From = xml:get_attr("from", Attrs), - Attrs1 = lists:keydelete("to", 1, Attrs), - Attrs2 = lists:keydelete("from", 1, Attrs1), + To = xml:get_attr(<<"to">>, Attrs), + From = xml:get_attr(<<"from">>, Attrs), + Attrs1 = lists:keydelete(<<"to">>, 1, Attrs), + Attrs2 = lists:keydelete(<<"from">>, 1, Attrs1), Attrs3 = case To of - {value, ToVal} -> - [{"from", ToVal} | Attrs2]; - _ -> - Attrs2 + {value, ToVal} -> [{<<"from">>, ToVal} | Attrs2]; + _ -> Attrs2 end, Attrs4 = case From of - {value, FromVal} -> - [{"to", FromVal} | Attrs3]; - _ -> - Attrs3 + {value, FromVal} -> [{<<"to">>, FromVal} | Attrs3]; + _ -> Attrs3 end, - Attrs5 = lists:keydelete("type", 1, Attrs4), - Attrs6 = [{"type", "result"} | Attrs5], + Attrs5 = lists:keydelete(<<"type">>, 1, Attrs4), + Attrs6 = [{<<"type">>, <<"result">>} | Attrs5], Attrs6. -make_error_reply({xmlelement, Name, Attrs, SubTags}, Code, Desc) -> - NewAttrs = make_error_reply_attrs(Attrs), - {xmlelement, Name, NewAttrs, SubTags ++ [{xmlelement, "error", - [{"code", Code}], - [{xmlcdata, Desc}]}]}. +-spec make_error_reply(xmlel(), binary(), binary()) -> xmlel(). -make_error_reply({xmlelement, Name, Attrs, SubTags}, Error) -> +make_error_reply(#xmlel{name = Name, attrs = Attrs, + children = SubTags}, + Code, Desc) -> NewAttrs = make_error_reply_attrs(Attrs), - {xmlelement, Name, NewAttrs, SubTags ++ [Error]}. + #xmlel{name = Name, attrs = NewAttrs, + children = + SubTags ++ + [#xmlel{name = <<"error">>, + attrs = [{<<"code">>, Code}], + children = [{xmlcdata, Desc}]}]}. + +-spec make_error_reply(xmlel(), xmlel()) -> xmlel(). + +make_error_reply(#xmlel{name = Name, attrs = Attrs, + children = SubTags}, + Error) -> + NewAttrs = make_error_reply_attrs(Attrs), + #xmlel{name = Name, attrs = NewAttrs, + children = SubTags ++ [Error]}. + +-spec make_error_reply_attrs([attr()]) -> [attr()]. make_error_reply_attrs(Attrs) -> - To = xml:get_attr("to", Attrs), - From = xml:get_attr("from", Attrs), - Attrs1 = lists:keydelete("to", 1, Attrs), - Attrs2 = lists:keydelete("from", 1, Attrs1), + To = xml:get_attr(<<"to">>, Attrs), + From = xml:get_attr(<<"from">>, Attrs), + Attrs1 = lists:keydelete(<<"to">>, 1, Attrs), + Attrs2 = lists:keydelete(<<"from">>, 1, Attrs1), Attrs3 = case To of - {value, ToVal} -> - [{"from", ToVal} | Attrs2]; - _ -> - Attrs2 + {value, ToVal} -> [{<<"from">>, ToVal} | Attrs2]; + _ -> Attrs2 end, Attrs4 = case From of - {value, FromVal} -> - [{"to", FromVal} | Attrs3]; - _ -> - Attrs3 + {value, FromVal} -> [{<<"to">>, FromVal} | Attrs3]; + _ -> Attrs3 end, - Attrs5 = lists:keydelete("type", 1, Attrs4), - Attrs6 = [{"type", "error"} | Attrs5], + Attrs5 = lists:keydelete(<<"type">>, 1, Attrs4), + Attrs6 = [{<<"type">>, <<"error">>} | Attrs5], Attrs6. +-spec make_error_element(binary(), binary()) -> xmlel(). + make_error_element(Code, Desc) -> - {xmlelement, "error", - [{"code", Code}], - [{xmlcdata, Desc}]}. + #xmlel{name = <<"error">>, attrs = [{<<"code">>, Code}], + children = [{xmlcdata, Desc}]}. + +-spec make_correct_from_to_attrs(binary(), binary(), [attr()]) -> [attr()]. make_correct_from_to_attrs(From, To, Attrs) -> - Attrs1 = lists:keydelete("from", 1, Attrs), - Attrs2 = case xml:get_attr("to", Attrs) of - {value, _} -> - Attrs1; - _ -> - [{"to", To} | Attrs1] + Attrs1 = lists:keydelete(<<"from">>, 1, Attrs), + Attrs2 = case xml:get_attr(<<"to">>, Attrs) of + {value, _} -> Attrs1; + _ -> [{<<"to">>, To} | Attrs1] end, - Attrs3 = [{"from", From} | Attrs2], + Attrs3 = [{<<"from">>, From} | Attrs2], Attrs3. +-spec replace_from_to_attrs(binary(), binary(), [attr()]) -> [attr()]. replace_from_to_attrs(From, To, Attrs) -> - Attrs1 = lists:keydelete("to", 1, Attrs), - Attrs2 = lists:keydelete("from", 1, Attrs1), - Attrs3 = [{"to", To} | Attrs2], - Attrs4 = [{"from", From} | Attrs3], + Attrs1 = lists:keydelete(<<"to">>, 1, Attrs), + Attrs2 = lists:keydelete(<<"from">>, 1, Attrs1), + Attrs3 = [{<<"to">>, To} | Attrs2], + Attrs4 = [{<<"from">>, From} | Attrs3], Attrs4. -replace_from_to(From, To, {xmlelement, Name, Attrs, Els}) -> - NewAttrs = replace_from_to_attrs(jlib:jid_to_string(From), - jlib:jid_to_string(To), - Attrs), - {xmlelement, Name, NewAttrs, Els}. +-spec replace_from_to(jid(), jid(), xmlel()) -> xmlel(). + +replace_from_to(From, To, + #xmlel{name = Name, attrs = Attrs, children = Els}) -> + NewAttrs = + replace_from_to_attrs(jlib:jid_to_string(From), + jlib:jid_to_string(To), Attrs), + #xmlel{name = Name, attrs = NewAttrs, children = Els}. + +-spec replace_from_attrs(binary(), [attr()]) -> [attr()]. replace_from_attrs(From, Attrs) -> - Attrs1 = lists:keydelete("from", 1, Attrs), - [{"from", From} | Attrs1]. + Attrs1 = lists:keydelete(<<"from">>, 1, Attrs), + [{<<"from">>, From} | Attrs1]. -replace_from(From, {xmlelement, Name, Attrs, Els}) -> - NewAttrs = replace_from_attrs(jlib:jid_to_string(From), Attrs), - {xmlelement, Name, NewAttrs, Els}. +-spec replace_from(jid(), xmlel()) -> xmlel(). -remove_attr(Attr, {xmlelement, Name, Attrs, Els}) -> +replace_from(From, + #xmlel{name = Name, attrs = Attrs, children = Els}) -> + NewAttrs = replace_from_attrs(jlib:jid_to_string(From), + Attrs), + #xmlel{name = Name, attrs = NewAttrs, children = Els}. + +-spec remove_attr(binary(), xmlel()) -> xmlel(). + +remove_attr(Attr, + #xmlel{name = Name, attrs = Attrs, children = Els}) -> NewAttrs = lists:keydelete(Attr, 1, Attrs), - {xmlelement, Name, NewAttrs, Els}. + #xmlel{name = Name, attrs = NewAttrs, children = Els}. +-spec make_jid(binary(), binary(), binary()) -> jid() | error. make_jid(User, Server, Resource) -> case nodeprep(User) of - error -> error; - LUser -> - case nameprep(Server) of - error -> error; - LServer -> - case resourceprep(Resource) of - error -> error; - LResource -> - #jid{user = User, - server = Server, - resource = Resource, - luser = LUser, - lserver = LServer, - lresource = LResource} - end - end + error -> error; + LUser -> + case nameprep(Server) of + error -> error; + LServer -> + case resourceprep(Resource) of + error -> error; + LResource -> + #jid{user = User, server = Server, resource = Resource, + luser = LUser, lserver = LServer, + lresource = LResource} + end + end end. +-spec make_jid({binary(), binary(), binary()}) -> jid() | error. + make_jid({User, Server, Resource}) -> make_jid(User, Server, Resource). -string_to_jid(J) -> - string_to_jid1(J, ""). +-spec string_to_jid(binary()) -> jid() | error. -string_to_jid1([$@ | _J], "") -> - error; +string_to_jid(S) -> + string_to_jid1(binary_to_list(S), ""). + +string_to_jid1([$@ | _J], "") -> error; string_to_jid1([$@ | J], N) -> string_to_jid2(J, lists:reverse(N), ""); -string_to_jid1([$/ | _J], "") -> - error; +string_to_jid1([$/ | _J], "") -> error; string_to_jid1([$/ | J], N) -> string_to_jid3(J, "", lists:reverse(N), ""); string_to_jid1([C | J], N) -> string_to_jid1(J, [C | N]); -string_to_jid1([], "") -> - error; +string_to_jid1([], "") -> error; string_to_jid1([], N) -> - make_jid("", lists:reverse(N), ""). + make_jid(<<"">>, list_to_binary(lists:reverse(N)), <<"">>). %% Only one "@" is admitted per JID -string_to_jid2([$@ | _J], _N, _S) -> - error; -string_to_jid2([$/ | _J], _N, "") -> - error; +string_to_jid2([$@ | _J], _N, _S) -> error; +string_to_jid2([$/ | _J], _N, "") -> error; string_to_jid2([$/ | J], N, S) -> string_to_jid3(J, N, lists:reverse(S), ""); string_to_jid2([C | J], N, S) -> string_to_jid2(J, N, [C | S]); -string_to_jid2([], _N, "") -> - error; +string_to_jid2([], _N, "") -> error; string_to_jid2([], N, S) -> - make_jid(N, lists:reverse(S), ""). + make_jid(list_to_binary(N), list_to_binary(lists:reverse(S)), <<"">>). string_to_jid3([C | J], N, S, R) -> string_to_jid3(J, N, S, [C | R]); string_to_jid3([], N, S, R) -> - make_jid(N, S, lists:reverse(R)). + make_jid(list_to_binary(N), list_to_binary(S), + list_to_binary(lists:reverse(R))). -jid_to_string(#jid{user = User, server = Server, resource = Resource}) -> +-spec jid_to_string(jid() | ljid()) -> binary(). + +jid_to_string(#jid{user = User, server = Server, + resource = Resource}) -> jid_to_string({User, Server, Resource}); -jid_to_string({Node, Server, Resource}) -> +jid_to_string({N, S, R}) -> + Node = iolist_to_binary(N), + Server = iolist_to_binary(S), + Resource = iolist_to_binary(R), S1 = case Node of - "" -> - ""; - _ -> - Node ++ "@" + <<"">> -> <<"">>; + _ -> <> end, - S2 = S1 ++ Server, + S2 = <>, S3 = case Resource of - "" -> - S2; - _ -> - S2 ++ "/" ++ Resource + <<"">> -> S2; + _ -> <> end, S3. +-spec is_nodename(binary()) -> boolean(). -is_nodename([]) -> - false; -is_nodename(J) -> - nodeprep(J) /= error. - +is_nodename(Node) -> + N = nodeprep(Node), + (N /= error) and (N /= <<>>). %tolower_c(C) when C >= $A, C =< $Z -> % C + 32; @@ -264,12 +274,9 @@ is_nodename(J) -> % C. -define(LOWER(Char), - if - Char >= $A, Char =< $Z -> - Char + 32; - true -> - Char - end). + if Char >= $A, Char =< $Z -> Char + 32; + true -> Char + end). %tolower(S) -> % lists:map(fun tolower_c/1, S). @@ -277,16 +284,16 @@ is_nodename(J) -> %tolower(S) -> % [?LOWER(Char) || Char <- S]. -% Not tail-recursive but it seems works faster than variants above -tolower([C | Cs]) -> - if - C >= $A, C =< $Z -> - [C + 32 | tolower(Cs)]; - true -> - [C | tolower(Cs)] +-spec tolower(binary()) -> binary(). + +tolower(B) -> + iolist_to_binary(tolower_s(binary_to_list(B))). + +tolower_s([C | Cs]) -> + if C >= $A, C =< $Z -> [C + 32 | tolower_s(Cs)]; + true -> [C | tolower_s(Cs)] end; -tolower([]) -> - []. +tolower_s([]) -> []. %tolower([C | Cs]) when C >= $A, C =< $Z -> % [C + 32 | tolower(Cs)]; @@ -295,514 +302,579 @@ tolower([]) -> %tolower([]) -> % []. +-spec nodeprep(binary()) -> binary() | error. -nodeprep(S) when length(S) < 1024 -> +nodeprep("") -> <<>>; +nodeprep(S) when byte_size(S) < 1024 -> R = stringprep:nodeprep(S), - if - length(R) < 1024 -> R; - true -> error + if byte_size(R) < 1024 -> R; + true -> error end; -nodeprep(_) -> - error. +nodeprep(_) -> error. -nameprep(S) when length(S) < 1024 -> +-spec nameprep(binary()) -> binary() | error. + +nameprep(S) when byte_size(S) < 1024 -> R = stringprep:nameprep(S), - if - length(R) < 1024 -> R; - true -> error + if byte_size(R) < 1024 -> R; + true -> error end; -nameprep(_) -> - error. +nameprep(_) -> error. -resourceprep(S) when length(S) < 1024 -> +-spec resourceprep(binary()) -> binary() | error. + +resourceprep(S) when byte_size(S) < 1024 -> R = stringprep:resourceprep(S), - if - length(R) < 1024 -> R; - true -> error + if byte_size(R) < 1024 -> R; + true -> error end; -resourceprep(_) -> - error. +resourceprep(_) -> error. +-spec jid_tolower(jid() | ljid()) -> error | ljid(). -jid_tolower(#jid{luser = U, lserver = S, lresource = R}) -> +jid_tolower(#jid{luser = U, lserver = S, + lresource = R}) -> {U, S, R}; jid_tolower({U, S, R}) -> case nodeprep(U) of - error -> error; - LUser -> - case nameprep(S) of - error -> error; - LServer -> - case resourceprep(R) of - error -> error; - LResource -> - {LUser, LServer, LResource} - end - end + error -> error; + LUser -> + case nameprep(S) of + error -> error; + LServer -> + case resourceprep(R) of + error -> error; + LResource -> {LUser, LServer, LResource} + end + end end. +-spec jid_remove_resource(jid()) -> jid(); + (ljid()) -> ljid(). + jid_remove_resource(#jid{} = JID) -> - JID#jid{resource = "", lresource = ""}; -jid_remove_resource({U, S, _R}) -> - {U, S, ""}. + JID#jid{resource = <<"">>, lresource = <<"">>}; +jid_remove_resource({U, S, _R}) -> {U, S, <<"">>}. + +-spec jid_replace_resource(jid(), binary()) -> error | jid(). jid_replace_resource(JID, Resource) -> case resourceprep(Resource) of - error -> error; - LResource -> - JID#jid{resource = Resource, lresource = LResource} + error -> error; + LResource -> + JID#jid{resource = Resource, lresource = LResource} end. +-spec get_iq_namespace(xmlel()) -> binary(). -get_iq_namespace({xmlelement, Name, _Attrs, Els}) when Name == "iq" -> +get_iq_namespace(#xmlel{name = <<"iq">>, children = Els}) -> case xml:remove_cdata(Els) of - [{xmlelement, _Name2, Attrs2, _Els2}] -> - xml:get_attr_s("xmlns", Attrs2); - _ -> - "" + [#xmlel{attrs = Attrs}] -> xml:get_attr_s(<<"xmlns">>, Attrs); + _ -> <<"">> end; -get_iq_namespace(_) -> - "". +get_iq_namespace(_) -> <<"">>. + +%% +-spec(iq_query_info/1 :: +( + Xmlel :: xmlel()) + -> iq_request() | 'reply' | 'invalid' | 'not_iq' +). %% @spec (xmlelement()) -> iq() | reply | invalid | not_iq +iq_query_info(El) -> iq_info_internal(El, request). -iq_query_info(El) -> - iq_info_internal(El, request). +%% +-spec(iq_query_or_response_info/1 :: +( + Xmlel :: xmlel()) + -> iq_request() | iq_reply() | 'reply' | 'invalid' | 'not_iq' +). iq_query_or_response_info(El) -> iq_info_internal(El, any). -iq_info_internal({xmlelement, Name, Attrs, Els}, Filter) when Name == "iq" -> - %% Filter is either request or any. If it is request, any replies - %% are converted to the atom reply. - ID = xml:get_attr_s("id", Attrs), - Type = xml:get_attr_s("type", Attrs), - Lang = xml:get_attr_s("xml:lang", Attrs), - {Type1, Class} = case Type of - "set" -> {set, request}; - "get" -> {get, request}; - "result" -> {result, reply}; - "error" -> {error, reply}; - _ -> {invalid, invalid} - end, - if - Type1 == invalid -> - invalid; - Class == request; Filter == any -> - %% The iq record is a bit strange. The sub_el field is an - %% XML tuple for requests, but a list of XML tuples for - %% responses. - FilteredEls = xml:remove_cdata(Els), - {XMLNS, SubEl} = - case {Class, FilteredEls} of - {request, [{xmlelement, _Name2, Attrs2, _Els2}]} -> - {xml:get_attr_s("xmlns", Attrs2), - hd(FilteredEls)}; - {reply, _} -> - %% Find the namespace of the first non-error - %% element, if there is one. - NonErrorEls = [El || - {xmlelement, SubName, _, _} = El - <- FilteredEls, - SubName /= "error"], - {case NonErrorEls of - [NonErrorEl] -> - xml:get_tag_attr_s("xmlns", NonErrorEl); - _ -> - "" - end, - FilteredEls}; - _ -> - {"", []} - end, - if XMLNS == "", Class == request -> - invalid; - true -> - #iq{id = ID, - type = Type1, - xmlns = XMLNS, - lang = Lang, - sub_el = SubEl} - end; - Class == reply, Filter /= any -> - reply +iq_info_internal(#xmlel{name = <<"iq">>, attrs = Attrs, children = Els}, Filter) -> + ID = xml:get_attr_s(<<"id">>, Attrs), + Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), + {Type, Class} = case xml:get_attr_s(<<"type">>, Attrs) of + <<"set">> -> {set, request}; + <<"get">> -> {get, request}; + <<"result">> -> {result, reply}; + <<"error">> -> {error, reply}; + _ -> {invalid, invalid} + end, + if Type == invalid -> invalid; Class == request; Filter == any -> + FilteredEls = xml:remove_cdata(Els), + {XMLNS, SubEl} = case {Class, FilteredEls} of + {request, [#xmlel{attrs = Attrs2}]} -> + {xml:get_attr_s(<<"xmlns">>, Attrs2), hd(FilteredEls)}; + {reply, _} -> + NonErrorEls = [El || #xmlel{name = SubName} = El <- FilteredEls, + SubName /= <<"error">>], + {case NonErrorEls of + [NonErrorEl] -> xml:get_tag_attr_s(<<"xmlns">>, NonErrorEl); + _ -> <<"">> + end, + FilteredEls}; + _ -> + {<<"">>, []} + end, + if XMLNS == <<"">>, Class == request -> + invalid; + true -> + #iq{id = ID, type = Type, xmlns = XMLNS, lang = Lang, sub_el = SubEl} + end; + Class == reply, Filter /= any -> + reply end; -iq_info_internal(_, _) -> - not_iq. +iq_info_internal(_, _) -> not_iq. + +-spec is_iq_request_type(set | get | result | error) -> boolean(). is_iq_request_type(set) -> true; is_iq_request_type(get) -> true; is_iq_request_type(_) -> false. -iq_type_to_string(set) -> "set"; -iq_type_to_string(get) -> "get"; -iq_type_to_string(result) -> "result"; -iq_type_to_string(error) -> "error"; -iq_type_to_string(_) -> invalid. +iq_type_to_string(set) -> <<"set">>; +iq_type_to_string(get) -> <<"get">>; +iq_type_to_string(result) -> <<"result">>; +iq_type_to_string(error) -> <<"error">>. +-spec(iq_to_xml/1 :: +( + IQ :: iq()) + -> xmlel() +). iq_to_xml(#iq{id = ID, type = Type, sub_el = SubEl}) -> - if - ID /= "" -> - {xmlelement, "iq", - [{"id", ID}, {"type", iq_type_to_string(Type)}], SubEl}; - true -> - {xmlelement, "iq", - [{"type", iq_type_to_string(Type)}], SubEl} + if ID /= <<"">> -> + #xmlel{name = <<"iq">>, + attrs = + [{<<"id">>, ID}, {<<"type">>, iq_type_to_string(Type)}], + children = SubEl}; + true -> + #xmlel{name = <<"iq">>, + attrs = [{<<"type">>, iq_type_to_string(Type)}], + children = SubEl} end. +-spec(parse_xdata_submit/1 :: +( + El :: xmlel()) + -> [{Var::binary(), Values::[binary()]}] + %% + | 'invalid' +). -parse_xdata_submit(El) -> - {xmlelement, _Name, Attrs, Els} = El, - case xml:get_attr_s("type", Attrs) of - "submit" -> - lists:reverse(parse_xdata_fields(Els, [])); - "form" -> %% This is a workaround to accept Psi's wrong forms - lists:reverse(parse_xdata_fields(Els, [])); - _ -> - invalid +parse_xdata_submit(#xmlel{attrs = Attrs, children = Els}) -> + case xml:get_attr_s(<<"type">>, Attrs) of + <<"submit">> -> + lists:reverse(parse_xdata_fields(Els, [])); + <<"form">> -> %% This is a workaround to accept Psi's wrong forms + lists:reverse(parse_xdata_fields(Els, [])); + _ -> + invalid end. -parse_xdata_fields([], Res) -> - Res; -parse_xdata_fields([{xmlelement, Name, Attrs, SubEls} | Els], Res) -> - case Name of - "field" -> - case xml:get_attr_s("var", Attrs) of - "" -> - parse_xdata_fields(Els, Res); - Var -> - Field = - {Var, lists:reverse(parse_xdata_values(SubEls, []))}, - parse_xdata_fields(Els, [Field | Res]) - end; - _ -> - parse_xdata_fields(Els, Res) +-spec(parse_xdata_fields/2 :: +( + Xmlels :: [xmlel() | cdata()], + Res :: [{Var::binary(), Values :: [binary()]}]) + -> [{Var::binary(), Values::[binary()]}] +). + +parse_xdata_fields([], Res) -> Res; +parse_xdata_fields([#xmlel{name = <<"field">>, attrs = Attrs, children = SubEls} + | Els], Res) -> + case xml:get_attr_s(<<"var">>, Attrs) of + <<>> -> + parse_xdata_fields(Els, Res); + Var -> + Field = {Var, lists:reverse(parse_xdata_values(SubEls, []))}, + parse_xdata_fields(Els, [Field | Res]) end; parse_xdata_fields([_ | Els], Res) -> parse_xdata_fields(Els, Res). -parse_xdata_values([], Res) -> - Res; -parse_xdata_values([{xmlelement, Name, _Attrs, SubEls} | Els], Res) -> - case Name of - "value" -> - Val = xml:get_cdata(SubEls), - parse_xdata_values(Els, [Val | Res]); - _ -> - parse_xdata_values(Els, Res) - end; +-spec(parse_xdata_values/2 :: +( + Xmlels :: [xmlel() | cdata()], + Res :: [binary()]) + -> [binary()] +). + +parse_xdata_values([], Res) -> Res; +parse_xdata_values([#xmlel{name = <<"value">>, children = SubEls} | Els], Res) -> + Val = xml:get_cdata(SubEls), + parse_xdata_values(Els, [Val | Res]); parse_xdata_values([_ | Els], Res) -> parse_xdata_values(Els, Res). -rsm_decode(#iq{sub_el=SubEl})-> - rsm_decode(SubEl); -rsm_decode({xmlelement, _,_,_}=SubEl)-> - case xml:get_subtag(SubEl,"set") of - false -> - none; - {xmlelement, "set", _Attrs, SubEls}-> - lists:foldl(fun rsm_parse_element/2, #rsm_in{}, SubEls) - end. +-spec rsm_decode(iq() | xmlel()) -> none | rsm_in(). -rsm_parse_element({xmlelement, "max",[], _}=Elem, RsmIn)-> +rsm_decode(#iq{sub_el = SubEl}) -> rsm_decode(SubEl); +rsm_decode(#xmlel{} = SubEl) -> + case xml:get_subtag(SubEl, <<"set">>) of + false -> none; + #xmlel{name = <<"set">>, children = SubEls} -> + lists:foldl(fun rsm_parse_element/2, #rsm_in{}, SubEls) + end. + +rsm_parse_element(#xmlel{name = <<"max">>, attrs = []} = + Elem, + RsmIn) -> CountStr = xml:get_tag_cdata(Elem), - {Count, _} = string:to_integer(CountStr), - RsmIn#rsm_in{max=Count}; - -rsm_parse_element({xmlelement, "before", [], _}=Elem, RsmIn)-> + {Count, _} = str:to_integer(CountStr), + RsmIn#rsm_in{max = Count}; +rsm_parse_element(#xmlel{name = <<"before">>, + attrs = []} = + Elem, + RsmIn) -> UID = xml:get_tag_cdata(Elem), - RsmIn#rsm_in{direction=before, id=UID}; - -rsm_parse_element({xmlelement, "after", [], _}=Elem, RsmIn)-> + RsmIn#rsm_in{direction = before, id = UID}; +rsm_parse_element(#xmlel{name = <<"after">>, + attrs = []} = + Elem, + RsmIn) -> UID = xml:get_tag_cdata(Elem), - RsmIn#rsm_in{direction=aft, id=UID}; - -rsm_parse_element({xmlelement, "index",[], _}=Elem, RsmIn)-> + RsmIn#rsm_in{direction = aft, id = UID}; +rsm_parse_element(#xmlel{name = <<"index">>, + attrs = []} = + Elem, + RsmIn) -> IndexStr = xml:get_tag_cdata(Elem), - {Index, _} = string:to_integer(IndexStr), - RsmIn#rsm_in{index=Index}; + {Index, _} = str:to_integer(IndexStr), + RsmIn#rsm_in{index = Index}; +rsm_parse_element(_, RsmIn) -> RsmIn. +-spec rsm_encode(iq(), rsm_out()) -> iq(). -rsm_parse_element(_, RsmIn)-> - RsmIn. +rsm_encode(#iq{sub_el = SubEl} = IQ, RsmOut) -> + Set = #xmlel{name = <<"set">>, + attrs = [{<<"xmlns">>, ?NS_RSM}], + children = lists:reverse(rsm_encode_out(RsmOut))}, + #xmlel{name = Name, attrs = Attrs, children = SubEls} = + SubEl, + New = #xmlel{name = Name, attrs = Attrs, + children = [Set | SubEls]}, + IQ#iq{sub_el = New}. -rsm_encode(#iq{sub_el=SubEl}=IQ,RsmOut)-> - Set = {xmlelement, "set", [{"xmlns", ?NS_RSM}], - lists:reverse(rsm_encode_out(RsmOut))}, - {xmlelement, Name, Attrs, SubEls} = SubEl, - New = {xmlelement, Name, Attrs, [Set | SubEls]}, - IQ#iq{sub_el=New}. +-spec rsm_encode(none | rsm_out()) -> [xmlel()]. -rsm_encode(none)-> - []; -rsm_encode(RsmOut)-> - [{xmlelement, "set", [{"xmlns", ?NS_RSM}], lists:reverse(rsm_encode_out(RsmOut))}]. -rsm_encode_out(#rsm_out{count=Count, index=Index, first=First, last=Last})-> +rsm_encode(none) -> []; +rsm_encode(RsmOut) -> + [#xmlel{name = <<"set">>, + attrs = [{<<"xmlns">>, ?NS_RSM}], + children = lists:reverse(rsm_encode_out(RsmOut))}]. + +rsm_encode_out(#rsm_out{count = Count, index = Index, + first = First, last = Last}) -> El = rsm_encode_first(First, Index, []), - El2 = rsm_encode_last(Last,El), + El2 = rsm_encode_last(Last, El), rsm_encode_count(Count, El2). -rsm_encode_first(undefined, undefined, Arr) -> - Arr; +rsm_encode_first(undefined, undefined, Arr) -> Arr; rsm_encode_first(First, undefined, Arr) -> - [{xmlelement, "first",[], [{xmlcdata, First}]}|Arr]; + [#xmlel{name = <<"first">>, attrs = [], + children = [{xmlcdata, First}]} + | Arr]; rsm_encode_first(First, Index, Arr) -> - [{xmlelement, "first",[{"index", i2l(Index)}], [{xmlcdata, First}]}|Arr]. + [#xmlel{name = <<"first">>, + attrs = [{<<"index">>, i2l(Index)}], + children = [{xmlcdata, First}]} + | Arr]. rsm_encode_last(undefined, Arr) -> Arr; rsm_encode_last(Last, Arr) -> - [{xmlelement, "last",[], [{xmlcdata, Last}]}|Arr]. + [#xmlel{name = <<"last">>, attrs = [], + children = [{xmlcdata, Last}]} + | Arr]. -rsm_encode_count(undefined, Arr)-> Arr; -rsm_encode_count(Count, Arr)-> - [{xmlelement, "count",[], [{xmlcdata, i2l(Count)}]} | Arr]. +rsm_encode_count(undefined, Arr) -> Arr; +rsm_encode_count(Count, Arr) -> + [#xmlel{name = <<"count">>, attrs = [], + children = [{xmlcdata, i2l(Count)}]} + | Arr]. -i2l(I) when is_integer(I) -> integer_to_list(I); -i2l(L) when is_list(L) -> L. +i2l(I) when is_integer(I) -> integer_to_binary(I). + +-type tz() :: {binary(), {integer(), integer()}} | {integer(), integer()} | utc. %% Timezone = utc | {Sign::string(), {Hours, Minutes}} | {Hours, Minutes} %% Hours = integer() %% Minutes = integer() -timestamp_to_iso({{Year, Month, Day}, {Hour, Minute, Second}}, Timezone) -> - Timestamp_string = - lists:flatten( - io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w", - [Year, Month, Day, Hour, Minute, Second])), - Timezone_string = - case Timezone of - utc -> "Z"; - {Sign, {TZh, TZm}} -> - io_lib:format("~s~2..0w:~2..0w", [Sign, TZh, TZm]); - {TZh, TZm} -> - Sign = case TZh >= 0 of - true -> "+"; - false -> "-" - end, - io_lib:format("~s~2..0w:~2..0w", [Sign, abs(TZh),TZm]) - end, - {Timestamp_string, Timezone_string}. +-spec timestamp_to_iso(calendar:datetime(), tz()) -> {binary(), binary()}. -timestamp_to_iso({{Year, Month, Day}, {Hour, Minute, Second}}) -> - lists:flatten( - io_lib:format("~4..0w~2..0w~2..0wT~2..0w:~2..0w:~2..0w", - [Year, Month, Day, Hour, Minute, Second])). +timestamp_to_iso({{Year, Month, Day}, + {Hour, Minute, Second}}, + Timezone) -> + Timestamp_string = + lists:flatten(io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w", + [Year, Month, Day, Hour, Minute, Second])), + Timezone_string = case Timezone of + utc -> "Z"; + {Sign, {TZh, TZm}} -> + io_lib:format("~s~2..0w:~2..0w", [Sign, TZh, TZm]); + {TZh, TZm} -> + Sign = case TZh >= 0 of + true -> "+"; + false -> "-" + end, + io_lib:format("~s~2..0w:~2..0w", + [Sign, abs(TZh), TZm]) + end, + {iolist_to_binary(Timestamp_string), iolist_to_binary(Timezone_string)}. + +-spec timestamp_to_iso(calendar:datetime()) -> binary(). + +timestamp_to_iso({{Year, Month, Day}, + {Hour, Minute, Second}}) -> + iolist_to_binary(io_lib:format("~4..0w~2..0w~2..0wT~2..0w:~2..0w:~2..0w", + [Year, Month, Day, Hour, Minute, Second])). + +-spec timestamp_to_xml(calendar:datetime(), tz(), jid(), binary()) -> xmlel(). timestamp_to_xml(DateTime, Timezone, FromJID, Desc) -> - {T_string, Tz_string} = timestamp_to_iso(DateTime, Timezone), + {T_string, Tz_string} = timestamp_to_iso(DateTime, + Timezone), Text = [{xmlcdata, Desc}], From = jlib:jid_to_string(FromJID), - {xmlelement, "delay", - [{"xmlns", ?NS_DELAY}, - {"from", From}, - {"stamp", T_string ++ Tz_string}], - Text}. - %% TODO: Remove this function once XEP-0091 is Obsolete -timestamp_to_xml({{Year, Month, Day}, {Hour, Minute, Second}}) -> - {xmlelement, "x", - [{"xmlns", ?NS_DELAY91}, - {"stamp", lists:flatten( - io_lib:format("~4..0w~2..0w~2..0wT~2..0w:~2..0w:~2..0w", - [Year, Month, Day, Hour, Minute, Second]))}], - []}. + #xmlel{name = <<"delay">>, + attrs = + [{<<"xmlns">>, ?NS_DELAY}, {<<"from">>, From}, + {<<"stamp">>, <>}], + children = Text}. + +-spec timestamp_to_xml(calendar:datetime()) -> xmlel(). + +timestamp_to_xml({{Year, Month, Day}, + {Hour, Minute, Second}}) -> + #xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, ?NS_DELAY91}, + {<<"stamp">>, + iolist_to_binary(io_lib:format("~4..0w~2..0w~2..0wT~2..0w:~2..0w:~2..0w", + [Year, Month, Day, Hour, Minute, + Second]))}], + children = []}. + +-spec now_to_utc_string(erlang:timestamp()) -> binary(). now_to_utc_string({MegaSecs, Secs, MicroSecs}) -> {{Year, Month, Day}, {Hour, Minute, Second}} = - calendar:now_to_universal_time({MegaSecs, Secs, MicroSecs}), - lists:flatten( - io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w.~6..0wZ", - [Year, Month, Day, Hour, Minute, Second, MicroSecs])). + calendar:now_to_universal_time({MegaSecs, Secs, + MicroSecs}), + list_to_binary(io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w.~6." + ".0wZ", + [Year, Month, Day, Hour, Minute, Second, + MicroSecs])). + +-spec now_to_local_string(erlang:timestamp()) -> binary(). now_to_local_string({MegaSecs, Secs, MicroSecs}) -> - LocalTime = calendar:now_to_local_time({MegaSecs, Secs, MicroSecs}), - UTCTime = calendar:now_to_universal_time({MegaSecs, Secs, MicroSecs}), - Seconds = calendar:datetime_to_gregorian_seconds(LocalTime) - - calendar:datetime_to_gregorian_seconds(UTCTime), - {{H, M, _}, Sign} = if - Seconds < 0 -> - {calendar:seconds_to_time(-Seconds), "-"}; - true -> - {calendar:seconds_to_time(Seconds), "+"} - end, - {{Year, Month, Day}, {Hour, Minute, Second}} = LocalTime, - lists:flatten( - io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w.~6..0w~s~2..0w:~2..0w", - [Year, Month, Day, Hour, Minute, Second, MicroSecs, Sign, H, M])). + LocalTime = calendar:now_to_local_time({MegaSecs, Secs, + MicroSecs}), + UTCTime = calendar:now_to_universal_time({MegaSecs, + Secs, MicroSecs}), + Seconds = + calendar:datetime_to_gregorian_seconds(LocalTime) - + calendar:datetime_to_gregorian_seconds(UTCTime), + {{H, M, _}, Sign} = if Seconds < 0 -> + {calendar:seconds_to_time(-Seconds), "-"}; + true -> {calendar:seconds_to_time(Seconds), "+"} + end, + {{Year, Month, Day}, {Hour, Minute, Second}} = + LocalTime, + list_to_binary(io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w.~6." + ".0w~s~2..0w:~2..0w", + [Year, Month, Day, Hour, Minute, Second, + MicroSecs, Sign, H, M])). +-spec datetime_string_to_timestamp(binary()) -> undefined | erlang:timestamp(). -% yyyy-mm-ddThh:mm:ss[.sss]{Z|{+|-}hh:mm} -> {MegaSecs, Secs, MicroSecs} datetime_string_to_timestamp(TimeStr) -> case catch parse_datetime(TimeStr) of - {'EXIT', _Err} -> - undefined; - TimeStamp -> - TimeStamp + {'EXIT', _Err} -> undefined; + TimeStamp -> TimeStamp end. parse_datetime(TimeStr) -> - [Date, Time] = string:tokens(TimeStr, "T"), + [Date, Time] = str:tokens(TimeStr, <<"T">>), D = parse_date(Date), {T, MS, TZH, TZM} = parse_time(Time), S = calendar:datetime_to_gregorian_seconds({D, T}), - S1 = calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}), - Seconds = (S - S1) - TZH * 60 * 60 - TZM * 60, + S1 = calendar:datetime_to_gregorian_seconds({{1970, 1, + 1}, + {0, 0, 0}}), + Seconds = S - S1 - TZH * 60 * 60 - TZM * 60, {Seconds div 1000000, Seconds rem 1000000, MS}. % yyyy-mm-dd parse_date(Date) -> - [Y, M, D] = string:tokens(Date, "-"), - Date1 = {list_to_integer(Y), list_to_integer(M), list_to_integer(D)}, + [Y, M, D] = str:tokens(Date, <<"-">>), + Date1 = {binary_to_integer(Y), binary_to_integer(M), + binary_to_integer(D)}, case calendar:valid_date(Date1) of - true -> - Date1; - _ -> - false + true -> Date1; + _ -> false end. % hh:mm:ss[.sss]TZD parse_time(Time) -> - case string:str(Time, "Z") of - 0 -> - parse_time_with_timezone(Time); - _ -> - [T | _] = string:tokens(Time, "Z"), - {TT, MS} = parse_time1(T), - {TT, MS, 0, 0} + case str:str(Time, <<"Z">>) of + 0 -> parse_time_with_timezone(Time); + _ -> + [T | _] = str:tokens(Time, <<"Z">>), + {TT, MS} = parse_time1(T), + {TT, MS, 0, 0} end. parse_time_with_timezone(Time) -> - case string:str(Time, "+") of - 0 -> - case string:str(Time, "-") of - 0 -> - false; - _ -> - parse_time_with_timezone(Time, "-") - end; - _ -> - parse_time_with_timezone(Time, "+") + case str:str(Time, <<"+">>) of + 0 -> + case str:str(Time, <<"-">>) of + 0 -> false; + _ -> parse_time_with_timezone(Time, <<"-">>) + end; + _ -> parse_time_with_timezone(Time, <<"+">>) end. parse_time_with_timezone(Time, Delim) -> - [T, TZ] = string:tokens(Time, Delim), + [T, TZ] = str:tokens(Time, Delim), {TZH, TZM} = parse_timezone(TZ), {TT, MS} = parse_time1(T), case Delim of - "-" -> - {TT, MS, -TZH, -TZM}; - "+" -> - {TT, MS, TZH, TZM} + <<"-">> -> {TT, MS, -TZH, -TZM}; + <<"+">> -> {TT, MS, TZH, TZM} end. parse_timezone(TZ) -> - [H, M] = string:tokens(TZ, ":"), + [H, M] = str:tokens(TZ, <<":">>), {[H1, M1], true} = check_list([{H, 12}, {M, 60}]), {H1, M1}. parse_time1(Time) -> - [HMS | T] = string:tokens(Time, "."), + [HMS | T] = str:tokens(Time, <<".">>), MS = case T of - [] -> - 0; - [Val] -> - list_to_integer(string:left(Val, 6, $0)) + [] -> 0; + [Val] -> binary_to_integer(str:left(Val, 6, $0)) end, - [H, M, S] = string:tokens(HMS, ":"), - {[H1, M1, S1], true} = check_list([{H, 24}, {M, 60}, {S, 60}]), + [H, M, S] = str:tokens(HMS, <<":">>), + {[H1, M1, S1], true} = check_list([{H, 24}, {M, 60}, + {S, 60}]), {{H1, M1, S1}, MS}. check_list(List) -> - lists:mapfoldl( - fun({L, N}, B)-> - V = list_to_integer(L), - if - (V >= 0) and (V =< N) -> - {V, B}; - true -> - {false, false} - end - end, true, List). - + lists:mapfoldl(fun ({L, N}, B) -> + V = binary_to_integer(L), + if (V >= 0) and (V =< N) -> {V, B}; + true -> {false, false} + end + end, + true, List). % % Base64 stuff (based on httpd_util.erl) % +-spec decode_base64(binary()) -> binary(). + decode_base64(S) -> - decode1_base64([C || C <- S, - C /= $ , - C /= $\t, - C /= $\n, - C /= $\r]). + decode_base64_bin(S, <<>>). -decode1_base64([]) -> - []; -decode1_base64([Sextet1,Sextet2,$=,$=|Rest]) -> - Bits2x6= - (d(Sextet1) bsl 18) bor - (d(Sextet2) bsl 12), - Octet1=Bits2x6 bsr 16, - [Octet1|decode1_base64(Rest)]; -decode1_base64([Sextet1,Sextet2,Sextet3,$=|Rest]) -> - Bits3x6= - (d(Sextet1) bsl 18) bor - (d(Sextet2) bsl 12) bor - (d(Sextet3) bsl 6), - Octet1=Bits3x6 bsr 16, - Octet2=(Bits3x6 bsr 8) band 16#ff, - [Octet1,Octet2|decode1_base64(Rest)]; -decode1_base64([Sextet1,Sextet2,Sextet3,Sextet4|Rest]) -> - Bits4x6= - (d(Sextet1) bsl 18) bor - (d(Sextet2) bsl 12) bor - (d(Sextet3) bsl 6) bor - d(Sextet4), - Octet1=Bits4x6 bsr 16, - Octet2=(Bits4x6 bsr 8) band 16#ff, - Octet3=Bits4x6 band 16#ff, - [Octet1,Octet2,Octet3|decode1_base64(Rest)]; -decode1_base64(_CatchAll) -> - "". +take_without_spaces(Bin, Count) -> + take_without_spaces(Bin, Count, <<>>). -d(X) when X >= $A, X =<$Z -> - X-65; -d(X) when X >= $a, X =<$z -> - X-71; -d(X) when X >= $0, X =<$9 -> - X+4; +take_without_spaces(Bin, 0, Acc) -> + {Acc, Bin}; +take_without_spaces(<<>>, _, Acc) -> + {Acc, <<>>}; +take_without_spaces(<<$\s, Tail/binary>>, Count, Acc) -> + take_without_spaces(Tail, Count, Acc); +take_without_spaces(<<$\t, Tail/binary>>, Count, Acc) -> + take_without_spaces(Tail, Count, Acc); +take_without_spaces(<<$\n, Tail/binary>>, Count, Acc) -> + take_without_spaces(Tail, Count, Acc); +take_without_spaces(<<$\r, Tail/binary>>, Count, Acc) -> + take_without_spaces(Tail, Count, Acc); +take_without_spaces(<>, Count, Acc) -> + take_without_spaces(Tail, Count-1, <>). + +decode_base64_bin(<<>>, Acc) -> + Acc; +decode_base64_bin(Bin, Acc) -> + case take_without_spaces(Bin, 4) of + {<>, _} -> + <>; + {<>, _} -> + <>; + {<>, Tail} -> + Acc2 = <>, + decode_base64_bin(Tail, Acc2); + _ -> + <<"">> + end. + +d(X) when X >= $A, X =< $Z -> X - 65; +d(X) when X >= $a, X =< $z -> X - 71; +d(X) when X >= $0, X =< $9 -> X + 4; d($+) -> 62; d($/) -> 63; d(_) -> 63. -encode_base64([]) -> - []; -encode_base64([A]) -> - [e(A bsr 2), e((A band 3) bsl 4), $=, $=]; -encode_base64([A,B]) -> - [e(A bsr 2), e(((A band 3) bsl 4) bor (B bsr 4)), e((B band 15) bsl 2), $=]; -encode_base64([A,B,C|Ls]) -> - encode_base64_do(A,B,C, Ls). -encode_base64_do(A,B,C, Rest) -> - BB = (A bsl 16) bor (B bsl 8) bor C, - [e(BB bsr 18), e((BB bsr 12) band 63), - e((BB bsr 6) band 63), e(BB band 63)|encode_base64(Rest)]. - -e(X) when X >= 0, X < 26 -> X+65; -e(X) when X>25, X<52 -> X+71; -e(X) when X>51, X<62 -> X-4; -e(62) -> $+; -e(63) -> $/; -e(X) -> exit({bad_encode_base64_token, X}). - %% Convert Erlang inet IP to list +-spec encode_base64(binary()) -> binary(). + +encode_base64(Data) -> + encode_base64_bin(Data, <<>>). + +encode_base64_bin(<>, Acc) -> + encode_base64_bin(Tail, <>); +encode_base64_bin(<>, Acc) -> + <>; +encode_base64_bin(<>, Acc) -> + <>; +encode_base64_bin(<<>>, Acc) -> + Acc. + +e(X) when X >= 0, X < 26 -> X + 65; +e(X) when X > 25, X < 52 -> X + 71; +e(X) when X > 51, X < 62 -> X - 4; +e(62) -> $+; +e(63) -> $/; +e(X) -> exit({bad_encode_base64_token, X}). + +-spec ip_to_list(inet:ip_address() | undefined | + {inet:ip_address(), inet:port_number()}) -> binary(). + ip_to_list({IP, _Port}) -> ip_to_list(IP); -ip_to_list({_,_,_,_,_,_,_,_} = Ipv6Address) -> - inet_parse:ntoa(Ipv6Address); %% This function clause could use inet_parse too: -ip_to_list({A,B,C,D}) -> - lists:flatten(io_lib:format("~w.~w.~w.~w",[A,B,C,D])); +ip_to_list(undefined) -> + <<"unknown">>; ip_to_list(IP) -> - lists:flatten(io_lib:format("~w", [IP])). + list_to_binary(inet_parse:ntoa(IP)). + +binary_to_atom(Bin) -> + erlang:binary_to_atom(Bin, utf8). + +binary_to_integer(Bin) -> + list_to_integer(binary_to_list(Bin)). + +binary_to_integer(Bin, Base) -> + list_to_integer(binary_to_list(Bin), Base). + +integer_to_binary(I) -> + list_to_binary(integer_to_list(I)). + +integer_to_binary(I, Base) -> + list_to_binary(erlang:integer_to_list(I, Base)). + +tuple_to_binary(T) -> + iolist_to_binary(tuple_to_list(T)). + +atom_to_binary(A) -> + erlang:atom_to_binary(A, utf8). diff --git a/src/jlib.hrl b/src/jlib.hrl index 0e5c8322b..b0f59e288 100644 --- a/src/jlib.hrl +++ b/src/jlib.hrl @@ -19,318 +19,687 @@ %%% %%%---------------------------------------------------------------------- --define(NS_DISCO_ITEMS, "http://jabber.org/protocol/disco#items"). --define(NS_DISCO_INFO, "http://jabber.org/protocol/disco#info"). --define(NS_VCARD, "vcard-temp"). --define(NS_VCARD_UPDATE, "vcard-temp:x:update"). --define(NS_AUTH, "jabber:iq:auth"). --define(NS_AUTH_ERROR, "jabber:iq:auth:error"). --define(NS_REGISTER, "jabber:iq:register"). --define(NS_SEARCH, "jabber:iq:search"). --define(NS_ROSTER, "jabber:iq:roster"). --define(NS_ROSTER_VER, "urn:xmpp:features:rosterver"). --define(NS_PRIVACY, "jabber:iq:privacy"). --define(NS_BLOCKING, "urn:xmpp:blocking"). --define(NS_PRIVATE, "jabber:iq:private"). --define(NS_VERSION, "jabber:iq:version"). --define(NS_TIME90, "jabber:iq:time"). % TODO: Remove once XEP-0090 is Obsolete --define(NS_TIME, "urn:xmpp:time"). --define(NS_LAST, "jabber:iq:last"). --define(NS_XDATA, "jabber:x:data"). --define(NS_IQDATA, "jabber:iq:data"). --define(NS_DELAY91, "jabber:x:delay"). % TODO: Remove once XEP-0091 is Obsolete --define(NS_DELAY, "urn:xmpp:delay"). --define(NS_EXPIRE, "jabber:x:expire"). --define(NS_EVENT, "jabber:x:event"). --define(NS_CHATSTATES, "http://jabber.org/protocol/chatstates"). --define(NS_XCONFERENCE, "jabber:x:conference"). --define(NS_STATS, "http://jabber.org/protocol/stats"). --define(NS_MUC, "http://jabber.org/protocol/muc"). --define(NS_MUC_USER, "http://jabber.org/protocol/muc#user"). --define(NS_MUC_ADMIN, "http://jabber.org/protocol/muc#admin"). --define(NS_MUC_OWNER, "http://jabber.org/protocol/muc#owner"). --define(NS_MUC_UNIQUE, "http://jabber.org/protocol/muc#unique"). --define(NS_PUBSUB, "http://jabber.org/protocol/pubsub"). --define(NS_PUBSUB_EVENT, "http://jabber.org/protocol/pubsub#event"). --define(NS_PUBSUB_OWNER, "http://jabber.org/protocol/pubsub#owner"). --define(NS_PUBSUB_NMI, "http://jabber.org/protocol/pubsub#node-meta-info"). --define(NS_PUBSUB_ERRORS,"http://jabber.org/protocol/pubsub#errors"). --define(NS_PUBSUB_NODE_CONFIG, "http://jabber.org/protocol/pubsub#node_config"). --define(NS_PUBSUB_SUB_OPTIONS, "http://jabber.org/protocol/pubsub#subscribe_options"). --define(NS_PUBSUB_SUB_AUTH, "http://jabber.org/protocol/pubsub#subscribe_authorization"). --define(NS_PUBSUB_GET_PENDING, "http://jabber.org/protocol/pubsub#get-pending"). --define(NS_COMMANDS, "http://jabber.org/protocol/commands"). --define(NS_BYTESTREAMS, "http://jabber.org/protocol/bytestreams"). --define(NS_ADMIN, "http://jabber.org/protocol/admin"). --define(NS_SERVERINFO, "http://jabber.org/network/serverinfo"). --define(NS_RSM, "http://jabber.org/protocol/rsm"). --define(NS_EJABBERD_CONFIG, "ejabberd:config"). +-define(NS_DISCO_ITEMS, + <<"http://jabber.org/protocol/disco#items">>). --define(NS_STREAM, "http://etherx.jabber.org/streams"). +-define(NS_DISCO_INFO, + <<"http://jabber.org/protocol/disco#info">>). --define(NS_STANZAS, "urn:ietf:params:xml:ns:xmpp-stanzas"). --define(NS_STREAMS, "urn:ietf:params:xml:ns:xmpp-streams"). +-define(NS_VCARD, <<"vcard-temp">>). --define(NS_TLS, "urn:ietf:params:xml:ns:xmpp-tls"). --define(NS_SASL, "urn:ietf:params:xml:ns:xmpp-sasl"). --define(NS_SESSION, "urn:ietf:params:xml:ns:xmpp-session"). --define(NS_BIND, "urn:ietf:params:xml:ns:xmpp-bind"). +-define(NS_VCARD_UPDATE, <<"vcard-temp:x:update">>). --define(NS_FEATURE_IQAUTH, "http://jabber.org/features/iq-auth"). --define(NS_FEATURE_IQREGISTER, "http://jabber.org/features/iq-register"). --define(NS_FEATURE_COMPRESS, "http://jabber.org/features/compress"). --define(NS_FEATURE_MSGOFFLINE, "msgoffline"). +-define(NS_AUTH, <<"jabber:iq:auth">>). --define(NS_COMPRESS, "http://jabber.org/protocol/compress"). +-define(NS_AUTH_ERROR, <<"jabber:iq:auth:error">>). --define(NS_CAPS, "http://jabber.org/protocol/caps"). --define(NS_SHIM, "http://jabber.org/protocol/shim"). --define(NS_ADDRESS, "http://jabber.org/protocol/address"). +-define(NS_REGISTER, <<"jabber:iq:register">>). -%% CAPTCHA related NSes. --define(NS_OOB, "jabber:x:oob"). --define(NS_CAPTCHA, "urn:xmpp:captcha"). --define(NS_MEDIA, "urn:xmpp:media-element"). --define(NS_BOB, "urn:xmpp:bob"). +-define(NS_SEARCH, <<"jabber:iq:search">>). + +-define(NS_ROSTER, <<"jabber:iq:roster">>). + +-define(NS_ROSTER_VER, + <<"urn:xmpp:features:rosterver">>). + +-define(NS_PRIVACY, <<"jabber:iq:privacy">>). + +-define(NS_BLOCKING, <<"urn:xmpp:blocking">>). + +-define(NS_PRIVATE, <<"jabber:iq:private">>). + +-define(NS_VERSION, <<"jabber:iq:version">>). + +-define(NS_TIME90, <<"jabber:iq:time">>). + +-define(NS_TIME, <<"urn:xmpp:time">>). + +-define(NS_LAST, <<"jabber:iq:last">>). + +-define(NS_XDATA, <<"jabber:x:data">>). + +-define(NS_IQDATA, <<"jabber:iq:data">>). + +-define(NS_DELAY91, <<"jabber:x:delay">>). + +-define(NS_DELAY, <<"urn:xmpp:delay">>). + +-define(NS_EXPIRE, <<"jabber:x:expire">>). + +-define(NS_EVENT, <<"jabber:x:event">>). + +-define(NS_CHATSTATES, + <<"http://jabber.org/protocol/chatstates">>). + +-define(NS_XCONFERENCE, <<"jabber:x:conference">>). + +-define(NS_STATS, + <<"http://jabber.org/protocol/stats">>). + +-define(NS_MUC, <<"http://jabber.org/protocol/muc">>). + +-define(NS_MUC_USER, + <<"http://jabber.org/protocol/muc#user">>). + +-define(NS_MUC_ADMIN, + <<"http://jabber.org/protocol/muc#admin">>). + +-define(NS_MUC_OWNER, + <<"http://jabber.org/protocol/muc#owner">>). + +-define(NS_MUC_UNIQUE, + <<"http://jabber.org/protocol/muc#unique">>). + +-define(NS_PUBSUB, + <<"http://jabber.org/protocol/pubsub">>). + +-define(NS_PUBSUB_EVENT, + <<"http://jabber.org/protocol/pubsub#event">>). + +-define(NS_PUBSUB_META_DATA, + <<"http://jabber.org/protocol/pubsub#meta-data">>). + +-define(NS_PUBSUB_OWNER, + <<"http://jabber.org/protocol/pubsub#owner">>). + +-define(NS_PUBSUB_NMI, + <<"http://jabber.org/protocol/pubsub#node-meta-info">>). + +-define(NS_PUBSUB_ERRORS, + <<"http://jabber.org/protocol/pubsub#errors">>). + +-define(NS_PUBSUB_NODE_CONFIG, + <<"http://jabber.org/protocol/pubsub#node_config">>). + +-define(NS_PUBSUB_SUB_OPTIONS, + <<"http://jabber.org/protocol/pubsub#subscribe_options">>). + +-define(NS_PUBSUB_SUBSCRIBE_OPTIONS, + <<"http://jabber.org/protocol/pubsub#subscribe_options">>). + +-define(NS_PUBSUB_PUBLISH_OPTIONS, + <<"http://jabber.org/protocol/pubsub#publish_options">>). + +-define(NS_PUBSUB_SUB_AUTH, + <<"http://jabber.org/protocol/pubsub#subscribe_authorization">>). + +-define(NS_PUBSUB_GET_PENDING, + <<"http://jabber.org/protocol/pubsub#get-pending">>). + +-define(NS_COMMANDS, + <<"http://jabber.org/protocol/commands">>). + +-define(NS_BYTESTREAMS, + <<"http://jabber.org/protocol/bytestreams">>). + +-define(NS_ADMIN, + <<"http://jabber.org/protocol/admin">>). +-define(NS_ADMIN_ANNOUNCE, + <<"http://jabber.org/protocol/admin#announce">>). +-define(NS_ADMIN_ANNOUNCE_ALL, + <<"http://jabber.org/protocol/admin#announce-all">>). +-define(NS_ADMIN_SET_MOTD, + <<"http://jabber.org/protocol/admin#set-motd">>). +-define(NS_ADMIN_EDIT_MOTD, + <<"http://jabber.org/protocol/admin#edit-motd">>). +-define(NS_ADMIN_DELETE_MOTD, + <<"http://jabber.org/protocol/admin#delete-motd">>). +-define(NS_ADMIN_ANNOUNCE_ALLHOSTS, + <<"http://jabber.org/protocol/admin#announce-allhosts">>). +-define(NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS, + <<"http://jabber.org/protocol/admin#announce-all-allhosts">>). +-define(NS_ADMIN_SET_MOTD_ALLHOSTS, + <<"http://jabber.org/protocol/admin#set-motd-allhosts">>). +-define(NS_ADMIN_EDIT_MOTD_ALLHOSTS, + <<"http://jabber.org/protocol/admin#edit-motd-allhosts">>). +-define(NS_ADMIN_DELETE_MOTD_ALLHOSTS, + <<"http://jabber.org/protocol/admin#delete-motd-allhosts">>). + +-define(NS_SERVERINFO, + <<"http://jabber.org/network/serverinfo">>). + +-define(NS_RSM, <<"http://jabber.org/protocol/rsm">>). + +-define(NS_EJABBERD_CONFIG, <<"ejabberd:config">>). + +-define(NS_STREAM, + <<"http://etherx.jabber.org/streams">>). + +-define(NS_STANZAS, + <<"urn:ietf:params:xml:ns:xmpp-stanzas">>). + +-define(NS_STREAMS, + <<"urn:ietf:params:xml:ns:xmpp-streams">>). + +-define(NS_TLS, <<"urn:ietf:params:xml:ns:xmpp-tls">>). + +-define(NS_SASL, + <<"urn:ietf:params:xml:ns:xmpp-sasl">>). + +-define(NS_SESSION, + <<"urn:ietf:params:xml:ns:xmpp-session">>). + +-define(NS_BIND, + <<"urn:ietf:params:xml:ns:xmpp-bind">>). + +-define(NS_FEATURE_IQAUTH, + <<"http://jabber.org/features/iq-auth">>). + +-define(NS_FEATURE_IQREGISTER, + <<"http://jabber.org/features/iq-register">>). + +-define(NS_FEATURE_COMPRESS, + <<"http://jabber.org/features/compress">>). + +-define(NS_FEATURE_MSGOFFLINE, <<"msgoffline">>). + +-define(NS_COMPRESS, + <<"http://jabber.org/protocol/compress">>). + +-define(NS_CAPS, <<"http://jabber.org/protocol/caps">>). + +-define(NS_SHIM, <<"http://jabber.org/protocol/shim">>). + +-define(NS_ADDRESS, + <<"http://jabber.org/protocol/address">>). + +-define(NS_OOB, <<"jabber:x:oob">>). + +-define(NS_CAPTCHA, <<"urn:xmpp:captcha">>). + +-define(NS_MEDIA, <<"urn:xmpp:media-element">>). + +-define(NS_BOB, <<"urn:xmpp:bob">>). -% TODO: remove "code" attribute (currently it used for backward-compatibility) -define(STANZA_ERROR(Code, Type, Condition), - {xmlelement, "error", - [{"code", Code}, {"type", Type}], - [{xmlelement, Condition, [{"xmlns", ?NS_STANZAS}], []}]}). + #xmlel{name = <<"error">>, + attrs = [{<<"code">>, Code}, {<<"type">>, Type}], + children = + [#xmlel{name = Condition, + attrs = [{<<"xmlns">>, ?NS_STANZAS}], + children = []}]}). -define(ERR_BAD_FORMAT, - ?STANZA_ERROR("406", "modify", "bad-format")). + ?STANZA_ERROR(<<"406">>, <<"modify">>, + <<"bad-format">>)). + -define(ERR_BAD_REQUEST, - ?STANZA_ERROR("400", "modify", "bad-request")). + ?STANZA_ERROR(<<"400">>, <<"modify">>, + <<"bad-request">>)). + -define(ERR_CONFLICT, - ?STANZA_ERROR("409", "cancel", "conflict")). + ?STANZA_ERROR(<<"409">>, <<"cancel">>, <<"conflict">>)). + -define(ERR_FEATURE_NOT_IMPLEMENTED, - ?STANZA_ERROR("501", "cancel", "feature-not-implemented")). + ?STANZA_ERROR(<<"501">>, <<"cancel">>, + <<"feature-not-implemented">>)). + -define(ERR_FORBIDDEN, - ?STANZA_ERROR("403", "auth", "forbidden")). + ?STANZA_ERROR(<<"403">>, <<"auth">>, <<"forbidden">>)). + -define(ERR_GONE, - ?STANZA_ERROR("302", "modify", "gone")). + ?STANZA_ERROR(<<"302">>, <<"modify">>, <<"gone">>)). + -define(ERR_INTERNAL_SERVER_ERROR, - ?STANZA_ERROR("500", "wait", "internal-server-error")). + ?STANZA_ERROR(<<"500">>, <<"wait">>, + <<"internal-server-error">>)). + -define(ERR_ITEM_NOT_FOUND, - ?STANZA_ERROR("404", "cancel", "item-not-found")). + ?STANZA_ERROR(<<"404">>, <<"cancel">>, + <<"item-not-found">>)). + -define(ERR_JID_MALFORMED, - ?STANZA_ERROR("400", "modify", "jid-malformed")). + ?STANZA_ERROR(<<"400">>, <<"modify">>, + <<"jid-malformed">>)). + -define(ERR_NOT_ACCEPTABLE, - ?STANZA_ERROR("406", "modify", "not-acceptable")). + ?STANZA_ERROR(<<"406">>, <<"modify">>, + <<"not-acceptable">>)). + -define(ERR_NOT_ALLOWED, - ?STANZA_ERROR("405", "cancel", "not-allowed")). + ?STANZA_ERROR(<<"405">>, <<"cancel">>, + <<"not-allowed">>)). + -define(ERR_NOT_AUTHORIZED, - ?STANZA_ERROR("401", "auth", "not-authorized")). + ?STANZA_ERROR(<<"401">>, <<"auth">>, + <<"not-authorized">>)). + -define(ERR_PAYMENT_REQUIRED, - ?STANZA_ERROR("402", "auth", "payment-required")). + ?STANZA_ERROR(<<"402">>, <<"auth">>, + <<"payment-required">>)). + -define(ERR_RECIPIENT_UNAVAILABLE, - ?STANZA_ERROR("404", "wait", "recipient-unavailable")). + ?STANZA_ERROR(<<"404">>, <<"wait">>, + <<"recipient-unavailable">>)). + -define(ERR_REDIRECT, - ?STANZA_ERROR("302", "modify", "redirect")). + ?STANZA_ERROR(<<"302">>, <<"modify">>, <<"redirect">>)). + -define(ERR_REGISTRATION_REQUIRED, - ?STANZA_ERROR("407", "auth", "registration-required")). + ?STANZA_ERROR(<<"407">>, <<"auth">>, + <<"registration-required">>)). + -define(ERR_REMOTE_SERVER_NOT_FOUND, - ?STANZA_ERROR("404", "cancel", "remote-server-not-found")). + ?STANZA_ERROR(<<"404">>, <<"cancel">>, + <<"remote-server-not-found">>)). + -define(ERR_REMOTE_SERVER_TIMEOUT, - ?STANZA_ERROR("504", "wait", "remote-server-timeout")). + ?STANZA_ERROR(<<"504">>, <<"wait">>, + <<"remote-server-timeout">>)). + -define(ERR_RESOURCE_CONSTRAINT, - ?STANZA_ERROR("500", "wait", "resource-constraint")). + ?STANZA_ERROR(<<"500">>, <<"wait">>, + <<"resource-constraint">>)). + -define(ERR_SERVICE_UNAVAILABLE, - ?STANZA_ERROR("503", "cancel", "service-unavailable")). + ?STANZA_ERROR(<<"503">>, <<"cancel">>, + <<"service-unavailable">>)). + -define(ERR_SUBSCRIPTION_REQUIRED, - ?STANZA_ERROR("407", "auth", "subscription-required")). + ?STANZA_ERROR(<<"407">>, <<"auth">>, + <<"subscription-required">>)). + -define(ERR_UNEXPECTED_REQUEST, - ?STANZA_ERROR("400", "wait", "unexpected-request")). + ?STANZA_ERROR(<<"400">>, <<"wait">>, + <<"unexpected-request">>)). + -define(ERR_UNEXPECTED_REQUEST_CANCEL, - ?STANZA_ERROR("401", "cancel", "unexpected-request")). + ?STANZA_ERROR(<<"401">>, <<"cancel">>, + <<"unexpected-request">>)). + %-define(ERR_, % ?STANZA_ERROR("", "", "")). --define(STANZA_ERRORT(Code, Type, Condition, Lang, Text), - {xmlelement, "error", - [{"code", Code}, {"type", Type}], - [{xmlelement, Condition, [{"xmlns", ?NS_STANZAS}], []}, - {xmlelement, "text", [{"xmlns", ?NS_STANZAS}], - [{xmlcdata, translate:translate(Lang, Text)}]}]}). +-define(STANZA_ERRORT(Code, Type, Condition, Lang, + Text), + #xmlel{name = <<"error">>, + attrs = [{<<"code">>, Code}, {<<"type">>, Type}], + children = + [#xmlel{name = Condition, + attrs = [{<<"xmlns">>, ?NS_STANZAS}], children = []}, + #xmlel{name = <<"text">>, + attrs = [{<<"xmlns">>, ?NS_STANZAS}], + children = + [{xmlcdata, + translate:translate(Lang, Text)}]}]}). -define(ERRT_BAD_FORMAT(Lang, Text), - ?STANZA_ERRORT("406", "modify", "bad-format", Lang, Text)). + ?STANZA_ERRORT(<<"406">>, <<"modify">>, + <<"bad-format">>, Lang, Text)). + -define(ERRT_BAD_REQUEST(Lang, Text), - ?STANZA_ERRORT("400", "modify", "bad-request", Lang, Text)). + ?STANZA_ERRORT(<<"400">>, <<"modify">>, + <<"bad-request">>, Lang, Text)). + -define(ERRT_CONFLICT(Lang, Text), - ?STANZA_ERRORT("409", "cancel", "conflict", Lang, Text)). + ?STANZA_ERRORT(<<"409">>, <<"cancel">>, <<"conflict">>, + Lang, Text)). + -define(ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Text), - ?STANZA_ERRORT("501", "cancel", "feature-not-implemented", Lang, Text)). + ?STANZA_ERRORT(<<"501">>, <<"cancel">>, + <<"feature-not-implemented">>, Lang, Text)). + -define(ERRT_FORBIDDEN(Lang, Text), - ?STANZA_ERRORT("403", "auth", "forbidden", Lang, Text)). + ?STANZA_ERRORT(<<"403">>, <<"auth">>, <<"forbidden">>, + Lang, Text)). + -define(ERRT_GONE(Lang, Text), - ?STANZA_ERRORT("302", "modify", "gone", Lang, Text)). + ?STANZA_ERRORT(<<"302">>, <<"modify">>, <<"gone">>, + Lang, Text)). + -define(ERRT_INTERNAL_SERVER_ERROR(Lang, Text), - ?STANZA_ERRORT("500", "wait", "internal-server-error", Lang, Text)). + ?STANZA_ERRORT(<<"500">>, <<"wait">>, + <<"internal-server-error">>, Lang, Text)). + -define(ERRT_ITEM_NOT_FOUND(Lang, Text), - ?STANZA_ERRORT("404", "cancel", "item-not-found", Lang, Text)). + ?STANZA_ERRORT(<<"404">>, <<"cancel">>, + <<"item-not-found">>, Lang, Text)). + -define(ERRT_JID_MALFORMED(Lang, Text), - ?STANZA_ERRORT("400", "modify", "jid-malformed", Lang, Text)). + ?STANZA_ERRORT(<<"400">>, <<"modify">>, + <<"jid-malformed">>, Lang, Text)). + -define(ERRT_NOT_ACCEPTABLE(Lang, Text), - ?STANZA_ERRORT("406", "modify", "not-acceptable", Lang, Text)). + ?STANZA_ERRORT(<<"406">>, <<"modify">>, + <<"not-acceptable">>, Lang, Text)). + -define(ERRT_NOT_ALLOWED(Lang, Text), - ?STANZA_ERRORT("405", "cancel", "not-allowed", Lang, Text)). + ?STANZA_ERRORT(<<"405">>, <<"cancel">>, + <<"not-allowed">>, Lang, Text)). + -define(ERRT_NOT_AUTHORIZED(Lang, Text), - ?STANZA_ERRORT("401", "auth", "not-authorized", Lang, Text)). + ?STANZA_ERRORT(<<"401">>, <<"auth">>, + <<"not-authorized">>, Lang, Text)). + -define(ERRT_PAYMENT_REQUIRED(Lang, Text), - ?STANZA_ERRORT("402", "auth", "payment-required", Lang, Text)). + ?STANZA_ERRORT(<<"402">>, <<"auth">>, + <<"payment-required">>, Lang, Text)). + -define(ERRT_RECIPIENT_UNAVAILABLE(Lang, Text), - ?STANZA_ERRORT("404", "wait", "recipient-unavailable", Lang, Text)). + ?STANZA_ERRORT(<<"404">>, <<"wait">>, + <<"recipient-unavailable">>, Lang, Text)). + -define(ERRT_REDIRECT(Lang, Text), - ?STANZA_ERRORT("302", "modify", "redirect", Lang, Text)). + ?STANZA_ERRORT(<<"302">>, <<"modify">>, <<"redirect">>, + Lang, Text)). + -define(ERRT_REGISTRATION_REQUIRED(Lang, Text), - ?STANZA_ERRORT("407", "auth", "registration-required", Lang, Text)). + ?STANZA_ERRORT(<<"407">>, <<"auth">>, + <<"registration-required">>, Lang, Text)). + -define(ERRT_REMOTE_SERVER_NOT_FOUND(Lang, Text), - ?STANZA_ERRORT("404", "cancel", "remote-server-not-found", Lang, Text)). + ?STANZA_ERRORT(<<"404">>, <<"cancel">>, + <<"remote-server-not-found">>, Lang, Text)). + -define(ERRT_REMOTE_SERVER_TIMEOUT(Lang, Text), - ?STANZA_ERRORT("504", "wait", "remote-server-timeout", Lang, Text)). + ?STANZA_ERRORT(<<"504">>, <<"wait">>, + <<"remote-server-timeout">>, Lang, Text)). + -define(ERRT_RESOURCE_CONSTRAINT(Lang, Text), - ?STANZA_ERRORT("500", "wait", "resource-constraint", Lang, Text)). + ?STANZA_ERRORT(<<"500">>, <<"wait">>, + <<"resource-constraint">>, Lang, Text)). + -define(ERRT_SERVICE_UNAVAILABLE(Lang, Text), - ?STANZA_ERRORT("503", "cancel", "service-unavailable", Lang, Text)). + ?STANZA_ERRORT(<<"503">>, <<"cancel">>, + <<"service-unavailable">>, Lang, Text)). + -define(ERRT_SUBSCRIPTION_REQUIRED(Lang, Text), - ?STANZA_ERRORT("407", "auth", "subscription-required", Lang, Text)). + ?STANZA_ERRORT(<<"407">>, <<"auth">>, + <<"subscription-required">>, Lang, Text)). + -define(ERRT_UNEXPECTED_REQUEST(Lang, Text), - ?STANZA_ERRORT("400", "wait", "unexpected-request", Lang, Text)). + ?STANZA_ERRORT(<<"400">>, <<"wait">>, + <<"unexpected-request">>, Lang, Text)). -% Auth stanza errors -define(ERR_AUTH_NO_RESOURCE_PROVIDED(Lang), - ?ERRT_NOT_ACCEPTABLE(Lang, "No resource provided")). + ?ERRT_NOT_ACCEPTABLE(Lang, <<"No resource provided">>)). + -define(ERR_AUTH_BAD_RESOURCE_FORMAT(Lang), - ?ERRT_NOT_ACCEPTABLE(Lang, "Illegal resource format")). + ?ERRT_NOT_ACCEPTABLE(Lang, + <<"Illegal resource format">>)). + -define(ERR_AUTH_RESOURCE_CONFLICT(Lang), - ?ERRT_CONFLICT(Lang, "Resource conflict")). + ?ERRT_CONFLICT(Lang, <<"Resource conflict">>)). - --define(STREAM_ERROR(Condition), - {xmlelement, "stream:error", - [], - [{xmlelement, Condition, [{"xmlns", ?NS_STREAMS}], []}]}). +-define(STREAM_ERROR(Condition, Cdata), + #xmlel{name = <<"stream:error">>, attrs = [], + children = + [#xmlel{name = Condition, + attrs = [{<<"xmlns">>, ?NS_STREAMS}], + children = [{xmlcdata, Cdata}]}]}). -define(SERR_BAD_FORMAT, - ?STREAM_ERROR("bad-format")). --define(SERR_BAD_NAMESPACE_PREFIX, - ?STREAM_ERROR("bad-namespace-prefix")). --define(SERR_CONFLICT, - ?STREAM_ERROR("conflict")). --define(SERR_CONNECTION_TIMEOUT, - ?STREAM_ERROR("connection-timeout")). --define(SERR_HOST_GONE, - ?STREAM_ERROR("host-gone")). --define(SERR_HOST_UNKNOWN, - ?STREAM_ERROR("host-unknown")). --define(SERR_IMPROPER_ADDRESSING, - ?STREAM_ERROR("improper-addressing")). --define(SERR_INTERNAL_SERVER_ERROR, - ?STREAM_ERROR("internal-server-error")). --define(SERR_INVALID_FROM, - ?STREAM_ERROR("invalid-from")). --define(SERR_INVALID_ID, - ?STREAM_ERROR("invalid-id")). --define(SERR_INVALID_NAMESPACE, - ?STREAM_ERROR("invalid-namespace")). --define(SERR_INVALID_XML, - ?STREAM_ERROR("invalid-xml")). --define(SERR_NOT_AUTHORIZED, - ?STREAM_ERROR("not-authorized")). --define(SERR_POLICY_VIOLATION, - ?STREAM_ERROR("policy-violation")). --define(SERR_REMOTE_CONNECTION_FAILED, - ?STREAM_ERROR("remote-connection-failed")). --define(SERR_RESOURSE_CONSTRAINT, - ?STREAM_ERROR("resource-constraint")). --define(SERR_RESTRICTED_XML, - ?STREAM_ERROR("restricted-xml")). -% TODO: include hostname or IP --define(SERR_SEE_OTHER_HOST, - ?STREAM_ERROR("see-other-host")). --define(SERR_SYSTEM_SHUTDOWN, - ?STREAM_ERROR("system-shutdown")). --define(SERR_UNSUPPORTED_ENCODING, - ?STREAM_ERROR("unsupported-encoding")). --define(SERR_UNSUPPORTED_STANZA_TYPE, - ?STREAM_ERROR("unsupported-stanza-type")). --define(SERR_UNSUPPORTED_VERSION, - ?STREAM_ERROR("unsupported-version")). --define(SERR_XML_NOT_WELL_FORMED, - ?STREAM_ERROR("xml-not-well-formed")). -%-define(SERR_, -% ?STREAM_ERROR("")). + ?STREAM_ERROR(<<"bad-format">>, <<"">>)). --define(STREAM_ERRORT(Condition, Lang, Text), - {xmlelement, "stream:error", - [], - [{xmlelement, Condition, [{"xmlns", ?NS_STREAMS}], []}, - {xmlelement, "text", [{"xml:lang", Lang}, {"xmlns", ?NS_STREAMS}], - [{xmlcdata, translate:translate(Lang, Text)}]}]}). +-define(SERR_BAD_NAMESPACE_PREFIX, + ?STREAM_ERROR(<<"bad-namespace-prefix">>, <<"">>)). + +-define(SERR_CONFLICT, + ?STREAM_ERROR(<<"conflict">>, <<"">>)). + +-define(SERR_CONNECTION_TIMEOUT, + ?STREAM_ERROR(<<"connection-timeout">>, <<"">>)). + +-define(SERR_HOST_GONE, + ?STREAM_ERROR(<<"host-gone">>, <<"">>)). + +-define(SERR_HOST_UNKNOWN, + ?STREAM_ERROR(<<"host-unknown">>, <<"">>)). + +-define(SERR_IMPROPER_ADDRESSING, + ?STREAM_ERROR(<<"improper-addressing">>, <<"">>)). + +-define(SERR_INTERNAL_SERVER_ERROR, + ?STREAM_ERROR(<<"internal-server-error">>, <<"">>)). + +-define(SERR_INVALID_FROM, + ?STREAM_ERROR(<<"invalid-from">>, <<"">>)). + +-define(SERR_INVALID_ID, + ?STREAM_ERROR(<<"invalid-id">>, <<"">>)). + +-define(SERR_INVALID_NAMESPACE, + ?STREAM_ERROR(<<"invalid-namespace">>, <<"">>)). + +-define(SERR_INVALID_XML, + ?STREAM_ERROR(<<"invalid-xml">>, <<"">>)). + +-define(SERR_NOT_AUTHORIZED, + ?STREAM_ERROR(<<"not-authorized">>, <<"">>)). + +-define(SERR_POLICY_VIOLATION, + ?STREAM_ERROR(<<"policy-violation">>, <<"">>)). + +-define(SERR_REMOTE_CONNECTION_FAILED, + ?STREAM_ERROR(<<"remote-connection-failed">>, <<"">>)). + +-define(SERR_RESOURSE_CONSTRAINT, + ?STREAM_ERROR(<<"resource-constraint">>, <<"">>)). + +-define(SERR_RESTRICTED_XML, + ?STREAM_ERROR(<<"restricted-xml">>, <<"">>)). + +-define(SERR_SEE_OTHER_HOST(Host), + ?STREAM_ERROR(<<"see-other-host">>, Host)). + +-define(SERR_SYSTEM_SHUTDOWN, + ?STREAM_ERROR(<<"system-shutdown">>, <<"">>)). + +-define(SERR_UNSUPPORTED_ENCODING, + ?STREAM_ERROR(<<"unsupported-encoding">>, <<"">>)). + +-define(SERR_UNSUPPORTED_STANZA_TYPE, + ?STREAM_ERROR(<<"unsupported-stanza-type">>, <<"">>)). + +-define(SERR_UNSUPPORTED_VERSION, + ?STREAM_ERROR(<<"unsupported-version">>, <<"">>)). + +-define(SERR_XML_NOT_WELL_FORMED, + ?STREAM_ERROR(<<"xml-not-well-formed">>, <<"">>)). + +%-define(SERR_, +% ?STREAM_ERROR("", "")). + +-define(STREAM_ERRORT(Condition, Cdata, Lang, Text), + #xmlel{name = <<"stream:error">>, attrs = [], + children = + [#xmlel{name = Condition, + attrs = [{<<"xmlns">>, ?NS_STREAMS}], + children = [{xmlcdata, Cdata}]}, + #xmlel{name = <<"text">>, + attrs = + [{<<"xml:lang">>, Lang}, + {<<"xmlns">>, ?NS_STREAMS}], + children = + [{xmlcdata, + translate:translate(Lang, Text)}]}]}). -define(SERRT_BAD_FORMAT(Lang, Text), - ?STREAM_ERRORT("bad-format", Lang, Text)). + ?STREAM_ERRORT(<<"bad-format">>, <<"">>, Lang, Text)). + -define(SERRT_BAD_NAMESPACE_PREFIX(Lang, Text), - ?STREAM_ERRORT("bad-namespace-prefix", Lang, Text)). + ?STREAM_ERRORT(<<"bad-namespace-prefix">>, <<"">>, Lang, + Text)). + -define(SERRT_CONFLICT(Lang, Text), - ?STREAM_ERRORT("conflict", Lang, Text)). + ?STREAM_ERRORT(<<"conflict">>, <<"">>, Lang, Text)). + -define(SERRT_CONNECTION_TIMEOUT(Lang, Text), - ?STREAM_ERRORT("connection-timeout", Lang, Text)). + ?STREAM_ERRORT(<<"connection-timeout">>, <<"">>, Lang, + Text)). + -define(SERRT_HOST_GONE(Lang, Text), - ?STREAM_ERRORT("host-gone", Lang, Text)). + ?STREAM_ERRORT(<<"host-gone">>, <<"">>, Lang, Text)). + -define(SERRT_HOST_UNKNOWN(Lang, Text), - ?STREAM_ERRORT("host-unknown", Lang, Text)). + ?STREAM_ERRORT(<<"host-unknown">>, <<"">>, Lang, Text)). + -define(SERRT_IMPROPER_ADDRESSING(Lang, Text), - ?STREAM_ERRORT("improper-addressing", Lang, Text)). + ?STREAM_ERRORT(<<"improper-addressing">>, <<"">>, Lang, + Text)). + -define(SERRT_INTERNAL_SERVER_ERROR(Lang, Text), - ?STREAM_ERRORT("internal-server-error", Lang, Text)). + ?STREAM_ERRORT(<<"internal-server-error">>, <<"">>, + Lang, Text)). + -define(SERRT_INVALID_FROM(Lang, Text), - ?STREAM_ERRORT("invalid-from", Lang, Text)). + ?STREAM_ERRORT(<<"invalid-from">>, <<"">>, Lang, Text)). + -define(SERRT_INVALID_ID(Lang, Text), - ?STREAM_ERRORT("invalid-id", Lang, Text)). + ?STREAM_ERRORT(<<"invalid-id">>, <<"">>, Lang, Text)). + -define(SERRT_INVALID_NAMESPACE(Lang, Text), - ?STREAM_ERRORT("invalid-namespace", Lang, Text)). + ?STREAM_ERRORT(<<"invalid-namespace">>, <<"">>, Lang, + Text)). + -define(SERRT_INVALID_XML(Lang, Text), - ?STREAM_ERRORT("invalid-xml", Lang, Text)). + ?STREAM_ERRORT(<<"invalid-xml">>, <<"">>, Lang, Text)). + -define(SERRT_NOT_AUTHORIZED(Lang, Text), - ?STREAM_ERRORT("not-authorized", Lang, Text)). + ?STREAM_ERRORT(<<"not-authorized">>, <<"">>, Lang, + Text)). + -define(SERRT_POLICY_VIOLATION(Lang, Text), - ?STREAM_ERRORT("policy-violation", Lang, Text)). + ?STREAM_ERRORT(<<"policy-violation">>, <<"">>, Lang, + Text)). + -define(SERRT_REMOTE_CONNECTION_FAILED(Lang, Text), - ?STREAM_ERRORT("remote-connection-failed", Lang, Text)). + ?STREAM_ERRORT(<<"remote-connection-failed">>, <<"">>, + Lang, Text)). + -define(SERRT_RESOURSE_CONSTRAINT(Lang, Text), - ?STREAM_ERRORT("resource-constraint", Lang, Text)). + ?STREAM_ERRORT(<<"resource-constraint">>, <<"">>, Lang, + Text)). + -define(SERRT_RESTRICTED_XML(Lang, Text), - ?STREAM_ERRORT("restricted-xml", Lang, Text)). -% TODO: include hostname or IP --define(SERRT_SEE_OTHER_HOST(Lang, Text), - ?STREAM_ERRORT("see-other-host", Lang, Text)). + ?STREAM_ERRORT(<<"restricted-xml">>, <<"">>, Lang, + Text)). + +-define(SERRT_SEE_OTHER_HOST(Host, Lang, Text), + ?STREAM_ERRORT(<<"see-other-host">>, Host, Lang, Text)). + -define(SERRT_SYSTEM_SHUTDOWN(Lang, Text), - ?STREAM_ERRORT("system-shutdown", Lang, Text)). + ?STREAM_ERRORT(<<"system-shutdown">>, <<"">>, Lang, + Text)). + -define(SERRT_UNSUPPORTED_ENCODING(Lang, Text), - ?STREAM_ERRORT("unsupported-encoding", Lang, Text)). + ?STREAM_ERRORT(<<"unsupported-encoding">>, <<"">>, Lang, + Text)). + -define(SERRT_UNSUPPORTED_STANZA_TYPE(Lang, Text), - ?STREAM_ERRORT("unsupported-stanza-type", Lang, Text)). + ?STREAM_ERRORT(<<"unsupported-stanza-type">>, <<"">>, + Lang, Text)). + -define(SERRT_UNSUPPORTED_VERSION(Lang, Text), - ?STREAM_ERRORT("unsupported-version", Lang, Text)). + ?STREAM_ERRORT(<<"unsupported-version">>, <<"">>, Lang, + Text)). + -define(SERRT_XML_NOT_WELL_FORMED(Lang, Text), - ?STREAM_ERRORT("xml-not-well-formed", Lang, Text)). -%-define(SERRT_(Lang, Text), -% ?STREAM_ERRORT("", Lang, Text)). + ?STREAM_ERRORT(<<"xml-not-well-formed">>, <<"">>, Lang, + Text)). +-record(jid, {user = <<"">> :: binary(), + server = <<"">> :: binary(), + resource = <<"">> :: binary(), + luser = <<"">> :: binary(), + lserver = <<"">> :: binary(), + lresource = <<"">> :: binary()}). --record(jid, {user, server, resource, - luser, lserver, lresource}). +-type(jid() :: #jid{}). --record(iq, {id = "", - type, - xmlns = "", - lang = "", - sub_el}). +-type(ljid() :: {binary(), binary(), binary()}). --record(rsm_in, {max, direction, id, index}). --record(rsm_out, {count, index, first, last}). +-record(xmlel, +{ + name = <<"">> :: binary(), + attrs = [] :: [attr()], + children = [] :: [xmlel() | cdata()] +}). + +-type(cdata() :: {xmlcdata, CData::binary()}). + +-type(attr() :: {Name::binary(), Value::binary()}). + +-type(xmlel() :: #xmlel{}). + +-record(iq, {id = <<"">> :: binary(), + type = get :: get | set | result | error, + xmlns = <<"">> :: binary(), + lang = <<"">> :: binary(), + sub_el = #xmlel{} :: xmlel() | [xmlel()]}). + +-type(iq_get() + :: #iq{ + id :: binary(), + type :: get, + xmlns :: binary(), + lang :: binary(), + sub_el :: xmlel() + } +). + +-type(iq_set() + :: #iq{ + id :: binary(), + type :: set, + xmlns :: binary(), + lang :: binary(), + sub_el :: xmlel() + } +). + +-type iq_request() :: iq_get() | iq_set(). + +-type(iq_result() + :: #iq{ + id :: binary(), + type :: result, + xmlns :: binary(), + lang :: binary(), + sub_el :: [xmlel()] + } +). + +-type(iq_error() + :: #iq{ + id :: binary(), + type :: error, + xmlns :: binary(), + lang :: binary(), + sub_el :: [xmlel()] + } +). + +-type iq_reply() :: iq_result() | iq_error() . + +-type(iq() :: iq_request() | iq_reply()). + +-record(rsm_in, {max :: integer(), + direction :: before | aft, + id :: binary(), + index :: integer()}). + +-record(rsm_out, {count :: integer(), + index :: integer(), + first :: binary(), + last :: binary()}). + +-type(rsm_in() :: #rsm_in{}). + +-type(rsm_out() :: #rsm_out{}). + +-type broadcast() :: {broadcast, broadcast_data()}. + +-type broadcast_data() :: + {rebind, pid(), binary()} | %% ejabberd_c2s + {item, ljid(), mod_roster:subscription()} | %% mod_roster/mod_shared_roster + {exit, binary()} | %% mod_roster/mod_shared_roster + {privacy_list, mod_privacy:userlist(), binary()} | %% mod_privacy + {blocking, unblock_all | {block | unblock, [ljid()]}}. %% mod_blocking + +-record(xmlelement, {name = "" :: string(), + attrs = [] :: [{string(), string()}], + children = [] :: [{xmlcdata, iodata()} | xmlelement()]}). + +-type xmlelement() :: #xmlelement{}. diff --git a/src/mod_adhoc.erl b/src/mod_adhoc.erl index 5c9039673..2eaf36840 100644 --- a/src/mod_adhoc.erl +++ b/src/mod_adhoc.erl @@ -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; - _ -> - Items = case Acc of - {result, I} -> I; - _ -> [] - end, - Nodes = [{xmlelement, - "item", - [{"jid", Server}, - {"node", ?NS_COMMANDS}, - {"name", translate:translate(Lang, "Commands")}], - []}], - {result, Items ++ Nodes} + false -> Acc; + _ -> + Items = case Acc of + {result, I} -> I; + _ -> [] + end, + 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; - _ -> - 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")}], - []}], - {result, Items ++ Nodes} + false -> Acc; + _ -> + Items = case Acc of + {result, I} -> I; + _ -> [] + end, + 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,113 +181,100 @@ 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; - _ -> [] + {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; - _ -> [] + {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} -> - IQ#iq{type = error, sub_el = [SubEl, Error]}; - #adhoc_request{} = AdhocRequest -> - Host = To#jid.lserver, - case ejabberd_hooks:run_fold(Hook, Host, empty, - [From, To, AdhocRequest]) of - ignore -> - ignore; - empty -> - 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]} - end + {error, Error} -> + IQ#iq{type = error, sub_el = [SubEl, Error]}; + #adhoc_request{} = AdhocRequest -> + Host = To#jid.lserver, + case ejabberd_hooks:run_fold(Hook, Host, empty, + [From, To, AdhocRequest]) + of + ignore -> ignore; + empty -> + 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]} + 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_response{status = completed, - notes = [{"info", translate:translate( - Lang, - "Pong")}]}); - true -> - {error, ?ERR_BAD_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} end; - -ping_command(Acc, _From, _To, _Request) -> - Acc. - +ping_command(Acc, _From, _To, _Request) -> Acc. diff --git a/src/mod_announce.erl b/src/mod_announce.erl index 7e0053462..2f6394076 100644 --- a/src/mod_announce.erl +++ b/src/mod_announce.erl @@ -35,6 +35,7 @@ -export([start/2, init/0, stop/1, + export/1, announce/3, send_motd/1, disco_identity/5, @@ -48,13 +49,17 @@ -include("jlib.hrl"). -include("adhoc.hrl"). --record(motd, {server, packet}). --record(motd_users, {us, dummy = []}). +-record(motd, {server = <<"">> :: binary(), + packet = #xmlel{} :: xmlel()}). +-record(motd_users, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1', + dummy = [] :: [] | '_'}). -define(PROCNAME, ejabberd_announce). --define(NS_ADMINL(Sub), ["http:","jabber.org","protocol","admin", Sub]). -tokenize(Node) -> string:tokens(Node, "/#"). +-define(NS_ADMINL(Sub), [<<"http:">>, <<"jabber.org">>, <<"protocol">>, + <<"admin">>, <>]). + +tokenize(Node) -> str:tokens(Node, <<"/#">>). start(Host, Opts) -> case gen_mod:db_type(Opts) of @@ -137,56 +142,52 @@ stop(Host) -> {wait, Proc}. %% Announcing via messages to a custom resource -announce(From, To, Packet) -> - case To of - #jid{luser = "", lresource = Res} -> - {xmlelement, Name, _Attrs, _Els} = Packet, - Proc = gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME), - case {Res, Name} of - {"announce/all", "message"} -> - Proc ! {announce_all, From, To, Packet}, - stop; - {"announce/all-hosts/all", "message"} -> - Proc ! {announce_all_hosts_all, From, To, Packet}, - stop; - {"announce/online", "message"} -> - Proc ! {announce_online, From, To, Packet}, - stop; - {"announce/all-hosts/online", "message"} -> - Proc ! {announce_all_hosts_online, From, To, Packet}, - stop; - {"announce/motd", "message"} -> - Proc ! {announce_motd, From, To, Packet}, - stop; - {"announce/all-hosts/motd", "message"} -> - Proc ! {announce_all_hosts_motd, From, To, Packet}, - stop; - {"announce/motd/update", "message"} -> - Proc ! {announce_motd_update, From, To, Packet}, - stop; - {"announce/all-hosts/motd/update", "message"} -> - Proc ! {announce_all_hosts_motd_update, From, To, Packet}, - stop; - {"announce/motd/delete", "message"} -> - Proc ! {announce_motd_delete, From, To, Packet}, - stop; - {"announce/all-hosts/motd/delete", "message"} -> - Proc ! {announce_all_hosts_motd_delete, From, To, Packet}, - stop; - _ -> - ok - end; - _ -> - ok - end. +announce(From, #jid{luser = <<>>} = To, #xmlel{name = <<"message">>} = Packet) -> + Proc = gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME), + case To#jid.lresource of + <<"announce/all">> -> + Proc ! {announce_all, From, To, Packet}, + stop; + <<"announce/all-hosts/all">> -> + Proc ! {announce_all_hosts_all, From, To, Packet}, + stop; + <<"announce/online">> -> + Proc ! {announce_online, From, To, Packet}, + stop; + <<"announce/all-hosts/online">> -> + Proc ! {announce_all_hosts_online, From, To, Packet}, + stop; + <<"announce/motd">> -> + Proc ! {announce_motd, From, To, Packet}, + stop; + <<"announce/all-hosts/motd">> -> + Proc ! {announce_all_hosts_motd, From, To, Packet}, + stop; + <<"announce/motd/update">> -> + Proc ! {announce_motd_update, From, To, Packet}, + stop; + <<"announce/all-hosts/motd/update">> -> + Proc ! {announce_all_hosts_motd_update, From, To, Packet}, + stop; + <<"announce/motd/delete">> -> + Proc ! {announce_motd_delete, From, To, Packet}, + stop; + <<"announce/all-hosts/motd/delete">> -> + Proc ! {announce_all_hosts_motd_delete, From, To, Packet}, + stop; + _ -> + ok + end; +announce(_From, _To, _Packet) -> + ok. %%------------------------------------------------------------------------- %% Announcing via ad-hoc commands -define(INFO_COMMAND(Lang, Node), - [{xmlelement, "identity", - [{"category", "automation"}, - {"type", "command-node"}, - {"name", get_title(Lang, Node)}], []}]). + [#xmlel{name = <<"identity">>, + attrs = [{<<"category">>, <<"automation">>}, + {<<"type">>, <<"command-node">>}, + {<<"name">>, get_title(Lang, Node)}]}]). disco_identity(Acc, _From, _To, Node, Lang) -> LNode = tokenize(Node), @@ -225,14 +226,13 @@ disco_identity(Acc, _From, _To, Node, Lang) -> {result, Feats} end). -disco_features(Acc, From, #jid{lserver = LServer} = _To, - "announce", _Lang) -> +disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, _Lang) -> case gen_mod:is_loaded(LServer, mod_adhoc) of false -> Acc; _ -> - Access1 = gen_mod:get_module_opt(LServer, ?MODULE, access, none), - Access2 = gen_mod:get_module_opt(global, ?MODULE, access, none), + Access1 = get_access(LServer), + Access2 = get_access(global), case {acl:match_rule(LServer, Access1, From), acl:match_rule(global, Access2, From)} of {deny, deny} -> @@ -242,36 +242,35 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, end end; -disco_features(Acc, From, #jid{lserver = LServer} = _To, - Node, _Lang) -> +disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) -> case gen_mod:is_loaded(LServer, mod_adhoc) of false -> Acc; _ -> - Access = gen_mod:get_module_opt(LServer, ?MODULE, access, none), + Access = get_access(LServer), Allow = acl:match_rule(LServer, Access, From), - AccessGlobal = gen_mod:get_module_opt(global, ?MODULE, access, none), + AccessGlobal = get_access(global), AllowGlobal = acl:match_rule(global, AccessGlobal, From), case Node of - ?NS_ADMIN ++ "#announce" -> + ?NS_ADMIN_ANNOUNCE -> ?INFO_RESULT(Allow, [?NS_COMMANDS]); - ?NS_ADMIN ++ "#announce-all" -> + ?NS_ADMIN_ANNOUNCE_ALL -> ?INFO_RESULT(Allow, [?NS_COMMANDS]); - ?NS_ADMIN ++ "#set-motd" -> + ?NS_ADMIN_SET_MOTD -> ?INFO_RESULT(Allow, [?NS_COMMANDS]); - ?NS_ADMIN ++ "#edit-motd" -> + ?NS_ADMIN_EDIT_MOTD -> ?INFO_RESULT(Allow, [?NS_COMMANDS]); - ?NS_ADMIN ++ "#delete-motd" -> + ?NS_ADMIN_DELETE_MOTD -> ?INFO_RESULT(Allow, [?NS_COMMANDS]); - ?NS_ADMIN ++ "#announce-allhosts" -> + ?NS_ADMIN_ANNOUNCE_ALLHOSTS -> ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]); - ?NS_ADMIN ++ "#announce-all-allhosts" -> + ?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS -> ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]); - ?NS_ADMIN ++ "#set-motd-allhosts" -> + ?NS_ADMIN_SET_MOTD_ALLHOSTS -> ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]); - ?NS_ADMIN ++ "#edit-motd-allhosts" -> + ?NS_ADMIN_EDIT_MOTD_ALLHOSTS -> ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]); - ?NS_ADMIN ++ "#delete-motd-allhosts" -> + ?NS_ADMIN_DELETE_MOTD_ALLHOSTS -> ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]); _ -> Acc @@ -279,13 +278,17 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, end. %%------------------------------------------------------------------------- - -define(NODE_TO_ITEM(Lang, Server, Node), - {xmlelement, "item", - [{"jid", Server}, - {"node", Node}, - {"name", get_title(Lang, Node)}], - []}). +( + #xmlel{ + name = <<"item">>, + attrs = [ + {<<"jid">>, Server}, + {<<"node">>, Node}, + {<<"name">>, get_title(Lang, Node)} + ] + } +)). -define(ITEMS_RESULT(Allow, Items), case Allow of @@ -295,14 +298,13 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, {result, Items} end). -disco_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, - "", Lang) -> +disco_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, <<>>, Lang) -> case gen_mod:is_loaded(LServer, mod_adhoc) of false -> Acc; _ -> - Access1 = gen_mod:get_module_opt(LServer, ?MODULE, access, none), - Access2 = gen_mod:get_module_opt(global, ?MODULE, access, none), + Access1 = get_access(LServer), + Access2 = get_access(global), case {acl:match_rule(LServer, Access1, From), acl:match_rule(global, Access2, From)} of {deny, deny} -> @@ -312,12 +314,12 @@ disco_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, {result, I} -> I; _ -> [] end, - Nodes = [?NODE_TO_ITEM(Lang, Server, "announce")], + Nodes = [?NODE_TO_ITEM(Lang, Server, <<"announce">>)], {result, Items ++ Nodes} end end; -disco_items(Acc, From, #jid{lserver = LServer} = To, "announce", Lang) -> +disco_items(Acc, From, #jid{lserver = LServer} = To, <<"announce">>, Lang) -> case gen_mod:is_loaded(LServer, mod_adhoc) of false -> Acc; @@ -330,30 +332,30 @@ disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) -> false -> Acc; _ -> - Access = gen_mod:get_module_opt(LServer, ?MODULE, access, none), + Access = get_access(LServer), Allow = acl:match_rule(LServer, Access, From), - AccessGlobal = gen_mod:get_module_opt(global, ?MODULE, access, none), + AccessGlobal = get_access(global), AllowGlobal = acl:match_rule(global, AccessGlobal, From), case Node of - ?NS_ADMIN ++ "#announce" -> + ?NS_ADMIN_ANNOUNCE -> ?ITEMS_RESULT(Allow, []); - ?NS_ADMIN ++ "#announce-all" -> + ?NS_ADMIN_ANNOUNCE_ALL -> ?ITEMS_RESULT(Allow, []); - ?NS_ADMIN ++ "#set-motd" -> + ?NS_ADMIN_SET_MOTD -> ?ITEMS_RESULT(Allow, []); - ?NS_ADMIN ++ "#edit-motd" -> + ?NS_ADMIN_EDIT_MOTD -> ?ITEMS_RESULT(Allow, []); - ?NS_ADMIN ++ "#delete-motd" -> + ?NS_ADMIN_DELETE_MOTD -> ?ITEMS_RESULT(Allow, []); - ?NS_ADMIN ++ "#announce-allhosts" -> + ?NS_ADMIN_ANNOUNCE_ALLHOSTS -> ?ITEMS_RESULT(AllowGlobal, []); - ?NS_ADMIN ++ "#announce-all-allhosts" -> + ?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS -> ?ITEMS_RESULT(AllowGlobal, []); - ?NS_ADMIN ++ "#set-motd-allhosts" -> + ?NS_ADMIN_SET_MOTD_ALLHOSTS -> ?ITEMS_RESULT(AllowGlobal, []); - ?NS_ADMIN ++ "#edit-motd-allhosts" -> + ?NS_ADMIN_EDIT_MOTD_ALLHOSTS -> ?ITEMS_RESULT(AllowGlobal, []); - ?NS_ADMIN ++ "#delete-motd-allhosts" -> + ?NS_ADMIN_DELETE_MOTD_ALLHOSTS -> ?ITEMS_RESULT(AllowGlobal, []); _ -> Acc @@ -363,25 +365,25 @@ disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) -> %%------------------------------------------------------------------------- announce_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, Lang) -> - Access1 = gen_mod:get_module_opt(LServer, ?MODULE, access, none), + Access1 = get_access(LServer), Nodes1 = case acl:match_rule(LServer, Access1, From) of allow -> - [?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#announce"), - ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#announce-all"), - ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#set-motd"), - ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#edit-motd"), - ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#delete-motd")]; + [?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_ANNOUNCE), + ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_ANNOUNCE_ALL), + ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_SET_MOTD), + ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_EDIT_MOTD), + ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_DELETE_MOTD)]; deny -> [] end, - Access2 = gen_mod:get_module_opt(global, ?MODULE, access, none), + Access2 = get_access(global), Nodes2 = case acl:match_rule(global, Access2, From) of allow -> - [?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#announce-allhosts"), - ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#announce-all-allhosts"), - ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#set-motd-allhosts"), - ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#edit-motd-allhosts"), - ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#delete-motd-allhosts")]; + [?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_ANNOUNCE_ALLHOSTS), + ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS), + ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_SET_MOTD_ALLHOSTS), + ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_EDIT_MOTD_ALLHOSTS), + ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_DELETE_MOTD_ALLHOSTS)]; deny -> [] end, @@ -411,7 +413,7 @@ announce_commands(Acc, From, #jid{lserver = LServer} = To, #adhoc_request{ node = Node} = Request) -> LNode = tokenize(Node), F = fun() -> - Access = gen_mod:get_module_opt(global, ?MODULE, access, none), + Access = get_access(global), Allow = acl:match_rule(global, Access, From), commands_result(Allow, From, To, Request) end, @@ -422,7 +424,7 @@ announce_commands(Acc, From, #jid{lserver = LServer} = To, ?NS_ADMINL("edit-motd-allhosts") -> F(); ?NS_ADMINL("delete-motd-allhosts") -> F(); _ -> - Access = gen_mod:get_module_opt(LServer, ?MODULE, access, none), + Access = get_access(LServer), Allow = acl:match_rule(LServer, Access, From), case LNode of ?NS_ADMINL("announce") -> @@ -455,19 +457,15 @@ announce_commands(From, To, %% understood as "execute". If there was no %% element in the first response (which there isn't in our %% case), "execute" and "complete" are equivalent. - ActionIsExecute = lists:member(Action, - ["", "execute", "complete"]), - if Action == "cancel" -> + ActionIsExecute = lists:member(Action, [<<>>, <<"execute">>, <<"complete">>]), + if Action == <<"cancel">> -> %% User cancels request - adhoc:produce_response(Request, - #adhoc_response{status = canceled}); + adhoc:produce_response(Request, #adhoc_response{status = canceled}); XData == false, ActionIsExecute -> %% User requests form Elements = generate_adhoc_form(Lang, Node, To#jid.lserver), - adhoc:produce_response( - Request, - #adhoc_response{status = executing, - elements = [Elements]}); + adhoc:produce_response(Request, + #adhoc_response{status = executing,elements = [Elements]}); XData /= false, ActionIsExecute -> %% User returns form. case jlib:parse_xdata_submit(XData) of @@ -481,16 +479,27 @@ announce_commands(From, To, end. -define(VVALUE(Val), - {xmlelement, "value", [], [{xmlcdata, Val}]}). +( + #xmlel{ + name = <<"value">>, + children = [{xmlcdata, Val}] + } +)). + -define(TVFIELD(Type, Var, Val), - {xmlelement, "field", [{"type", Type}, - {"var", Var}], - vvaluel(Val)}). --define(HFIELD(), ?TVFIELD("hidden", "FORM_TYPE", ?NS_ADMIN)). +( + #xmlel{ + name = <<"field">>, + attrs = [{<<"type">>, Type}, {<<"var">>, Var}], + children = vvaluel(Val) + } +)). + +-define(HFIELD(), ?TVFIELD(<<"hidden">>, <<"FORM_TYPE">>, ?NS_ADMIN)). vvaluel(Val) -> case Val of - "" -> []; + <<>> -> []; _ -> [?VVALUE(Val)] end. @@ -502,136 +511,152 @@ generate_adhoc_form(Lang, Node, ServerHost) -> true -> {[], []} end, - {xmlelement, "x", - [{"xmlns", ?NS_XDATA}, - {"type", "form"}], - [?HFIELD(), - {xmlelement, "title", [], [{xmlcdata, get_title(Lang, Node)}]}] - ++ - if (LNode == ?NS_ADMINL("delete-motd")) - or (LNode == ?NS_ADMINL("delete-motd-allhosts")) -> - [{xmlelement, "field", - [{"var", "confirm"}, - {"type", "boolean"}, - {"label", translate:translate(Lang, "Really delete message of the day?")}], - [{xmlelement, "value", - [], - [{xmlcdata, "true"}]}]}]; - true -> - [{xmlelement, "field", - [{"var", "subject"}, - {"type", "text-single"}, - {"label", translate:translate(Lang, "Subject")}], - vvaluel(OldSubject)}, - {xmlelement, "field", - [{"var", "body"}, - {"type", "text-multi"}, - {"label", translate:translate(Lang, "Message body")}], - vvaluel(OldBody)}] - end}. + #xmlel{ + name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}], + children = [ + ?HFIELD(), + #xmlel{name = <<"title">>, children = [{xmlcdata, get_title(Lang, Node)}]} + ] + ++ + if (LNode == ?NS_ADMINL("delete-motd")) + or (LNode == ?NS_ADMINL("delete-motd-allhosts")) -> + [#xmlel{ + name = <<"field">>, + attrs = [ + {<<"var">>, <<"confirm">>}, + {<<"type">>, <<"boolean">>}, + {<<"label">>, + translate:translate(Lang, <<"Really delete message of the day?">>)} + ], + children = [ + #xmlel{name = <<"value">>, children = [{xmlcdata, <<"true">>}]} + ] + } + ]; + true -> + [#xmlel{ + name = <<"field">>, + attrs = [ + {<<"var">>, <<"subject">>}, + {<<"type">>, <<"text-single">>}, + {<<"label">>, translate:translate(Lang, <<"Subject">>)}], + children = vvaluel(OldSubject) + }, + #xmlel{ + name = <<"field">>, + attrs = [ + {<<"var">>, <<"body">>}, + {<<"type">>, <<"text-multi">>}, + {<<"label">>, translate:translate(Lang, <<"Message body">>)}], + children = vvaluel(OldBody) + } + ] + + end}. join_lines([]) -> - []; + <<>>; join_lines(Lines) -> join_lines(Lines, []). join_lines([Line|Lines], Acc) -> - join_lines(Lines, ["\n",Line|Acc]); + join_lines(Lines, [<<"\n">>,Line|Acc]); join_lines([], Acc) -> %% Remove last newline - lists:flatten(lists:reverse(tl(Acc))). + iolist_to_binary(lists:reverse(tl(Acc))). handle_adhoc_form(From, #jid{lserver = LServer} = To, #adhoc_request{lang = Lang, node = Node, sessionid = SessionID}, Fields) -> - Confirm = case lists:keysearch("confirm", 1, Fields) of - {value, {"confirm", ["true"]}} -> + Confirm = case lists:keysearch(<<"confirm">>, 1, Fields) of + {value, {<<"confirm">>, [<<"true">>]}} -> true; - {value, {"confirm", ["1"]}} -> + {value, {<<"confirm">>, [<<"1">>]}} -> true; _ -> false end, - Subject = case lists:keysearch("subject", 1, Fields) of - {value, {"subject", SubjectLines}} -> + Subject = case lists:keysearch(<<"subject">>, 1, Fields) of + {value, {<<"subject">>, SubjectLines}} -> %% There really shouldn't be more than one %% subject line, but can we stop them? join_lines(SubjectLines); _ -> - [] + <<>> end, - Body = case lists:keysearch("body", 1, Fields) of - {value, {"body", BodyLines}} -> + Body = case lists:keysearch(<<"body">>, 1, Fields) of + {value, {<<"body">>, BodyLines}} -> join_lines(BodyLines); _ -> - [] + <<>> end, Response = #adhoc_response{lang = Lang, node = Node, sessionid = SessionID, status = completed}, - Packet = {xmlelement, "message", [{"type", "headline"}], - if Subject /= [] -> - [{xmlelement, "subject", [], - [{xmlcdata, Subject}]}]; - true -> - [] - end ++ - if Body /= [] -> - [{xmlelement, "body", [], - [{xmlcdata, Body}]}]; - true -> - [] - end}, - + Packet = #xmlel{ + name = <<"message">>, + attrs = [{<<"type">>, <<"headline">>}], + children = if Subject /= <<>> -> + [#xmlel{name = <<"subject">>, children = [{xmlcdata, Subject}]}]; + true -> + [] + end + ++ + if Body /= <<>> -> + [#xmlel{name = <<"body">>, children = [{xmlcdata, Body}]}]; + true -> + [] + end + }, Proc = gen_mod:get_module_proc(LServer, ?PROCNAME), case {Node, Body} of - {?NS_ADMIN ++ "#delete-motd", _} -> + {?NS_ADMIN_DELETE_MOTD, _} -> if Confirm -> Proc ! {announce_motd_delete, From, To, Packet}, adhoc:produce_response(Response); true -> adhoc:produce_response(Response) end; - {?NS_ADMIN ++ "#delete-motd-allhosts", _} -> + {?NS_ADMIN_DELETE_MOTD_ALLHOSTS, _} -> if Confirm -> Proc ! {announce_all_hosts_motd_delete, From, To, Packet}, adhoc:produce_response(Response); true -> adhoc:produce_response(Response) end; - {_, []} -> + {_, <<>>} -> %% An announce message with no body is definitely an operator error. %% Throw an error and give him/her a chance to send message again. - {error, ?ERRT_NOT_ACCEPTABLE( - Lang, - "No body provided for announce message")}; + {error, ?ERRT_NOT_ACCEPTABLE(Lang, + <<"No body provided for announce message">>)}; %% Now send the packet to ?PROCNAME. %% We don't use direct announce_* functions because it %% leads to large delay in response and queries processing - {?NS_ADMIN ++ "#announce", _} -> + {?NS_ADMIN_ANNOUNCE, _} -> Proc ! {announce_online, From, To, Packet}, adhoc:produce_response(Response); - {?NS_ADMIN ++ "#announce-allhosts", _} -> + {?NS_ADMIN_ANNOUNCE_ALLHOSTS, _} -> Proc ! {announce_all_hosts_online, From, To, Packet}, adhoc:produce_response(Response); - {?NS_ADMIN ++ "#announce-all", _} -> + {?NS_ADMIN_ANNOUNCE_ALL, _} -> Proc ! {announce_all, From, To, Packet}, adhoc:produce_response(Response); - {?NS_ADMIN ++ "#announce-all-allhosts", _} -> + {?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS, _} -> Proc ! {announce_all_hosts_all, From, To, Packet}, adhoc:produce_response(Response); - {?NS_ADMIN ++ "#set-motd", _} -> + {?NS_ADMIN_SET_MOTD, _} -> Proc ! {announce_motd, From, To, Packet}, adhoc:produce_response(Response); - {?NS_ADMIN ++ "#set-motd-allhosts", _} -> + {?NS_ADMIN_SET_MOTD_ALLHOSTS, _} -> Proc ! {announce_all_hosts_motd, From, To, Packet}, adhoc:produce_response(Response); - {?NS_ADMIN ++ "#edit-motd", _} -> + {?NS_ADMIN_EDIT_MOTD, _} -> Proc ! {announce_motd_update, From, To, Packet}, adhoc:produce_response(Response); - {?NS_ADMIN ++ "#edit-motd-allhosts", _} -> + {?NS_ADMIN_EDIT_MOTD_ALLHOSTS, _} -> Proc ! {announce_all_hosts_motd_update, From, To, Packet}, adhoc:produce_response(Response); _ -> @@ -640,65 +665,65 @@ handle_adhoc_form(From, #jid{lserver = LServer} = To, {error, ?ERR_INTERNAL_SERVER_ERROR} end. -get_title(Lang, "announce") -> - translate:translate(Lang, "Announcements"); -get_title(Lang, ?NS_ADMIN ++ "#announce-all") -> - translate:translate(Lang, "Send announcement to all users"); -get_title(Lang, ?NS_ADMIN ++ "#announce-all-allhosts") -> - translate:translate(Lang, "Send announcement to all users on all hosts"); -get_title(Lang, ?NS_ADMIN ++ "#announce") -> - translate:translate(Lang, "Send announcement to all online users"); -get_title(Lang, ?NS_ADMIN ++ "#announce-allhosts") -> - translate:translate(Lang, "Send announcement to all online users on all hosts"); -get_title(Lang, ?NS_ADMIN ++ "#set-motd") -> - translate:translate(Lang, "Set message of the day and send to online users"); -get_title(Lang, ?NS_ADMIN ++ "#set-motd-allhosts") -> - translate:translate(Lang, "Set message of the day on all hosts and send to online users"); -get_title(Lang, ?NS_ADMIN ++ "#edit-motd") -> - translate:translate(Lang, "Update message of the day (don't send)"); -get_title(Lang, ?NS_ADMIN ++ "#edit-motd-allhosts") -> - translate:translate(Lang, "Update message of the day on all hosts (don't send)"); -get_title(Lang, ?NS_ADMIN ++ "#delete-motd") -> - translate:translate(Lang, "Delete message of the day"); -get_title(Lang, ?NS_ADMIN ++ "#delete-motd-allhosts") -> - translate:translate(Lang, "Delete message of the day on all hosts"). +get_title(Lang, <<"announce">>) -> + translate:translate(Lang, <<"Announcements">>); +get_title(Lang, ?NS_ADMIN_ANNOUNCE_ALL) -> + translate:translate(Lang, <<"Send announcement to all users">>); +get_title(Lang, ?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS) -> + translate:translate(Lang, <<"Send announcement to all users on all hosts">>); +get_title(Lang, ?NS_ADMIN_ANNOUNCE) -> + translate:translate(Lang, <<"Send announcement to all online users">>); +get_title(Lang, ?NS_ADMIN_ANNOUNCE_ALLHOSTS) -> + translate:translate(Lang, <<"Send announcement to all online users on all hosts">>); +get_title(Lang, ?NS_ADMIN_SET_MOTD) -> + translate:translate(Lang, <<"Set message of the day and send to online users">>); +get_title(Lang, ?NS_ADMIN_SET_MOTD_ALLHOSTS) -> + translate:translate(Lang, <<"Set message of the day on all hosts and send to online users">>); +get_title(Lang, ?NS_ADMIN_EDIT_MOTD) -> + translate:translate(Lang, <<"Update message of the day (don't send)">>); +get_title(Lang, ?NS_ADMIN_EDIT_MOTD_ALLHOSTS) -> + translate:translate(Lang, <<"Update message of the day on all hosts (don't send)">>); +get_title(Lang, ?NS_ADMIN_DELETE_MOTD) -> + translate:translate(Lang, <<"Delete message of the day">>); +get_title(Lang, ?NS_ADMIN_DELETE_MOTD_ALLHOSTS) -> + translate:translate(Lang, <<"Delete message of the day on all hosts">>). %%------------------------------------------------------------------------- announce_all(From, To, Packet) -> Host = To#jid.lserver, - Access = gen_mod:get_module_opt(Host, ?MODULE, access, none), + Access = get_access(Host), case acl:match_rule(Host, Access, From) of deny -> Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN), ejabberd_router:route(To, From, Err); allow -> - Local = jlib:make_jid("", To#jid.server, ""), + Local = jlib:make_jid(<<>>, To#jid.server, <<>>), lists:foreach( fun({User, Server}) -> - Dest = jlib:make_jid(User, Server, ""), + Dest = jlib:make_jid(User, Server, <<>>), ejabberd_router:route(Local, Dest, Packet) end, ejabberd_auth:get_vh_registered_users(Host)) end. announce_all_hosts_all(From, To, Packet) -> - Access = gen_mod:get_module_opt(global, ?MODULE, access, none), + Access = get_access(global), case acl:match_rule(global, Access, From) of deny -> Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN), ejabberd_router:route(To, From, Err); allow -> - Local = jlib:make_jid("", To#jid.server, ""), + Local = jlib:make_jid(<<>>, To#jid.server, <<>>), lists:foreach( fun({User, Server}) -> - Dest = jlib:make_jid(User, Server, ""), + Dest = jlib:make_jid(User, Server, <<>>), ejabberd_router:route(Local, Dest, Packet) end, ejabberd_auth:dirty_get_registered_users()) end. announce_online(From, To, Packet) -> Host = To#jid.lserver, - Access = gen_mod:get_module_opt(Host, ?MODULE, access, none), + Access = get_access(Host), case acl:match_rule(Host, Access, From) of deny -> Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN), @@ -710,7 +735,7 @@ announce_online(From, To, Packet) -> end. announce_all_hosts_online(From, To, Packet) -> - Access = gen_mod:get_module_opt(global, ?MODULE, access, none), + Access = get_access(global), case acl:match_rule(global, Access, From) of deny -> Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN), @@ -722,7 +747,7 @@ announce_all_hosts_online(From, To, Packet) -> end. announce_online1(Sessions, Server, Packet) -> - Local = jlib:make_jid("", Server, ""), + Local = jlib:make_jid(<<>>, Server, <<>>), lists:foreach( fun({U, S, R}) -> Dest = jlib:make_jid(U, S, R), @@ -731,7 +756,7 @@ announce_online1(Sessions, Server, Packet) -> announce_motd(From, To, Packet) -> Host = To#jid.lserver, - Access = gen_mod:get_module_opt(Host, ?MODULE, access, none), + Access = get_access(Host), case acl:match_rule(Host, Access, From) of deny -> Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN), @@ -741,7 +766,7 @@ announce_motd(From, To, Packet) -> end. announce_all_hosts_motd(From, To, Packet) -> - Access = gen_mod:get_module_opt(global, ?MODULE, access, none), + Access = get_access(global), case acl:match_rule(global, Access, From) of deny -> Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN), @@ -771,10 +796,10 @@ announce_motd(Host, Packet) -> fun({U, _S, _R}) -> Username = ejabberd_odbc:escape(U), odbc_queries:update_t( - "motd", - ["username", "xml"], - [Username, ""], - ["username='", Username, "'"]) + <<"motd">>, + [<<"username">>, <<"xml">>], + [Username, <<"">>], + [<<"username='">>, Username, <<"'">>]) end, Sessions) end, ejabberd_odbc:sql_transaction(LServer, F) @@ -782,7 +807,7 @@ announce_motd(Host, Packet) -> announce_motd_update(From, To, Packet) -> Host = To#jid.lserver, - Access = gen_mod:get_module_opt(Host, ?MODULE, access, none), + Access = get_access(Host), case acl:match_rule(Host, Access, From) of deny -> Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN), @@ -792,7 +817,7 @@ announce_motd_update(From, To, Packet) -> end. announce_all_hosts_motd_update(From, To, Packet) -> - Access = gen_mod:get_module_opt(global, ?MODULE, access, none), + Access = get_access(global), case acl:match_rule(global, Access, From) of deny -> Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN), @@ -814,17 +839,17 @@ announce_motd_update(LServer, Packet) -> XML = ejabberd_odbc:escape(xml:element_to_binary(Packet)), F = fun() -> odbc_queries:update_t( - "motd", - ["username", "xml"], - ["", XML], - ["username=''"]) + <<"motd">>, + [<<"username">>, <<"xml">>], + [<<"">>, XML], + [<<"username=''">>]) end, ejabberd_odbc:sql_transaction(LServer, F) end. announce_motd_delete(From, To, Packet) -> Host = To#jid.lserver, - Access = gen_mod:get_module_opt(Host, ?MODULE, access, none), + Access = get_access(Host), case acl:match_rule(Host, Access, From) of deny -> Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN), @@ -834,7 +859,7 @@ announce_motd_delete(From, To, Packet) -> end. announce_all_hosts_motd_delete(From, To, Packet) -> - Access = gen_mod:get_module_opt(global, ?MODULE, access, none), + Access = get_access(global), case acl:match_rule(global, Access, From) of deny -> Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN), @@ -862,7 +887,7 @@ announce_motd_delete(LServer) -> mnesia:transaction(F); odbc -> F = fun() -> - ejabberd_odbc:sql_query_t(["delete from motd;"]) + ejabberd_odbc:sql_query_t([<<"delete from motd;">>]) end, ejabberd_odbc:sql_transaction(LServer, F) end. @@ -878,7 +903,7 @@ send_motd(#jid{luser = LUser, lserver = LServer} = JID, mnesia) -> [#motd_users{}] -> ok; _ -> - Local = jlib:make_jid("", LServer, ""), + Local = jlib:make_jid(<<>>, LServer, <<>>), ejabberd_router:route(Local, JID, Packet), F = fun() -> mnesia:write(#motd_users{us = US}) @@ -888,10 +913,10 @@ send_motd(#jid{luser = LUser, lserver = LServer} = JID, mnesia) -> _ -> ok end; -send_motd(#jid{luser = LUser, lserver = LServer} = JID, odbc) when LUser /= "" -> +send_motd(#jid{luser = LUser, lserver = LServer} = JID, odbc) when LUser /= <<>> -> case catch ejabberd_odbc:sql_query( - LServer, ["select xml from motd where username='';"]) of - {selected, ["xml"], [{XML}]} -> + LServer, [<<"select xml from motd where username='';">>]) of + {selected, [<<"xml">>], [[XML]]} -> case xml_stream:parse_element(XML) of {error, _} -> ok; @@ -899,17 +924,17 @@ send_motd(#jid{luser = LUser, lserver = LServer} = JID, odbc) when LUser /= "" - Username = ejabberd_odbc:escape(LUser), case catch ejabberd_odbc:sql_query( LServer, - ["select username from motd " - "where username='", Username, "';"]) of - {selected, ["username"], []} -> - Local = jlib:make_jid("", LServer, ""), + [<<"select username from motd " + "where username='">>, Username, <<"';">>]) of + {selected, [<<"username">>], []} -> + Local = jlib:make_jid(<<"">>, LServer, <<"">>), ejabberd_router:route(Local, JID, Packet), F = fun() -> odbc_queries:update_t( - "motd", - ["username", "xml"], - [Username, ""], - ["username='", Username, "'"]) + <<"motd">>, + [<<"username">>, <<"xml">>], + [Username, <<"">>], + [<<"username='">>, Username, <<"'">>]) end, ejabberd_odbc:sql_transaction(LServer, F); _ -> @@ -925,10 +950,10 @@ send_motd(_, odbc) -> get_stored_motd(LServer) -> case get_stored_motd_packet(LServer, gen_mod:db_type(LServer, ?MODULE)) of {ok, Packet} -> - {xml:get_subtag_cdata(Packet, "subject"), - xml:get_subtag_cdata(Packet, "body")}; + {xml:get_subtag_cdata(Packet, <<"subject">>), + xml:get_subtag_cdata(Packet, <<"body">>)}; error -> - {"", ""} + {<<>>, <<>>} end. get_stored_motd_packet(LServer, mnesia) -> @@ -940,8 +965,8 @@ get_stored_motd_packet(LServer, mnesia) -> end; get_stored_motd_packet(LServer, odbc) -> case catch ejabberd_odbc:sql_query( - LServer, ["select xml from motd where username='';"]) of - {selected, ["xml"], [{XML}]} -> + LServer, [<<"select xml from motd where username='';">>]) of + {selected, [<<"xml">>], [[XML]]} -> case xml_stream:parse_element(XML) of {error, _} -> error; @@ -954,25 +979,36 @@ get_stored_motd_packet(LServer, odbc) -> %% This function is similar to others, but doesn't perform any ACL verification send_announcement_to_all(Host, SubjectS, BodyS) -> - SubjectEls = if SubjectS /= [] -> - [{xmlelement, "subject", [], [{xmlcdata, SubjectS}]}]; - true -> - [] - end, - BodyEls = if BodyS /= [] -> - [{xmlelement, "body", [], [{xmlcdata, BodyS}]}]; - true -> - [] - end, - Packet = {xmlelement, "message", [{"type", "headline"}], SubjectEls ++ BodyEls}, + SubjectEls = if SubjectS /= <<>> -> + [#xmlel{name = <<"subject">>, children = [{xmlcdata, SubjectS}]}]; + true -> + [] + end, + BodyEls = if BodyS /= <<>> -> + [#xmlel{name = <<"body">>, children = [{xmlcdata, BodyS}]}]; + true -> + [] + end, + Packet = #xmlel{ + name = <<"message">>, + attrs = [{<<"type">>, <<"headline">>}], + children = SubjectEls ++ BodyEls + }, Sessions = ejabberd_sm:dirty_get_sessions_list(), - Local = jlib:make_jid("", Host, ""), + Local = jlib:make_jid(<<>>, Host, <<>>), lists:foreach( fun({U, S, R}) -> Dest = jlib:make_jid(U, S, R), ejabberd_router:route(Local, Dest, Packet) end, Sessions). +-spec get_access(global | binary()) -> atom(). + +get_access(Host) -> + gen_mod:get_module_opt(Host, ?MODULE, access, + fun(A) when is_atom(A) -> A end, + none). + %%------------------------------------------------------------------------- update_tables() -> @@ -983,39 +1019,14 @@ update_motd_table() -> Fields = record_info(fields, motd), case mnesia:table_info(motd, attributes) of Fields -> - ok; - [id, packet] -> - ?INFO_MSG("Converting motd table from " - "{id, packet} format", []), - Host = ?MYNAME, - {atomic, ok} = mnesia:create_table( - mod_announce_tmp_table, - [{disc_only_copies, [node()]}, - {type, bag}, - {local_content, true}, - {record_name, motd}, - {attributes, record_info(fields, motd)}]), - mnesia:transform_table(motd, ignore, Fields), - F1 = fun() -> - mnesia:write_lock_table(mod_announce_tmp_table), - mnesia:foldl( - fun(#motd{server = _} = R, _) -> - mnesia:dirty_write( - mod_announce_tmp_table, - R#motd{server = Host}) - end, ok, motd) - end, - mnesia:transaction(F1), - mnesia:clear_table(motd), - F2 = fun() -> - mnesia:write_lock_table(motd), - mnesia:foldl( - fun(R, _) -> - mnesia:dirty_write(R) - end, ok, mod_announce_tmp_table) - end, - mnesia:transaction(F2), - mnesia:delete_table(mod_announce_tmp_table); + ejabberd_config:convert_table_to_binary( + motd, Fields, set, + fun(#motd{server = S}) -> S end, + fun(#motd{server = S, packet = P} = R) -> + NewS = iolist_to_binary(S), + NewP = xml:to_xmlel(P), + R#motd{server = NewS, packet = NewP} + end); _ -> ?INFO_MSG("Recreating motd table", []), mnesia:transform_table(motd, ignore, Fields) @@ -1026,40 +1037,37 @@ update_motd_users_table() -> Fields = record_info(fields, motd_users), case mnesia:table_info(motd_users, attributes) of Fields -> - ok; - [luser, dummy] -> - ?INFO_MSG("Converting motd_users table from " - "{luser, dummy} format", []), - Host = ?MYNAME, - {atomic, ok} = mnesia:create_table( - mod_announce_tmp_table, - [{disc_only_copies, [node()]}, - {type, bag}, - {local_content, true}, - {record_name, motd_users}, - {attributes, record_info(fields, motd_users)}]), - mnesia:transform_table(motd_users, ignore, Fields), - F1 = fun() -> - mnesia:write_lock_table(mod_announce_tmp_table), - mnesia:foldl( - fun(#motd_users{us = U} = R, _) -> - mnesia:dirty_write( - mod_announce_tmp_table, - R#motd_users{us = {U, Host}}) - end, ok, motd_users) - end, - mnesia:transaction(F1), - mnesia:clear_table(motd_users), - F2 = fun() -> - mnesia:write_lock_table(motd_users), - mnesia:foldl( - fun(R, _) -> - mnesia:dirty_write(R) - end, ok, mod_announce_tmp_table) - end, - mnesia:transaction(F2), - mnesia:delete_table(mod_announce_tmp_table); + ejabberd_config:convert_table_to_binary( + motd_users, Fields, set, + fun(#motd_users{us = {U, _}}) -> U end, + fun(#motd_users{us = {U, S}} = R) -> + NewUS = {iolist_to_binary(U), + iolist_to_binary(S)}, + R#motd_users{us = NewUS} + end); _ -> ?INFO_MSG("Recreating motd_users table", []), mnesia:transform_table(motd_users, ignore, Fields) end. + +export(_Server) -> + [{motd, + 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}, + {motd_users, + 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}]. diff --git a/src/mod_blocking.erl b/src/mod_blocking.erl index db5fcdb4e..5d5f33e99 100644 --- a/src/mod_blocking.erl +++ b/src/mod_blocking.erl @@ -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,138 +63,118 @@ 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} -> - JIDs = parse_blocklist_items(Els, []), - process_blocklist_block(LUser, LServer, JIDs); - {"unblock", []} -> - process_blocklist_unblock_all(LUser, LServer); - {"unblock", Els} -> - JIDs = parse_blocklist_items(Els, []), - process_blocklist_unblock(LUser, LServer, JIDs); - _ -> - {error, ?ERR_BAD_REQUEST} - end, + 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">>, []} -> + process_blocklist_unblock_all(LUser, LServer); + {<<"unblock">>, Els} -> + JIDs = parse_blocklist_items(Els, []), + process_blocklist_unblock(LUser, LServer, JIDs); + _ -> {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} -> - Match = true; - #listitem{match_iq = true, - match_message = true, - match_presence_in = true, - match_presence_out = true} -> - Match = true; - _ -> - Match = false + #listitem{match_all = true} -> Match = true; + #listitem{match_iq = true, match_message = true, + match_presence_in = true, match_presence_out = true} -> + Match = true; + _ -> 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 - {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) +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 -> 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; - false -> - [#listitem{type = jid, - value = JID, - action = deny, - order = 0, - match_all = true} - | List1] - end - end, List, JIDs) - end, + Filter = fun (List) -> + AlreadyBlocked = list_to_blocklist_jids(List, []), + lists:foldr(fun (JID, List1) -> + case lists:member(JID, AlreadyBlocked) + of + true -> List1; + false -> + [#listitem{type = jid, + value = JID, + action = deny, + order = 0, + match_all = true} + | List1] + end + end, + List, JIDs) + end, case process_blocklist_block(LUser, LServer, Filter, - 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}), - {result, [], UserList}; - _ -> - {error, ?ERR_INTERNAL_SERVER_ERROR} + 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}), + {result, [], UserList}; + _ -> {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", - NewLists1 = [], - List = []; - [#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", - NewLists1 = Lists, - List = [] - end + [] -> + P = #privacy{us = {LUser, LServer}}, + NewDefault = <<"Blocked contacts">>, + NewLists1 = [], + List = []; + [#privacy{default = Default, lists = Lists} = P] -> + case lists:keysearch(Default, 1, Lists) of + {value, {_, List}} -> + NewDefault = Default, + NewLists1 = lists:keydelete(Default, 1, Lists); + false -> + NewDefault = <<"Blocked contacts">>, + NewLists1 = Lists, + List = [] + end end, - NewList = Filter(List), + NewList = Filter(List), NewLists = [{NewDefault, NewList} | NewLists1], mnesia:write(P#privacy{default = NewDefault, lists = NewLists}), @@ -200,205 +182,183 @@ process_blocklist_block(LUser, LServer, Filter, mnesia) -> end, mnesia:transaction(F); process_blocklist_block(LUser, LServer, Filter, odbc) -> - F = fun() -> - Default = - case mod_privacy:sql_get_default_privacy_list_t(LUser) of - {selected, ["name"], []} -> - Name = "Blocked contacts", - mod_privacy:sql_add_privacy_list(LUser, Name), - mod_privacy:sql_set_default_privacy_list( - LUser, Name), - Name; - {selected, ["name"], [{Name}]} -> - Name - end, - {selected, ["id"], [{ID}]} = - mod_privacy:sql_get_privacy_list_id_t(LUser, Default), - case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of - {selected, - ["t", "value", "action", "ord", - "match_all", "match_iq", "match_message", - "match_presence_in", - "match_presence_out"], - RItems = [_|_]} -> - List = lists:map( - fun mod_privacy:raw_to_item/1, - RItems); - _ -> - List = [] - end, - NewList = Filter(List), - NewRItems = lists:map( - fun mod_privacy:item_to_raw/1, - NewList), - mod_privacy:sql_set_privacy_list( - ID, NewRItems), - {ok, Default, NewList} - end, + F = fun () -> + Default = case + mod_privacy:sql_get_default_privacy_list_t(LUser) + of + {selected, [<<"name">>], []} -> + Name = <<"Blocked contacts">>, + mod_privacy:sql_add_privacy_list(LUser, Name), + mod_privacy:sql_set_default_privacy_list(LUser, + Name), + Name; + {selected, [<<"name">>], [[Name]]} -> Name + end, + {selected, [<<"id">>], [[ID]]} = + mod_privacy:sql_get_privacy_list_id_t(LUser, Default), + case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) + of + {selected, + [<<"t">>, <<"value">>, <<"action">>, <<"ord">>, + <<"match_all">>, <<"match_iq">>, <<"match_message">>, + <<"match_presence_in">>, <<"match_presence_out">>], + RItems = [_ | _]} -> + List = lists:map(fun mod_privacy:raw_to_item/1, RItems); + _ -> List = [] + end, + NewList = Filter(List), + NewRItems = lists:map(fun mod_privacy:item_to_raw/1, + NewList), + 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) - 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_blocklist_event(LUser, LServer, unblock_all), - {result, [], UserList}; - _ -> - {error, ?ERR_INTERNAL_SERVER_ERROR} + Filter = fun (List) -> + lists:filter(fun (#listitem{action = A}) -> A =/= deny + end, + 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_blocklist_event(LUser, LServer, unblock_all), + {result, [], UserList}; + _ -> {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] -> - 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 - ok - end + [] -> + % No lists, nothing to unblock + ok; + [#privacy{default = Default, lists = Lists} = P] -> + case lists:keysearch(Default, 1, Lists) of + {value, {_, List}} -> + 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 + ok + end end end, mnesia:transaction(F); -process_blocklist_unblock_all(LUser, LServer, Filter, odbc) -> - F = fun() -> - case mod_privacy:sql_get_default_privacy_list_t(LUser) of - {selected, ["name"], []} -> - ok; - {selected, ["name"], [{Default}]} -> - {selected, ["id"], [{ID}]} = - mod_privacy:sql_get_privacy_list_id_t( - LUser, Default), - case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of - {selected, - ["t", "value", "action", "ord", - "match_all", "match_iq", "match_message", - "match_presence_in", - "match_presence_out"], - RItems = [_|_]} -> - List = lists:map( - fun mod_privacy:raw_to_item/1, - RItems), - NewList = Filter(List), - NewRItems = lists:map( - fun mod_privacy:item_to_raw/1, - NewList), - mod_privacy:sql_set_privacy_list( - ID, NewRItems), - {ok, Default, NewList}; - _ -> - ok - end; - _ -> - ok - end - end, +process_blocklist_unblock_all(LUser, LServer, Filter, + odbc) -> + F = fun () -> + case mod_privacy:sql_get_default_privacy_list_t(LUser) + of + {selected, [<<"name">>], []} -> ok; + {selected, [<<"name">>], [[Default]]} -> + {selected, [<<"id">>], [[ID]]} = + mod_privacy:sql_get_privacy_list_id_t(LUser, Default), + case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) + of + {selected, + [<<"t">>, <<"value">>, <<"action">>, <<"ord">>, + <<"match_all">>, <<"match_iq">>, <<"match_message">>, + <<"match_presence_in">>, <<"match_presence_out">>], + RItems = [_ | _]} -> + List = lists:map(fun mod_privacy:raw_to_item/1, + RItems), + NewList = Filter(List), + NewRItems = lists:map(fun mod_privacy:item_to_raw/1, + NewList), + mod_privacy:sql_set_privacy_list(ID, NewRItems), + {ok, Default, NewList}; + _ -> ok + end; + _ -> 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, - value = JID}) -> - not(lists:member(JID, JIDs)); - (_) -> - true - end, List) - end, + Filter = fun (List) -> + lists:filter(fun (#listitem{action = deny, type = jid, + value = JID}) -> + 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, []}; - {atomic, {ok, Default, List}} -> - UserList = make_userlist(Default, List), - broadcast_list_update(LUser, LServer, Default, UserList), - broadcast_blocklist_event(LUser, LServer, {unblock, JIDs}), - {result, [], UserList}; - _ -> - {error, ?ERR_INTERNAL_SERVER_ERROR} + 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}), + {result, [], UserList}; + _ -> {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] -> - 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 - ok - end + [] -> + % No lists, nothing to unblock + ok; + [#privacy{default = Default, lists = Lists} = P] -> + case lists:keysearch(Default, 1, Lists) of + {value, {_, List}} -> + 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 + ok + end end end, mnesia:transaction(F); -process_blocklist_unblock(LUser, LServer, Filter, odbc) -> - F = fun() -> - case mod_privacy:sql_get_default_privacy_list_t(LUser) of - {selected, ["name"], []} -> - ok; - {selected, ["name"], [{Default}]} -> - {selected, ["id"], [{ID}]} = - mod_privacy:sql_get_privacy_list_id_t( - LUser, Default), - case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of - {selected, - ["t", "value", "action", "ord", - "match_all", "match_iq", "match_message", - "match_presence_in", - "match_presence_out"], - RItems = [_|_]} -> - List = lists:map( - fun mod_privacy:raw_to_item/1, - RItems), - NewList = Filter(List), - NewRItems = lists:map( - fun mod_privacy:item_to_raw/1, - NewList), - mod_privacy:sql_set_privacy_list( - ID, NewRItems), - {ok, Default, NewList}; - _ -> - ok - end; - _ -> - ok - end - end, +process_blocklist_unblock(LUser, LServer, Filter, + odbc) -> + F = fun () -> + case mod_privacy:sql_get_default_privacy_list_t(LUser) + of + {selected, [<<"name">>], []} -> ok; + {selected, [<<"name">>], [[Default]]} -> + {selected, [<<"id">>], [[ID]]} = + mod_privacy:sql_get_privacy_list_id_t(LUser, Default), + case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) + of + {selected, + [<<"t">>, <<"value">>, <<"action">>, <<"ord">>, + <<"match_all">>, <<"match_iq">>, <<"match_message">>, + <<"match_presence_in">>, <<"match_presence_out">>], + RItems = [_ | _]} -> + List = lists:map(fun mod_privacy:raw_to_item/1, + RItems), + NewList = Filter(List), + NewRItems = lists:map(fun mod_privacy:item_to_raw/1, + NewList), + mod_privacy:sql_set_privacy_list(ID, NewRItems), + {ok, Default, NewList}; + _ -> ok + end; + _ -> ok + end + end, ejabberd_odbc:sql_transaction(LServer, F). make_userlist(Name, List) -> @@ -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}; - List -> - JIDs = list_to_blocklist_jids(List, []), - Items = lists:map( - fun(JID) -> - ?DEBUG("JID: ~p",[JID]), - {xmlelement, "item", - [{"jid", jlib:jid_to_string(JID)}], []} - end, JIDs), - {result, - [{xmlelement, "blocklist", [{"xmlns", ?NS_BLOCKING}], - Items}]} + case process_blocklist_get(LUser, LServer, + gen_mod:db_type(LServer, mod_privacy)) + of + error -> {error, ?ERR_INTERNAL_SERVER_ERROR}; + List -> + JIDs = list_to_blocklist_jids(List, []), + Items = lists:map(fun (JID) -> + ?DEBUG("JID: ~p", [JID]), + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, + jlib:jid_to_string(JID)}], + children = []} + end, + JIDs), + {result, + [#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; - [] -> - []; - [#privacy{default = Default, lists = Lists}] -> - case lists:keysearch(Default, 1, Lists) of - {value, {_, List}} -> - List; - _ -> - [] - end + 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; + _ -> [] + 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"], - RItems} -> - lists:map(fun mod_privacy:raw_to_item/1, RItems); - {'EXIT', _} -> - error - end; - {'EXIT', _} -> - error + 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 + end; + {'EXIT', _} -> error end. diff --git a/src/mod_caps.erl b/src/mod_caps.erl index 6641e956f..4bec5f29e 100644 --- a/src/mod_caps.erl +++ b/src/mod_caps.erl @@ -27,30 +27,23 @@ %%%---------------------------------------------------------------------- -module(mod_caps). + -author('henoch@dtek.chalmers.se'). -behaviour(gen_server). + -behaviour(gen_mod). --export([read_caps/1, - caps_stream_features/2, - disco_features/5, - disco_identity/5, - disco_info/5, +-export([read_caps/1, caps_stream_features/2, + disco_features/5, disco_identity/5, disco_info/5, get_features/1]). %% gen_mod callbacks --export([start/2, start_link/2, - stop/1]). +-export([start/2, start_link/2, stop/1]). %% gen_server callbacks --export([init/1, - handle_info/2, - handle_call/3, - handle_cast/2, - terminate/2, - code_change/3 - ]). +-export([init/1, handle_info/2, handle_call/3, + handle_cast/2, terminate/2, code_change/3]). %% hook handlers -export([user_send_packet/3, @@ -59,32 +52,45 @@ c2s_broadcast_recipients/5]). -include("ejabberd.hrl"). + -include("jlib.hrl"). -define(PROCNAME, ejabberd_mod_caps). --define(BAD_HASH_LIFETIME, 600). %% in seconds --record(caps, {node, version, hash, exts}). --record(caps_features, {node_pair, features = []}). +-define(BAD_HASH_LIFETIME, 600). --record(state, {host}). +-record(caps, +{ + node = <<"">> :: binary(), + version = <<"">> :: binary(), + hash = <<"">> :: binary(), + exts = [] :: [binary()] +}). + +-type caps() :: #caps{}. + +-export_type([caps/0]). + +-record(caps_features, +{ + node_pair = {<<"">>, <<"">>} :: {binary(), binary()}, + features = [] :: [binary()] | pos_integer() +}). + +-record(state, {host = <<"">> :: binary()}). %%==================================================================== %% API %%==================================================================== start_link(Host, Opts) -> Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). + gen_server:start_link({local, Proc}, ?MODULE, + [Host, Opts], []). start(Host, Opts) -> Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - ChildSpec = - {Proc, - {?MODULE, start_link, [Host, Opts]}, - transient, - 1000, - worker, - [?MODULE]}, + ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]}, + transient, 1000, worker, [?MODULE]}, supervisor:start_child(ejabberd_sup, ChildSpec). stop(Host) -> @@ -96,164 +102,170 @@ stop(Host) -> %% get_features returns a list of features implied by the given caps %% record (as extracted by read_caps) or 'unknown' if features are %% not completely collected at the moment. -get_features(nothing) -> - []; +get_features(nothing) -> []; get_features(#caps{node = Node, version = Version, exts = Exts}) -> SubNodes = [Version | Exts], - lists:foldl( - fun(SubNode, Acc) -> - BinaryNode = node_to_binary(Node, SubNode), - case cache_tab:lookup(caps_features, BinaryNode, - caps_read_fun(BinaryNode)) of - {ok, Features} when is_list(Features) -> - binary_to_features(Features) ++ Acc; - _ -> - Acc - end - end, [], SubNodes). - %% read_caps takes a list of XML elements (the child elements of a %% 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 - ?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) +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 = 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" -> - case read_caps(Els) of - nothing -> - ok; - #caps{version = Version, exts = Exts} = Caps -> - feature_request(Server, From, Caps, [Version | Exts]) - end; - true -> - ok +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; + #caps{version = Version, exts = Exts} = Caps -> + feature_request(Server, From, Caps, [Version | Exts]) + end; + 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" -> - case read_caps(Els) of - nothing -> - ok; - #caps{version = Version, exts = Exts} = Caps -> - feature_request(Server, From, Caps, [Version | Exts]) - end; - true -> - ok + #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; + #caps{version = Version, exts = Exts} = Caps -> + feature_request(Server, From, Caps, [Version | Exts]) + end; + true -> ok end; user_receive_packet(_JID, _From, _To, _Packet) -> ok. caps_stream_features(Acc, MyHost) -> case make_my_disco_hash(MyHost) of - "" -> - Acc; - Hash -> - [{xmlelement, "c", [{"xmlns", ?NS_CAPS}, - {"hash", "sha-1"}, - {"node", ?EJABBERD_URI}, - {"ver", Hash}], []} | Acc] + <<"">> -> Acc; + Hash -> + [#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) -> - ejabberd_hooks:run_fold(disco_local_features, - To#jid.lserver, - empty, - [From, To, "", Lang]); -disco_features(Acc, _From, _To, _Node, _Lang) -> - Acc. +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]); + false -> + Acc + end. -disco_identity(_Acc, From, To, ?EJABBERD_URI ++ "#" ++ [_|_], Lang) -> - ejabberd_hooks:run_fold(disco_local_identity, - To#jid.lserver, - [], - [From, To, "", Lang]); -disco_identity(Acc, _From, _To, _Node, _Lang) -> - Acc. +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]); + 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")) - and ((Subscription == both) or (Subscription == to)), - Delete = (Type == "unavailable") or (Type == "error") or (Type == "invisible"), +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">>), 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() - end, - Caps = read_caps(Els), - {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}; - none -> - {true, gb_trees:insert(LFrom, Caps, Rs)}; - _ -> - {true, gb_trees:update(LFrom, Caps, Rs)} - end; - _ -> - {false, gb_trees:delete_any(LFrom, Rs)} + LFrom = jlib:jid_tolower(From), + Rs = case ejabberd_c2s:get_aux_field(caps_resources, + C2SState) + of + {ok, Rs1} -> Rs1; + error -> gb_trees:empty() end, - if CapsUpdated -> - ejabberd_hooks:run(caps_update, To#jid.lserver, - [From, To, get_features(Caps)]); - true -> - ok - end, - ejabberd_c2s:set_aux_field(caps_resources, NewRs, C2SState); - true -> - C2SState + Caps = read_caps(Els), + {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}; + none -> + {true, + gb_trees:insert(LFrom, Caps, + Rs)}; + _ -> + {true, + gb_trees:update(LFrom, Caps, Rs)} + end; + _ -> {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 + end, + 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 - {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 - end, - if NeedRequest -> - IQ = #iq{type = get, - xmlns = ?NS_DISCO_INFO, - sub_el = [{xmlelement, "query", - [{"xmlns", ?NS_DISCO_INFO}, - {"node", Node ++ "#" ++ SubNode}], - []}]}, - %% We cache current timestamp in order to avoid - %% caps requests flood - cache_tab:insert(caps_features, BinaryNode, now_ts(), - caps_write_fun(BinaryNode, now_ts())), - F = fun(IQReply) -> - feature_response( - IQReply, Host, From, Caps, SubNodes) + 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 end, - ejabberd_local:route_iq( - jlib:make_jid("", Host, ""), From, IQ, F); - true -> - feature_request(Host, From, Caps, Tail) - end + if NeedRequest -> + IQ = #iq{type = get, xmlns = ?NS_DISCO_INFO, + sub_el = + [#xmlel{name = <<"query">>, + attrs = + [{<<"xmlns">>, ?NS_DISCO_INFO}, + {<<"node">>, + <>}], + children = []}]}, + cache_tab:insert(caps_features, NodePair, now_ts(), + caps_write_fun(NodePair, now_ts())), + F = fun (IQReply) -> + feature_response(IQReply, Host, From, Caps, + SubNodes) + end, + ejabberd_local:route_iq(jlib:make_jid(<<"">>, Host, + <<"">>), + From, IQ, F); + true -> feature_request(Host, From, Caps, Tail) + 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 + true -> + Features = lists:flatmap(fun (#xmlel{name = + <<"feature">>, + attrs = FAttrs}) -> + [xml:get_attr_s(<<"var">>, FAttrs)]; + (_) -> [] + end, + Els), + cache_tab:insert(caps_features, NodePair, + Features, + caps_write_fun(NodePair, Features)); + false -> ok end, feature_request(Host, From, Caps, SubNodes); -feature_response(_IQResult, Host, From, Caps, [_SubNode | SubNodes]) -> - %% We got type=error or invalid type=result stanza or timeout. +feature_response(_IQResult, Host, From, Caps, + [_SubNode | SubNodes]) -> feature_request(Host, From, Caps, SubNodes). -node_to_binary(Node, SubNode) -> - {list_to_binary(Node), list_to_binary(SubNode)}. - -features_to_binary(L) -> [list_to_binary(I) || I <- L]. -binary_to_features(L) -> [binary_to_list(I) || I <- L]. - caps_read_fun(Node) -> - fun() -> + fun () -> case mnesia:dirty_read({caps_features, Node}) of - [#caps_features{features = Features}] -> - {ok, Features}; - _ -> - error + [#caps_features{features = Features}] -> {ok, Features}; + _ -> error end end. caps_write_fun(Node, Features) -> - fun() -> - mnesia:dirty_write( - #caps_features{node_pair = Node, - features = Features}) + fun () -> + mnesia:dirty_write(#caps_features{node_pair = Node, + features = Features}) end. make_my_disco_hash(Host) -> - JID = jlib:make_jid("", Host, ""), + JID = jlib:make_jid(<<"">>, Host, <<"">>), case {ejabberd_hooks:run_fold(disco_local_features, - Host, - empty, - [JID, JID, "", ""]), - ejabberd_hooks:run_fold(disco_local_identity, - Host, - [], - [JID, JID, "", ""]), - ejabberd_hooks:run_fold(disco_info, - Host, - [], - [Host, undefined, "", ""])} of - {{result, Features}, Identities, Info} -> - Feats = lists:map( - fun({{Feat, _Host}}) -> - {xmlelement, "feature", [{"var", Feat}], []}; - (Feat) -> - {xmlelement, "feature", [{"var", Feat}], []} - end, Features), - make_disco_hash(Identities ++ Info ++ Feats, sha1); - _Err -> - "" + 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}}) -> + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, Feat}], + children = []}; + (Feat) -> + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, Feat}], + children = []} + end, + Features), + make_disco_hash(Identities ++ Info ++ Feats, sha1); + _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) - end). + 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" -> - Caps#caps.version == make_disco_hash(Els, md2); - "md5" -> - Caps#caps.version == make_disco_hash(Els, md5); - "sha-1" -> - Caps#caps.version == make_disco_hash(Els, sha1); - "sha-224" -> - Caps#caps.version == make_disco_hash(Els, sha224); - "sha-256" -> - Caps#caps.version == make_disco_hash(Els, sha256); - "sha-384" -> - Caps#caps.version == make_disco_hash(Els, sha384); - "sha-512" -> - Caps#caps.version == make_disco_hash(Els, sha512); - _ -> - true + <<"md2">> -> + Caps#caps.version == make_disco_hash(Els, md2); + <<"md5">> -> + Caps#caps.version == make_disco_hash(Els, md5); + <<"sha-1">> -> + Caps#caps.version == make_disco_hash(Els, sha1); + <<"sha-224">> -> + Caps#caps.version == make_disco_hash(Els, sha224); + <<"sha-256">> -> + Caps#caps.version == make_disco_hash(Els, sha256); + <<"sha-384">> -> + Caps#caps.version == make_disco_hash(Els, sha384); + <<"sha-512">> -> + Caps#caps.version == make_disco_hash(Els, sha512); + _ -> 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) - end). + 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" -> - Caps#caps.version == make_disco_hash(Els, md5); - "sha-1" -> - Caps#caps.version == make_disco_hash(Els, sha1); - "sha-224" -> - Caps#caps.version == make_disco_hash(Els, sha224); - "sha-256" -> - Caps#caps.version == make_disco_hash(Els, sha256); - "sha-384" -> - Caps#caps.version == make_disco_hash(Els, sha384); - "sha-512" -> - Caps#caps.version == make_disco_hash(Els, sha512); - _ -> - true + <<"md5">> -> + Caps#caps.version == make_disco_hash(Els, md5); + <<"sha-1">> -> + Caps#caps.version == make_disco_hash(Els, sha1); + <<"sha-224">> -> + Caps#caps.version == make_disco_hash(Els, sha224); + <<"sha-256">> -> + Caps#caps.version == make_disco_hash(Els, sha256); + <<"sha-384">> -> + Caps#caps.version == make_disco_hash(Els, sha384); + <<"sha-512">> -> + Caps#caps.version == make_disco_hash(Els, sha512); + _ -> 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"} -> - [concat_xdata_fields(Fields)]; - _ -> - [] - end; - (_) -> - [] - end, Els)). + 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)). concat_xdata_fields(Fields) -> - [Form, Res] = - lists:foldl( - fun({xmlelement, "field", Attrs, Els} = El, - [FormType, VarFields] = Acc) -> - 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), $<]]; - (_) -> - [] - end, Els))] | VarFields]] - end; - (_, Acc) -> - Acc - end, ["", []], Fields), + [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]; + Var -> + [FormType, + [[[Var, $<], + lists:sort(lists:flatmap(fun + (#xmlel{name + = + <<"value">>, + children + = + VEls}) -> + [[xml:get_cdata(VEls), + $<]]; + (_) -> + [] + end, + Els))] + | VarFields]] + end; + (_, Acc) -> Acc + end, + [<<"">>, []], Fields), [Form, $<, lists:sort(Res)]. gb_trees_fold(F, Acc, Tree) -> @@ -613,13 +607,19 @@ gb_trees_fold(F, Acc, Tree) -> gb_trees_fold_iter(F, Acc, Iter) -> case gb_trees:next(Iter) of - {Key, Val, NewIter} -> - NewAcc = F(Key, Val, Acc), - gb_trees_fold_iter(F, NewAcc, NewIter); - _ -> - Acc + {Key, Val, NewIter} -> + NewAcc = F(Key, Val, Acc), + gb_trees_fold_iter(F, NewAcc, NewIter); + _ -> 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. diff --git a/src/mod_configure.erl b/src/mod_configure.erl index 091ea6071..3d2b7d267 100644 --- a/src/mod_configure.erl +++ b/src/mod_configure.erl @@ -28,25 +28,21 @@ %%% (2005-08-19) -module(mod_configure). + -author('alexey@process-one.net'). -behaviour(gen_mod). --export([start/2, - stop/1, - get_local_identity/5, - get_local_features/5, - get_local_items/5, - adhoc_local_items/4, - adhoc_local_commands/4, - get_sm_identity/5, - get_sm_features/5, - get_sm_items/5, - adhoc_sm_items/4, - adhoc_sm_commands/4]). +-export([start/2, stop/1, get_local_identity/5, + get_local_features/5, get_local_items/5, + adhoc_local_items/4, adhoc_local_commands/4, + get_sm_identity/5, get_sm_features/5, get_sm_items/5, + adhoc_sm_items/4, adhoc_sm_commands/4]). -include("ejabberd.hrl"). + -include("jlib.hrl"). + -include("adhoc.hrl"). -define(T(Lang, Text), translate:translate(Lang, Text)). @@ -55,437 +51,466 @@ -record(session, {sid, usr, us, priority, info}). start(Host, _Opts) -> - ejabberd_hooks:add(disco_local_items, Host, ?MODULE, get_local_items, 50), - ejabberd_hooks:add(disco_local_features, Host, ?MODULE, get_local_features, 50), - ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, get_local_identity, 50), - ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, get_sm_items, 50), - ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 50), - ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, get_sm_identity, 50), - ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE, adhoc_local_items, 50), - ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE, adhoc_local_commands, 50), - ejabberd_hooks:add(adhoc_sm_items, Host, ?MODULE, adhoc_sm_items, 50), - ejabberd_hooks:add(adhoc_sm_commands, Host, ?MODULE, adhoc_sm_commands, 50), + ejabberd_hooks:add(disco_local_items, Host, ?MODULE, + get_local_items, 50), + ejabberd_hooks:add(disco_local_features, Host, ?MODULE, + get_local_features, 50), + ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, + get_local_identity, 50), + ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, + get_sm_items, 50), + ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, + get_sm_features, 50), + ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, + get_sm_identity, 50), + ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE, + adhoc_local_items, 50), + ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE, + adhoc_local_commands, 50), + ejabberd_hooks:add(adhoc_sm_items, Host, ?MODULE, + adhoc_sm_items, 50), + ejabberd_hooks:add(adhoc_sm_commands, Host, ?MODULE, + adhoc_sm_commands, 50), ok. stop(Host) -> - ejabberd_hooks:delete(adhoc_sm_commands, Host, ?MODULE, adhoc_sm_commands, 50), - ejabberd_hooks:delete(adhoc_sm_items, Host, ?MODULE, adhoc_sm_items, 50), - ejabberd_hooks:delete(adhoc_local_commands, Host, ?MODULE, adhoc_local_commands, 50), - ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE, adhoc_local_items, 50), - ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, get_sm_identity, 50), - ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50), - ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, get_sm_items, 50), - ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, get_local_identity, 50), - ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_local_features, 50), - ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, get_local_items, 50), - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS), - gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_COMMANDS). + ejabberd_hooks:delete(adhoc_sm_commands, Host, ?MODULE, + adhoc_sm_commands, 50), + ejabberd_hooks:delete(adhoc_sm_items, Host, ?MODULE, + adhoc_sm_items, 50), + ejabberd_hooks:delete(adhoc_local_commands, Host, + ?MODULE, adhoc_local_commands, 50), + ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE, + adhoc_local_items, 50), + ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, + get_sm_identity, 50), + ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, + get_sm_features, 50), + ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, + get_sm_items, 50), + ejabberd_hooks:delete(disco_local_identity, Host, + ?MODULE, get_local_identity, 50), + ejabberd_hooks:delete(disco_local_features, Host, + ?MODULE, get_local_features, 50), + ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, + get_local_items, 50), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, + ?NS_COMMANDS), + gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, + ?NS_COMMANDS). %%%----------------------------------------------------------------------- -define(INFO_IDENTITY(Category, Type, Name, Lang), - [{xmlelement, "identity", - [{"category", Category}, - {"type", Type}, - {"name", ?T(Lang, Name)}], []}]). + [#xmlel{name = <<"identity">>, + attrs = + [{<<"category">>, Category}, {<<"type">>, Type}, + {<<"name">>, ?T(Lang, Name)}], + children = []}]). -define(INFO_COMMAND(Name, Lang), - ?INFO_IDENTITY("automation", "command-node", Name, Lang)). + ?INFO_IDENTITY(<<"automation">>, <<"command-node">>, + Name, Lang)). -define(NODEJID(To, Name, Node), - {xmlelement, "item", - [{"jid", jlib:jid_to_string(To)}, - {"name", ?T(Lang, Name)}, - {"node", Node}], []}). + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, jlib:jid_to_string(To)}, + {<<"name">>, ?T(Lang, Name)}, {<<"node">>, Node}], + children = []}). -define(NODE(Name, Node), - {xmlelement, "item", - [{"jid", Server}, - {"name", ?T(Lang, Name)}, - {"node", Node}], []}). + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, Server}, {<<"name">>, ?T(Lang, Name)}, + {<<"node">>, Node}], + children = []}). --define(NS_ADMINX(Sub), ?NS_ADMIN++"#"++Sub). --define(NS_ADMINL(Sub), ["http:","jabber.org","protocol","admin", Sub]). -tokenize(Node) -> string:tokens(Node, "/#"). +-define(NS_ADMINX(Sub), + <<(?NS_ADMIN)/binary, "#", Sub/binary>>). + +-define(NS_ADMINL(Sub), + [<<"http:">>, <<"jabber.org">>, <<"protocol">>, + <<"admin">>, Sub]). + +tokenize(Node) -> str:tokens(Node, <<"/#">>). get_sm_identity(Acc, _From, _To, Node, Lang) -> case Node of - "config" -> - ?INFO_COMMAND("Configuration", Lang); - _ -> - Acc + <<"config">> -> + ?INFO_COMMAND(<<"Configuration">>, Lang); + _ -> Acc end. get_local_identity(Acc, _From, _To, Node, Lang) -> LNode = tokenize(Node), case LNode of - ["running nodes", ENode] -> - ?INFO_IDENTITY("ejabberd", "node", ENode, Lang); - ["running nodes", _ENode, "DB"] -> - ?INFO_COMMAND("Database", Lang); - ["running nodes", _ENode, "modules", "start"] -> - ?INFO_COMMAND("Start Modules", Lang); - ["running nodes", _ENode, "modules", "stop"] -> - ?INFO_COMMAND("Stop Modules", Lang); - ["running nodes", _ENode, "backup", "backup"] -> - ?INFO_COMMAND("Backup", Lang); - ["running nodes", _ENode, "backup", "restore"] -> - ?INFO_COMMAND("Restore", Lang); - ["running nodes", _ENode, "backup", "textfile"] -> - ?INFO_COMMAND("Dump to Text File", Lang); - ["running nodes", _ENode, "import", "file"] -> - ?INFO_COMMAND("Import File", Lang); - ["running nodes", _ENode, "import", "dir"] -> - ?INFO_COMMAND("Import Directory", Lang); - ["running nodes", _ENode, "restart"] -> - ?INFO_COMMAND("Restart Service", Lang); - ["running nodes", _ENode, "shutdown"] -> - ?INFO_COMMAND("Shut Down Service", Lang); - ?NS_ADMINL("add-user") -> - ?INFO_COMMAND("Add User", Lang); - ?NS_ADMINL("delete-user") -> - ?INFO_COMMAND("Delete User", Lang); - ?NS_ADMINL("end-user-session") -> - ?INFO_COMMAND("End User Session", Lang); - ?NS_ADMINL("get-user-password") -> - ?INFO_COMMAND("Get User Password", Lang); - ?NS_ADMINL("change-user-password") -> - ?INFO_COMMAND("Change User Password", Lang); - ?NS_ADMINL("get-user-lastlogin") -> - ?INFO_COMMAND("Get User Last Login Time", Lang); - ?NS_ADMINL("user-stats") -> - ?INFO_COMMAND("Get User Statistics", Lang); - ?NS_ADMINL("get-registered-users-num") -> - ?INFO_COMMAND("Get Number of Registered Users", Lang); - ?NS_ADMINL("get-online-users-num") -> - ?INFO_COMMAND("Get Number of Online Users", Lang); - ["config", "acls"] -> - ?INFO_COMMAND("Access Control Lists", Lang); - ["config", "access"] -> - ?INFO_COMMAND("Access Rules", Lang); - _ -> - Acc + [<<"running nodes">>, ENode] -> + ?INFO_IDENTITY(<<"ejabberd">>, <<"node">>, ENode, Lang); + [<<"running nodes">>, _ENode, <<"DB">>] -> + ?INFO_COMMAND(<<"Database">>, Lang); + [<<"running nodes">>, _ENode, <<"modules">>, + <<"start">>] -> + ?INFO_COMMAND(<<"Start Modules">>, Lang); + [<<"running nodes">>, _ENode, <<"modules">>, + <<"stop">>] -> + ?INFO_COMMAND(<<"Stop Modules">>, Lang); + [<<"running nodes">>, _ENode, <<"backup">>, + <<"backup">>] -> + ?INFO_COMMAND(<<"Backup">>, Lang); + [<<"running nodes">>, _ENode, <<"backup">>, + <<"restore">>] -> + ?INFO_COMMAND(<<"Restore">>, Lang); + [<<"running nodes">>, _ENode, <<"backup">>, + <<"textfile">>] -> + ?INFO_COMMAND(<<"Dump to Text File">>, Lang); + [<<"running nodes">>, _ENode, <<"import">>, + <<"file">>] -> + ?INFO_COMMAND(<<"Import File">>, Lang); + [<<"running nodes">>, _ENode, <<"import">>, + <<"dir">>] -> + ?INFO_COMMAND(<<"Import Directory">>, Lang); + [<<"running nodes">>, _ENode, <<"restart">>] -> + ?INFO_COMMAND(<<"Restart Service">>, Lang); + [<<"running nodes">>, _ENode, <<"shutdown">>] -> + ?INFO_COMMAND(<<"Shut Down Service">>, Lang); + ?NS_ADMINL(<<"add-user">>) -> + ?INFO_COMMAND(<<"Add User">>, Lang); + ?NS_ADMINL(<<"delete-user">>) -> + ?INFO_COMMAND(<<"Delete User">>, Lang); + ?NS_ADMINL(<<"end-user-session">>) -> + ?INFO_COMMAND(<<"End User Session">>, Lang); + ?NS_ADMINL(<<"get-user-password">>) -> + ?INFO_COMMAND(<<"Get User Password">>, Lang); + ?NS_ADMINL(<<"change-user-password">>) -> + ?INFO_COMMAND(<<"Change User Password">>, Lang); + ?NS_ADMINL(<<"get-user-lastlogin">>) -> + ?INFO_COMMAND(<<"Get User Last Login Time">>, Lang); + ?NS_ADMINL(<<"user-stats">>) -> + ?INFO_COMMAND(<<"Get User Statistics">>, Lang); + ?NS_ADMINL(<<"get-registered-users-num">>) -> + ?INFO_COMMAND(<<"Get Number of Registered Users">>, + Lang); + ?NS_ADMINL(<<"get-online-users-num">>) -> + ?INFO_COMMAND(<<"Get Number of Online Users">>, Lang); + [<<"config">>, <<"acls">>] -> + ?INFO_COMMAND(<<"Access Control Lists">>, Lang); + [<<"config">>, <<"access">>] -> + ?INFO_COMMAND(<<"Access Rules">>, Lang); + _ -> Acc end. %%%----------------------------------------------------------------------- -define(INFO_RESULT(Allow, Feats), case Allow of - deny -> - {error, ?ERR_FORBIDDEN}; - allow -> - {result, Feats} + deny -> {error, ?ERR_FORBIDDEN}; + allow -> {result, Feats} end). -get_sm_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) -> +get_sm_features(Acc, From, + #jid{lserver = LServer} = _To, Node, _Lang) -> case gen_mod:is_loaded(LServer, mod_adhoc) of - false -> - Acc; - _ -> - Allow = acl:match_rule(LServer, configure, From), - case Node of - "config" -> - ?INFO_RESULT(Allow, [?NS_COMMANDS]); - _ -> - Acc - end + false -> Acc; + _ -> + Allow = acl:match_rule(LServer, configure, From), + case Node of + <<"config">> -> ?INFO_RESULT(Allow, [?NS_COMMANDS]); + _ -> Acc + end end. -get_local_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) -> +get_local_features(Acc, From, + #jid{lserver = LServer} = _To, Node, _Lang) -> case gen_mod:is_loaded(LServer, mod_adhoc) of - false -> - Acc; - _ -> - LNode = tokenize(Node), - Allow = acl:match_rule(LServer, configure, From), - case LNode of - ["config"] -> - ?INFO_RESULT(Allow, []); - ["user"] -> - ?INFO_RESULT(Allow, []); - ["online users"] -> - ?INFO_RESULT(Allow, []); - ["all users"] -> - ?INFO_RESULT(Allow, []); - ["all users", [$@ | _]] -> - ?INFO_RESULT(Allow, []); - ["outgoing s2s" | _] -> - ?INFO_RESULT(Allow, []); - ["running nodes"] -> - ?INFO_RESULT(Allow, []); - ["stopped nodes"] -> - ?INFO_RESULT(Allow, []); - ["running nodes", _ENode] -> - ?INFO_RESULT(Allow, [?NS_STATS]); - ["running nodes", _ENode, "DB"] -> - ?INFO_RESULT(Allow, [?NS_COMMANDS]); - ["running nodes", _ENode, "modules"] -> - ?INFO_RESULT(Allow, []); - ["running nodes", _ENode, "modules", _] -> - ?INFO_RESULT(Allow, [?NS_COMMANDS]); - ["running nodes", _ENode, "backup"] -> - ?INFO_RESULT(Allow, []); - ["running nodes", _ENode, "backup", _] -> - ?INFO_RESULT(Allow, [?NS_COMMANDS]); - ["running nodes", _ENode, "import"] -> - ?INFO_RESULT(Allow, []); - ["running nodes", _ENode, "import", _] -> - ?INFO_RESULT(Allow, [?NS_COMMANDS]); - ["running nodes", _ENode, "restart"] -> - ?INFO_RESULT(Allow, [?NS_COMMANDS]); - ["running nodes", _ENode, "shutdown"] -> - ?INFO_RESULT(Allow, [?NS_COMMANDS]); - ["config", _] -> - ?INFO_RESULT(Allow, [?NS_COMMANDS]); - ?NS_ADMINL("add-user") -> - ?INFO_RESULT(Allow, [?NS_COMMANDS]); - ?NS_ADMINL("delete-user") -> - ?INFO_RESULT(Allow, [?NS_COMMANDS]); - ?NS_ADMINL("end-user-session") -> - ?INFO_RESULT(Allow, [?NS_COMMANDS]); - ?NS_ADMINL("get-user-password") -> - ?INFO_RESULT(Allow, [?NS_COMMANDS]); - ?NS_ADMINL("change-user-password") -> - ?INFO_RESULT(Allow, [?NS_COMMANDS]); - ?NS_ADMINL("get-user-lastlogin") -> - ?INFO_RESULT(Allow, [?NS_COMMANDS]); - ?NS_ADMINL("user-stats") -> - ?INFO_RESULT(Allow, [?NS_COMMANDS]); - ?NS_ADMINL("get-registered-users-num") -> - ?INFO_RESULT(Allow, [?NS_COMMANDS]); - ?NS_ADMINL("get-online-users-num") -> - ?INFO_RESULT(Allow, [?NS_COMMANDS]); - _ -> - Acc - end + false -> Acc; + _ -> + LNode = tokenize(Node), + Allow = acl:match_rule(LServer, configure, From), + case LNode of + [<<"config">>] -> ?INFO_RESULT(Allow, []); + [<<"user">>] -> ?INFO_RESULT(Allow, []); + [<<"online users">>] -> ?INFO_RESULT(Allow, []); + [<<"all users">>] -> ?INFO_RESULT(Allow, []); + [<<"all users">>, <<$@, _/binary>>] -> + ?INFO_RESULT(Allow, []); + [<<"outgoing s2s">> | _] -> ?INFO_RESULT(Allow, []); + [<<"running nodes">>] -> ?INFO_RESULT(Allow, []); + [<<"stopped nodes">>] -> ?INFO_RESULT(Allow, []); + [<<"running nodes">>, _ENode] -> + ?INFO_RESULT(Allow, [?NS_STATS]); + [<<"running nodes">>, _ENode, <<"DB">>] -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + [<<"running nodes">>, _ENode, <<"modules">>] -> + ?INFO_RESULT(Allow, []); + [<<"running nodes">>, _ENode, <<"modules">>, _] -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + [<<"running nodes">>, _ENode, <<"backup">>] -> + ?INFO_RESULT(Allow, []); + [<<"running nodes">>, _ENode, <<"backup">>, _] -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + [<<"running nodes">>, _ENode, <<"import">>] -> + ?INFO_RESULT(Allow, []); + [<<"running nodes">>, _ENode, <<"import">>, _] -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + [<<"running nodes">>, _ENode, <<"restart">>] -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + [<<"running nodes">>, _ENode, <<"shutdown">>] -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + [<<"config">>, _] -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + ?NS_ADMINL(<<"add-user">>) -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + ?NS_ADMINL(<<"delete-user">>) -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + ?NS_ADMINL(<<"end-user-session">>) -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + ?NS_ADMINL(<<"get-user-password">>) -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + ?NS_ADMINL(<<"change-user-password">>) -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + ?NS_ADMINL(<<"get-user-lastlogin">>) -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + ?NS_ADMINL(<<"user-stats">>) -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + ?NS_ADMINL(<<"get-registered-users-num">>) -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + ?NS_ADMINL(<<"get-online-users-num">>) -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + _ -> Acc + end end. %%%----------------------------------------------------------------------- -adhoc_sm_items(Acc, From, #jid{lserver = LServer} = To, Lang) -> +adhoc_sm_items(Acc, From, #jid{lserver = LServer} = To, + Lang) -> case acl:match_rule(LServer, configure, From) of - allow -> - Items = case Acc of - {result, Its} -> Its; - empty -> [] - end, - Nodes = [{xmlelement, "item", - [{"jid", jlib:jid_to_string(To)}, - {"name", ?T(Lang, "Configuration")}, - {"node", "config"}], []}], - {result, Items ++ Nodes}; - _ -> - Acc + allow -> + Items = case Acc of + {result, Its} -> Its; + empty -> [] + end, + Nodes = [#xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, jlib:jid_to_string(To)}, + {<<"name">>, ?T(Lang, <<"Configuration">>)}, + {<<"node">>, <<"config">>}], + children = []}], + {result, Items ++ Nodes}; + _ -> Acc end. %%%----------------------------------------------------------------------- get_sm_items(Acc, From, - #jid{user = User, server = Server, lserver = LServer} = To, + #jid{user = User, server = Server, lserver = LServer} = + To, Node, Lang) -> case gen_mod:is_loaded(LServer, mod_adhoc) of - false -> - Acc; - _ -> - Items = case Acc of - {result, Its} -> Its; - empty -> [] - end, - case {acl:match_rule(LServer, configure, From), Node} of - {allow, ""} -> - Nodes = [?NODEJID(To, "Configuration", "config"), - ?NODEJID(To, "User Management", "user")], - {result, Items ++ Nodes ++ get_user_resources(User, Server)}; - {allow, "config"} -> - {result, []}; - {_, "config"} -> - {error, ?ERR_FORBIDDEN}; - _ -> - Acc - end + false -> Acc; + _ -> + Items = case Acc of + {result, Its} -> Its; + empty -> [] + end, + case {acl:match_rule(LServer, configure, From), Node} of + {allow, <<"">>} -> + Nodes = [?NODEJID(To, <<"Configuration">>, + <<"config">>), + ?NODEJID(To, <<"User Management">>, <<"user">>)], + {result, + Items ++ Nodes ++ get_user_resources(User, Server)}; + {allow, <<"config">>} -> {result, []}; + {_, <<"config">>} -> {error, ?ERR_FORBIDDEN}; + _ -> Acc + end end. get_user_resources(User, Server) -> Rs = ejabberd_sm:get_user_resources(User, Server), - lists:map(fun(R) -> - {xmlelement, "item", - [{"jid", User ++ "@" ++ Server ++ "/" ++ R}, - {"name", User}], []} - end, lists:sort(Rs)). + lists:map(fun (R) -> + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, + <>}, + {<<"name">>, User}], + children = []} + end, + lists:sort(Rs)). %%%----------------------------------------------------------------------- -adhoc_local_items(Acc, From, #jid{lserver = LServer, server = Server} = To, - Lang) -> +adhoc_local_items(Acc, From, + #jid{lserver = LServer, server = Server} = To, Lang) -> case acl:match_rule(LServer, configure, From) of - allow -> - Items = case Acc of - {result, Its} -> Its; - empty -> [] - end, - PermLev = get_permission_level(From), - %% Recursively get all configure commands - Nodes = recursively_get_local_items(PermLev, LServer, "", Server, - Lang), - Nodes1 = lists:filter( - fun(N) -> - Nd = xml:get_tag_attr_s("node", N), - F = get_local_features([], From, To, Nd, Lang), - case F of - {result, [?NS_COMMANDS]} -> - true; - _ -> - false - end - end, Nodes), - {result, Items ++ Nodes1}; - _ -> - Acc + allow -> + Items = case Acc of + {result, Its} -> Its; + empty -> [] + end, + PermLev = get_permission_level(From), + Nodes = recursively_get_local_items(PermLev, LServer, + <<"">>, Server, Lang), + Nodes1 = lists:filter(fun (N) -> + Nd = xml:get_tag_attr_s(<<"node">>, N), + F = get_local_features([], From, To, Nd, + Lang), + case F of + {result, [?NS_COMMANDS]} -> true; + _ -> false + end + end, + Nodes), + {result, Items ++ Nodes1}; + _ -> Acc end. -recursively_get_local_items(_PermLev, _LServer, "online users", _Server, _Lang) -> +recursively_get_local_items(_PermLev, _LServer, + <<"online users">>, _Server, _Lang) -> []; - -recursively_get_local_items(_PermLev, _LServer, "all users", _Server, _Lang) -> +recursively_get_local_items(_PermLev, _LServer, + <<"all users">>, _Server, _Lang) -> []; - -recursively_get_local_items(PermLev, LServer, Node, Server, Lang) -> +recursively_get_local_items(PermLev, LServer, Node, + Server, Lang) -> LNode = tokenize(Node), - Items = case get_local_items({PermLev, LServer}, LNode, Server, Lang) of - {result, Res} -> - Res; - {error, _Error} -> - [] + Items = case get_local_items({PermLev, LServer}, LNode, + Server, Lang) + of + {result, Res} -> Res; + {error, _Error} -> [] end, - Nodes = lists:flatten( - lists:map( - fun(N) -> - S = xml:get_tag_attr_s("jid", N), - Nd = xml:get_tag_attr_s("node", N), - if (S /= Server) or (Nd == "") -> - []; - true -> - [N, recursively_get_local_items( - PermLev, LServer, Nd, Server, Lang)] - end - end, Items)), + Nodes = lists:flatten(lists:map(fun (N) -> + S = xml:get_tag_attr_s(<<"jid">>, + N), + Nd = xml:get_tag_attr_s(<<"node">>, + N), + if (S /= Server) or + (Nd == <<"">>) -> + []; + true -> + [N, + recursively_get_local_items(PermLev, + LServer, + Nd, + Server, + Lang)] + end + end, + Items)), Nodes. get_permission_level(JID) -> case acl:match_rule(global, configure, JID) of - allow -> global; - deny -> vhost + allow -> global; + deny -> vhost end. %%%----------------------------------------------------------------------- -define(ITEMS_RESULT(Allow, LNode, Fallback), case Allow of - deny -> - Fallback; - allow -> - PermLev = get_permission_level(From), - case get_local_items({PermLev, LServer}, LNode, - jlib:jid_to_string(To), Lang) of - {result, Res} -> - {result, Res}; - {error, Error} -> - {error, Error} - end + deny -> Fallback; + allow -> + PermLev = get_permission_level(From), + case get_local_items({PermLev, LServer}, LNode, + jlib:jid_to_string(To), Lang) + of + {result, Res} -> {result, Res}; + {error, Error} -> {error, Error} + end end). -get_local_items(Acc, From, #jid{lserver = LServer} = To, "", Lang) -> +get_local_items(Acc, From, #jid{lserver = LServer} = To, + <<"">>, Lang) -> case gen_mod:is_loaded(LServer, mod_adhoc) of - false -> - Acc; - _ -> - Items = case Acc of - {result, Its} -> Its; - empty -> [] - end, - Allow = acl:match_rule(LServer, configure, From), - case Allow of - deny -> - {result, Items}; - allow -> - PermLev = get_permission_level(From), - case get_local_items({PermLev, LServer}, [], - jlib:jid_to_string(To), Lang) of - {result, Res} -> - {result, Items ++ Res}; - {error, _Error} -> - {result, Items} - end - end + false -> Acc; + _ -> + Items = case Acc of + {result, Its} -> Its; + empty -> [] + end, + Allow = acl:match_rule(LServer, configure, From), + case Allow of + deny -> {result, Items}; + allow -> + PermLev = get_permission_level(From), + case get_local_items({PermLev, LServer}, [], + jlib:jid_to_string(To), Lang) + of + {result, Res} -> {result, Items ++ Res}; + {error, _Error} -> {result, Items} + end + end end; - -get_local_items(Acc, From, #jid{lserver = LServer} = To, Node, Lang) -> +get_local_items(Acc, From, #jid{lserver = LServer} = To, + Node, Lang) -> case gen_mod:is_loaded(LServer, mod_adhoc) of - false -> - Acc; - _ -> - LNode = tokenize(Node), - Allow = acl:match_rule(LServer, configure, From), - case LNode of - ["config"] -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ["user"] -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ["online users"] -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ["all users"] -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ["all users", [$@ | _]] -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ["outgoing s2s" | _] -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ["running nodes"] -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ["stopped nodes"] -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ["running nodes", _ENode] -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ["running nodes", _ENode, "DB"] -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ["running nodes", _ENode, "modules"] -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ["running nodes", _ENode, "modules", _] -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ["running nodes", _ENode, "backup"] -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ["running nodes", _ENode, "backup", _] -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ["running nodes", _ENode, "import"] -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ["running nodes", _ENode, "import", _] -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ["running nodes", _ENode, "restart"] -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ["running nodes", _ENode, "shutdown"] -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ["config", _] -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ?NS_ADMINL("add-user") -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ?NS_ADMINL("delete-user") -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ?NS_ADMINL("end-user-session") -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ?NS_ADMINL("get-user-password") -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ?NS_ADMINL("change-user-password") -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ?NS_ADMINL("get-user-lastlogin") -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ?NS_ADMINL("user-stats") -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ?NS_ADMINL("get-registered-users-num") -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - ?NS_ADMINL("get-online-users-num") -> - ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); - _ -> - Acc - end + false -> Acc; + _ -> + LNode = tokenize(Node), + Allow = acl:match_rule(LServer, configure, From), + case LNode of + [<<"config">>] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + [<<"user">>] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + [<<"online users">>] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + [<<"all users">>] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + [<<"all users">>, <<$@, _/binary>>] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + [<<"outgoing s2s">> | _] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + [<<"running nodes">>] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + [<<"stopped nodes">>] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + [<<"running nodes">>, _ENode] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + [<<"running nodes">>, _ENode, <<"DB">>] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + [<<"running nodes">>, _ENode, <<"modules">>] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + [<<"running nodes">>, _ENode, <<"modules">>, _] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + [<<"running nodes">>, _ENode, <<"backup">>] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + [<<"running nodes">>, _ENode, <<"backup">>, _] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + [<<"running nodes">>, _ENode, <<"import">>] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + [<<"running nodes">>, _ENode, <<"import">>, _] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + [<<"running nodes">>, _ENode, <<"restart">>] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + [<<"running nodes">>, _ENode, <<"shutdown">>] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + [<<"config">>, _] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ?NS_ADMINL(<<"add-user">>) -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ?NS_ADMINL(<<"delete-user">>) -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ?NS_ADMINL(<<"end-user-session">>) -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ?NS_ADMINL(<<"get-user-password">>) -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ?NS_ADMINL(<<"change-user-password">>) -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ?NS_ADMINL(<<"get-user-lastlogin">>) -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ?NS_ADMINL(<<"user-stats">>) -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ?NS_ADMINL(<<"get-registered-users-num">>) -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ?NS_ADMINL(<<"get-online-users-num">>) -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + _ -> Acc + end end. %%%----------------------------------------------------------------------- @@ -495,1403 +520,1633 @@ get_local_items(Acc, From, #jid{lserver = LServer} = To, Node, Lang) -> %% PermissionLevel = global | vhost get_local_items(_Host, [], Server, Lang) -> {result, - [?NODE("Configuration", "config"), - ?NODE("User Management", "user"), - ?NODE("Online Users", "online users"), - ?NODE("All Users", "all users"), - ?NODE("Outgoing s2s Connections", "outgoing s2s"), - ?NODE("Running Nodes", "running nodes"), - ?NODE("Stopped Nodes", "stopped nodes") - ]}; - -get_local_items(_Host, ["config"], Server, Lang) -> + [?NODE(<<"Configuration">>, <<"config">>), + ?NODE(<<"User Management">>, <<"user">>), + ?NODE(<<"Online Users">>, <<"online users">>), + ?NODE(<<"All Users">>, <<"all users">>), + ?NODE(<<"Outgoing s2s Connections">>, + <<"outgoing s2s">>), + ?NODE(<<"Running Nodes">>, <<"running nodes">>), + ?NODE(<<"Stopped Nodes">>, <<"stopped nodes">>)]}; +get_local_items(_Host, [<<"config">>], Server, Lang) -> {result, - [?NODE("Access Control Lists", "config/acls"), - ?NODE("Access Rules", "config/access") - ]}; - -get_local_items(_Host, ["config", _], _Server, _Lang) -> + [?NODE(<<"Access Control Lists">>, <<"config/acls">>), + ?NODE(<<"Access Rules">>, <<"config/access">>)]}; +get_local_items(_Host, [<<"config">>, _], _Server, + _Lang) -> {result, []}; - -get_local_items(_Host, ["user"], Server, Lang) -> +get_local_items(_Host, [<<"user">>], Server, Lang) -> {result, - [?NODE("Add User", ?NS_ADMINX("add-user")), - ?NODE("Delete User", ?NS_ADMINX("delete-user")), - ?NODE("End User Session", ?NS_ADMINX("end-user-session")), - ?NODE("Get User Password", ?NS_ADMINX("get-user-password")), - ?NODE("Change User Password",?NS_ADMINX("change-user-password")), - ?NODE("Get User Last Login Time", ?NS_ADMINX("get-user-lastlogin")), - ?NODE("Get User Statistics", ?NS_ADMINX("user-stats")), - ?NODE("Get Number of Registered Users",?NS_ADMINX("get-registered-users-num")), - ?NODE("Get Number of Online Users",?NS_ADMINX("get-online-users-num")) - ]}; - -get_local_items(_Host, ["http:" | _], _Server, _Lang) -> + [?NODE(<<"Add User">>, (?NS_ADMINX(<<"add-user">>))), + ?NODE(<<"Delete User">>, + (?NS_ADMINX(<<"delete-user">>))), + ?NODE(<<"End User Session">>, + (?NS_ADMINX(<<"end-user-session">>))), + ?NODE(<<"Get User Password">>, + (?NS_ADMINX(<<"get-user-password">>))), + ?NODE(<<"Change User Password">>, + (?NS_ADMINX(<<"change-user-password">>))), + ?NODE(<<"Get User Last Login Time">>, + (?NS_ADMINX(<<"get-user-lastlogin">>))), + ?NODE(<<"Get User Statistics">>, + (?NS_ADMINX(<<"user-stats">>))), + ?NODE(<<"Get Number of Registered Users">>, + (?NS_ADMINX(<<"get-registered-users-num">>))), + ?NODE(<<"Get Number of Online Users">>, + (?NS_ADMINX(<<"get-online-users-num">>)))]}; +get_local_items(_Host, [<<"http:">> | _], _Server, + _Lang) -> {result, []}; - -get_local_items({_, Host}, ["online users"], _Server, _Lang) -> +get_local_items({_, Host}, [<<"online users">>], + _Server, _Lang) -> {result, get_online_vh_users(Host)}; - -get_local_items({_, Host}, ["all users"], _Server, _Lang) -> +get_local_items({_, Host}, [<<"all users">>], _Server, + _Lang) -> {result, get_all_vh_users(Host)}; - -get_local_items({_, Host}, ["all users", [$@ | Diap]], _Server, _Lang) -> - case catch ejabberd_auth:get_vh_registered_users(Host) of - {'EXIT', _Reason} -> - ?ERR_INTERNAL_SERVER_ERROR; - Users -> - SUsers = lists:sort([{S, U} || {U, S} <- Users]), - case catch begin - [S1, S2] = ejabberd_regexp:split(Diap, "-"), - N1 = list_to_integer(S1), - N2 = list_to_integer(S2), - Sub = lists:sublist(SUsers, N1, N2 - N1 + 1), - lists:map(fun({S, U}) -> - {xmlelement, "item", - [{"jid", U ++ "@" ++ S}, - {"name", U ++ "@" ++ S}], []} - end, Sub) - end of - {'EXIT', _Reason} -> - ?ERR_NOT_ACCEPTABLE; - Res -> - {result, Res} - end +get_local_items({_, Host}, + [<<"all users">>, <<$@, Diap/binary>>], _Server, + _Lang) -> + case catch ejabberd_auth:get_vh_registered_users(Host) + of + {'EXIT', _Reason} -> ?ERR_INTERNAL_SERVER_ERROR; + Users -> + SUsers = lists:sort([{S, U} || {U, S} <- Users]), + case catch begin + [S1, S2] = ejabberd_regexp:split(Diap, <<"-">>), + N1 = jlib:binary_to_integer(S1), + N2 = jlib:binary_to_integer(S2), + Sub = lists:sublist(SUsers, N1, N2 - N1 + 1), + lists:map(fun ({S, U}) -> + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, + <>}, + {<<"name">>, + <>}], + children = []} + end, + Sub) + end + of + {'EXIT', _Reason} -> ?ERR_NOT_ACCEPTABLE; + Res -> {result, Res} + end end; - -get_local_items({_, Host}, ["outgoing s2s"], _Server, Lang) -> +get_local_items({_, Host}, [<<"outgoing s2s">>], + _Server, Lang) -> {result, get_outgoing_s2s(Host, Lang)}; - -get_local_items({_, Host}, ["outgoing s2s", To], _Server, Lang) -> +get_local_items({_, Host}, [<<"outgoing s2s">>, To], + _Server, Lang) -> {result, get_outgoing_s2s(Host, Lang, To)}; - -get_local_items(_Host, ["running nodes"], Server, Lang) -> +get_local_items(_Host, [<<"running nodes">>], Server, + Lang) -> {result, get_running_nodes(Server, Lang)}; - -get_local_items(_Host, ["stopped nodes"], _Server, Lang) -> +get_local_items(_Host, [<<"stopped nodes">>], _Server, + Lang) -> {result, get_stopped_nodes(Lang)}; - -get_local_items({global, _Host}, ["running nodes", ENode], Server, Lang) -> +get_local_items({global, _Host}, + [<<"running nodes">>, ENode], Server, Lang) -> {result, - [?NODE("Database", "running nodes/" ++ ENode ++ "/DB"), - ?NODE("Modules", "running nodes/" ++ ENode ++ "/modules"), - ?NODE("Backup Management", "running nodes/" ++ ENode ++ "/backup"), - ?NODE("Import Users From jabberd14 Spool Files", - "running nodes/" ++ ENode ++ "/import"), - ?NODE("Restart Service", "running nodes/" ++ ENode ++ "/restart"), - ?NODE("Shut Down Service", "running nodes/" ++ ENode ++ "/shutdown") - ]}; - -get_local_items({vhost, _Host}, ["running nodes", ENode], Server, Lang) -> + [?NODE(<<"Database">>, + <<"running nodes/", ENode/binary, "/DB">>), + ?NODE(<<"Modules">>, + <<"running nodes/", ENode/binary, "/modules">>), + ?NODE(<<"Backup Management">>, + <<"running nodes/", ENode/binary, "/backup">>), + ?NODE(<<"Import Users From jabberd14 Spool Files">>, + <<"running nodes/", ENode/binary, "/import">>), + ?NODE(<<"Restart Service">>, + <<"running nodes/", ENode/binary, "/restart">>), + ?NODE(<<"Shut Down Service">>, + <<"running nodes/", ENode/binary, "/shutdown">>)]}; +get_local_items({vhost, _Host}, + [<<"running nodes">>, ENode], Server, Lang) -> {result, - [?NODE("Modules", "running nodes/" ++ ENode ++ "/modules") - ]}; - -get_local_items(_Host, ["running nodes", _ENode, "DB"], _Server, _Lang) -> + [?NODE(<<"Modules">>, + <<"running nodes/", ENode/binary, "/modules">>)]}; +get_local_items(_Host, + [<<"running nodes">>, _ENode, <<"DB">>], _Server, + _Lang) -> {result, []}; - -get_local_items(_Host, ["running nodes", ENode, "modules"], Server, Lang) -> +get_local_items(_Host, + [<<"running nodes">>, ENode, <<"modules">>], Server, + Lang) -> {result, - [?NODE("Start Modules", "running nodes/" ++ ENode ++ "/modules/start"), - ?NODE("Stop Modules", "running nodes/" ++ ENode ++ "/modules/stop") - ]}; - -get_local_items(_Host, ["running nodes", _ENode, "modules", _], _Server, _Lang) -> + [?NODE(<<"Start Modules">>, + <<"running nodes/", ENode/binary, "/modules/start">>), + ?NODE(<<"Stop Modules">>, + <<"running nodes/", ENode/binary, "/modules/stop">>)]}; +get_local_items(_Host, + [<<"running nodes">>, _ENode, <<"modules">>, _], + _Server, _Lang) -> {result, []}; - -get_local_items(_Host, ["running nodes", ENode, "backup"], Server, Lang) -> +get_local_items(_Host, + [<<"running nodes">>, ENode, <<"backup">>], Server, + Lang) -> {result, - [?NODE("Backup", "running nodes/" ++ ENode ++ "/backup/backup"), - ?NODE("Restore", "running nodes/" ++ ENode ++ "/backup/restore"), - ?NODE("Dump to Text File", - "running nodes/" ++ ENode ++ "/backup/textfile") - ]}; - -get_local_items(_Host, ["running nodes", _ENode, "backup", _], _Server, _Lang) -> + [?NODE(<<"Backup">>, + <<"running nodes/", ENode/binary, "/backup/backup">>), + ?NODE(<<"Restore">>, + <<"running nodes/", ENode/binary, "/backup/restore">>), + ?NODE(<<"Dump to Text File">>, + <<"running nodes/", ENode/binary, + "/backup/textfile">>)]}; +get_local_items(_Host, + [<<"running nodes">>, _ENode, <<"backup">>, _], _Server, + _Lang) -> {result, []}; - -get_local_items(_Host, ["running nodes", ENode, "import"], Server, Lang) -> +get_local_items(_Host, + [<<"running nodes">>, ENode, <<"import">>], Server, + Lang) -> {result, - [?NODE("Import File", "running nodes/" ++ ENode ++ "/import/file"), - ?NODE("Import Directory", "running nodes/" ++ ENode ++ "/import/dir") - ]}; - -get_local_items(_Host, ["running nodes", _ENode, "import", _], _Server, _Lang) -> + [?NODE(<<"Import File">>, + <<"running nodes/", ENode/binary, "/import/file">>), + ?NODE(<<"Import Directory">>, + <<"running nodes/", ENode/binary, "/import/dir">>)]}; +get_local_items(_Host, + [<<"running nodes">>, _ENode, <<"import">>, _], _Server, + _Lang) -> {result, []}; - -get_local_items(_Host, ["running nodes", _ENode, "restart"], _Server, _Lang) -> +get_local_items(_Host, + [<<"running nodes">>, _ENode, <<"restart">>], _Server, + _Lang) -> {result, []}; - -get_local_items(_Host, ["running nodes", _ENode, "shutdown"], _Server, _Lang) -> +get_local_items(_Host, + [<<"running nodes">>, _ENode, <<"shutdown">>], _Server, + _Lang) -> {result, []}; - get_local_items(_Host, _, _Server, _Lang) -> {error, ?ERR_ITEM_NOT_FOUND}. - get_online_vh_users(Host) -> case catch ejabberd_sm:get_vh_session_list(Host) of - {'EXIT', _Reason} -> - []; - USRs -> - SURs = lists:sort([{S, U, R} || {U, S, R} <- USRs]), - lists:map(fun({S, U, R}) -> - {xmlelement, "item", - [{"jid", U ++ "@" ++ S ++ "/" ++ R}, - {"name", U ++ "@" ++ S}], []} - end, SURs) + {'EXIT', _Reason} -> []; + USRs -> + SURs = lists:sort([{S, U, R} || {U, S, R} <- USRs]), + lists:map(fun ({S, U, R}) -> + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, + <>}, + {<<"name">>, + <>}], + children = []} + end, + SURs) end. get_all_vh_users(Host) -> - case catch ejabberd_auth:get_vh_registered_users(Host) of - {'EXIT', _Reason} -> - []; - Users -> - SUsers = lists:sort([{S, U} || {U, S} <- Users]), - case length(SUsers) of - N when N =< 100 -> - lists:map(fun({S, U}) -> - {xmlelement, "item", - [{"jid", U ++ "@" ++ S}, - {"name", U ++ "@" ++ S}], []} - end, SUsers); - N -> - NParts = trunc(math:sqrt(N * 0.618)) + 1, - M = trunc(N / NParts) + 1, - lists:map(fun(K) -> - L = K + M - 1, - Node = - "@" ++ integer_to_list(K) ++ - "-" ++ integer_to_list(L), - {FS, FU} = lists:nth(K, SUsers), - {LS, LU} = - if L < N -> lists:nth(L, SUsers); - true -> lists:last(SUsers) - end, - Name = - FU ++ "@" ++ FS ++ - " -- " ++ - LU ++ "@" ++ LS, - {xmlelement, "item", - [{"jid", Host}, - {"node", "all users/" ++ Node}, - {"name", Name}], []} - end, lists:seq(1, N, M)) - end + case catch ejabberd_auth:get_vh_registered_users(Host) + of + {'EXIT', _Reason} -> []; + Users -> + SUsers = lists:sort([{S, U} || {U, S} <- Users]), + case length(SUsers) of + N when N =< 100 -> + lists:map(fun ({S, U}) -> + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, + <>}, + {<<"name">>, + <>}], + children = []} + end, + SUsers); + N -> + NParts = trunc(math:sqrt(N * 6.17999999999999993783e-1)) + + 1, + M = trunc(N / NParts) + 1, + lists:map(fun (K) -> + L = K + M - 1, + Node = <<"@", + (iolist_to_binary(integer_to_list(K)))/binary, + "-", + (iolist_to_binary(integer_to_list(L)))/binary>>, + {FS, FU} = lists:nth(K, SUsers), + {LS, LU} = if L < N -> lists:nth(L, SUsers); + true -> lists:last(SUsers) + end, + Name = <>, + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, Host}, + {<<"node">>, + <<"all users/", Node/binary>>}, + {<<"name">>, Name}], + children = []} + end, + lists:seq(1, N, M)) + end end. get_outgoing_s2s(Host, Lang) -> case catch ejabberd_s2s:dirty_get_connections() of - {'EXIT', _Reason} -> - []; - Connections -> - DotHost = "." ++ Host, - TConns = [TH || {FH, TH} <- Connections, - Host == FH orelse lists:suffix(DotHost, FH)], - lists:map( - fun(T) -> - {xmlelement, "item", - [{"jid", Host}, - {"node", "outgoing s2s/" ++ T}, - {"name", - lists:flatten( - io_lib:format( - ?T(Lang, "To ~s"), [T]))}], - []} - end, lists:usort(TConns)) + {'EXIT', _Reason} -> []; + Connections -> + DotHost = <<".", Host/binary>>, + TConns = [TH + || {FH, TH} <- Connections, + Host == FH orelse str:suffix(DotHost, FH)], + lists:map(fun (T) -> + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, Host}, + {<<"node">>, + <<"outgoing s2s/", T/binary>>}, + {<<"name">>, + iolist_to_binary(io_lib:format(?T(Lang, + <<"To ~s">>), + [T]))}], + children = []} + end, + lists:usort(TConns)) end. get_outgoing_s2s(Host, Lang, To) -> case catch ejabberd_s2s:dirty_get_connections() of - {'EXIT', _Reason} -> - []; - Connections -> - lists:map( - fun({F, _T}) -> - {xmlelement, "item", - [{"jid", Host}, - {"node", "outgoing s2s/" ++ To ++ "/" ++ F}, - {"name", - lists:flatten( - io_lib:format( - ?T(Lang, "From ~s"), [F]))}], - []} - end, lists:keysort(1, lists:filter(fun(E) -> - element(2, E) == To - end, Connections))) + {'EXIT', _Reason} -> []; + Connections -> + lists:map(fun ({F, _T}) -> + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, Host}, + {<<"node">>, + <<"outgoing s2s/", To/binary, "/", + F/binary>>}, + {<<"name">>, + iolist_to_binary(io_lib:format(?T(Lang, + <<"From ~s">>), + [F]))}], + children = []} + end, + lists:keysort(1, + lists:filter(fun (E) -> element(2, E) == To + end, + Connections))) end. - get_running_nodes(Server, _Lang) -> case catch mnesia:system_info(running_db_nodes) of - {'EXIT', _Reason} -> - []; - DBNodes -> - lists:map( - fun(N) -> - S = atom_to_list(N), - {xmlelement, "item", - [{"jid", Server}, - {"node", "running nodes/" ++ S}, - {"name", S}], - []} - end, lists:sort(DBNodes)) + {'EXIT', _Reason} -> []; + DBNodes -> + lists:map(fun (N) -> + S = iolist_to_binary(atom_to_list(N)), + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, Server}, + {<<"node">>, + <<"running nodes/", S/binary>>}, + {<<"name">>, S}], + children = []} + end, + lists:sort(DBNodes)) end. get_stopped_nodes(_Lang) -> - case catch (lists:usort(mnesia:system_info(db_nodes) ++ - mnesia:system_info(extra_db_nodes)) -- - mnesia:system_info(running_db_nodes)) of - {'EXIT', _Reason} -> - []; - DBNodes -> - lists:map( - fun(N) -> - S = atom_to_list(N), - {xmlelement, "item", - [{"jid", ?MYNAME}, - {"node", "stopped nodes/" ++ S}, - {"name", S}], - []} - end, lists:sort(DBNodes)) + case catch lists:usort(mnesia:system_info(db_nodes) ++ + mnesia:system_info(extra_db_nodes)) + -- mnesia:system_info(running_db_nodes) + of + {'EXIT', _Reason} -> []; + DBNodes -> + lists:map(fun (N) -> + S = iolist_to_binary(atom_to_list(N)), + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, ?MYNAME}, + {<<"node">>, + <<"stopped nodes/", S/binary>>}, + {<<"name">>, S}], + children = []} + end, + lists:sort(DBNodes)) end. %%------------------------------------------------------------------------- --define(COMMANDS_RESULT(LServerOrGlobal, From, To, Request), +-define(COMMANDS_RESULT(LServerOrGlobal, From, To, + Request), case acl:match_rule(LServerOrGlobal, configure, From) of - deny -> - {error, ?ERR_FORBIDDEN}; - allow -> - adhoc_local_commands(From, To, Request) + deny -> {error, ?ERR_FORBIDDEN}; + allow -> adhoc_local_commands(From, To, Request) end). -adhoc_local_commands(Acc, From, #jid{lserver = LServer} = To, +adhoc_local_commands(Acc, From, + #jid{lserver = LServer} = To, #adhoc_request{node = Node} = Request) -> LNode = tokenize(Node), case LNode of - ["running nodes", _ENode, "DB"] -> - ?COMMANDS_RESULT(global, From, To, Request); - ["running nodes", _ENode, "modules", _] -> - ?COMMANDS_RESULT(LServer, From, To, Request); - ["running nodes", _ENode, "backup", _] -> - ?COMMANDS_RESULT(global, From, To, Request); - ["running nodes", _ENode, "import", _] -> - ?COMMANDS_RESULT(global, From, To, Request); - ["running nodes", _ENode, "restart"] -> - ?COMMANDS_RESULT(global, From, To, Request); - ["running nodes", _ENode, "shutdown"] -> - ?COMMANDS_RESULT(global, From, To, Request); - ["config", _] -> - ?COMMANDS_RESULT(LServer, From, To, Request); - ?NS_ADMINL(_) -> - ?COMMANDS_RESULT(LServer, From, To, Request); - _ -> - Acc + [<<"running nodes">>, _ENode, <<"DB">>] -> + ?COMMANDS_RESULT(global, From, To, Request); + [<<"running nodes">>, _ENode, <<"modules">>, _] -> + ?COMMANDS_RESULT(LServer, From, To, Request); + [<<"running nodes">>, _ENode, <<"backup">>, _] -> + ?COMMANDS_RESULT(global, From, To, Request); + [<<"running nodes">>, _ENode, <<"import">>, _] -> + ?COMMANDS_RESULT(global, From, To, Request); + [<<"running nodes">>, _ENode, <<"restart">>] -> + ?COMMANDS_RESULT(global, From, To, Request); + [<<"running nodes">>, _ENode, <<"shutdown">>] -> + ?COMMANDS_RESULT(global, From, To, Request); + [<<"config">>, _] -> + ?COMMANDS_RESULT(LServer, From, To, Request); + ?NS_ADMINL(_) -> + ?COMMANDS_RESULT(LServer, From, To, Request); + _ -> Acc end. -adhoc_local_commands(From, #jid{lserver = LServer} = _To, - #adhoc_request{lang = Lang, - node = Node, - sessionid = SessionID, - action = Action, - xdata = XData} = Request) -> +adhoc_local_commands(From, + #jid{lserver = LServer} = _To, + #adhoc_request{lang = Lang, node = Node, + sessionid = SessionID, action = Action, + xdata = XData} = + Request) -> LNode = tokenize(Node), - %% If the "action" attribute is not present, it is - %% understood as "execute". If there was no - %% element in the first response (which there isn't in our - %% case), "execute" and "complete" are equivalent. ActionIsExecute = lists:member(Action, - ["", "execute", "complete"]), - if Action == "cancel" -> - %% User cancels request - adhoc:produce_response( - Request, - #adhoc_response{status = canceled}); - XData == false, ActionIsExecute -> - %% User requests form - case get_form(LServer, LNode, Lang) of - {result, Form} -> - adhoc:produce_response( - Request, - #adhoc_response{status = executing, - elements = Form}); - {result, Status, Form} -> - adhoc:produce_response( - Request, - #adhoc_response{status = Status, - elements = Form}); - {error, Error} -> - {error, Error} - end; - XData /= false, ActionIsExecute -> - %% User returns form. - case jlib:parse_xdata_submit(XData) of - invalid -> - {error, ?ERR_BAD_REQUEST}; - Fields -> - case catch set_form(From, LServer, LNode, Lang, Fields) of - {result, Res} -> - adhoc:produce_response( - #adhoc_response{lang = Lang, - node = Node, - sessionid = SessionID, - elements = Res, - status = completed}); - {'EXIT', _} -> - {error, ?ERR_BAD_REQUEST}; - {error, Error} -> - {error, Error} - end - end; - true -> - {error, ?ERR_BAD_REQUEST} + [<<"">>, <<"execute">>, <<"complete">>]), + if Action == <<"cancel">> -> + adhoc:produce_response(Request, + #adhoc_response{status = canceled}); + XData == false, ActionIsExecute -> + case get_form(LServer, LNode, Lang) of + {result, Form} -> + adhoc:produce_response(Request, + #adhoc_response{status = executing, + elements = Form}); + {result, Status, Form} -> + adhoc:produce_response(Request, + #adhoc_response{status = Status, + elements = Form}); + {error, Error} -> {error, Error} + end; + XData /= false, ActionIsExecute -> + case jlib:parse_xdata_submit(XData) of + invalid -> {error, ?ERR_BAD_REQUEST}; + Fields -> + case catch set_form(From, LServer, LNode, Lang, Fields) + of + {result, Res} -> + adhoc:produce_response(#adhoc_response{lang = Lang, + node = Node, + sessionid = + SessionID, + elements = Res, + status = + completed}); + {'EXIT', _} -> {error, ?ERR_BAD_REQUEST}; + {error, Error} -> {error, Error} + end + end; + true -> {error, ?ERR_BAD_REQUEST} end. - -define(TVFIELD(Type, Var, Val), - {xmlelement, "field", [{"type", Type}, - {"var", Var}], - [{xmlelement, "value", [], [{xmlcdata, Val}]}]}). --define(HFIELD(), ?TVFIELD("hidden", "FORM_TYPE", ?NS_ADMIN)). + #xmlel{name = <<"field">>, + attrs = [{<<"type">>, Type}, {<<"var">>, Var}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Val}]}]}). + +-define(HFIELD(), + ?TVFIELD(<<"hidden">>, <<"FORM_TYPE">>, (?NS_ADMIN))). -define(TLFIELD(Type, Label, Var), - {xmlelement, "field", [{"type", Type}, - {"label", ?T(Lang, Label)}, - {"var", Var}], []}). + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, Type}, {<<"label">>, ?T(Lang, Label)}, + {<<"var">>, Var}], + children = []}). -define(XFIELD(Type, Label, Var, Val), - {xmlelement, "field", [{"type", Type}, - {"label", ?T(Lang, Label)}, - {"var", Var}], - [{xmlelement, "value", [], [{xmlcdata, Val}]}]}). + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, Type}, {<<"label">>, ?T(Lang, Label)}, + {<<"var">>, Var}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Val}]}]}). -define(XMFIELD(Type, Label, Var, Vals), - {xmlelement, "field", [{"type", Type}, - {"label", ?T(Lang, Label)}, - {"var", Var}], - [{xmlelement, "value", [], [{xmlcdata,Val}]} || Val <- Vals]}). + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, Type}, {<<"label">>, ?T(Lang, Label)}, + {<<"var">>, Var}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Val}]} + || Val <- Vals]}). -define(TABLEFIELD(Table, Val), - {xmlelement, "field", [{"type", "list-single"}, - {"label", atom_to_list(Table)}, - {"var", atom_to_list(Table)}], - [{xmlelement, "value", [], [{xmlcdata, atom_to_list(Val)}]}, - {xmlelement, "option", [{"label", - ?T(Lang, "RAM copy")}], - [{xmlelement, "value", [], [{xmlcdata, "ram_copies"}]}]}, - {xmlelement, "option", [{"label", - ?T(Lang, - "RAM and disc copy")}], - [{xmlelement, "value", [], [{xmlcdata, "disc_copies"}]}]}, - {xmlelement, "option", [{"label", - ?T(Lang, - "Disc only copy")}], - [{xmlelement, "value", [], [{xmlcdata, "disc_only_copies"}]}]}, - {xmlelement, "option", [{"label", - ?T(Lang, "Remote copy")}], - [{xmlelement, "value", [], [{xmlcdata, "unknown"}]}]} - ]}). + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"list-single">>}, + {<<"label">>, iolist_to_binary(atom_to_list(Table))}, + {<<"var">>, iolist_to_binary(atom_to_list(Table))}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, + iolist_to_binary(atom_to_list(Val))}]}, + #xmlel{name = <<"option">>, + attrs = [{<<"label">>, ?T(Lang, <<"RAM copy">>)}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, <<"ram_copies">>}]}]}, + #xmlel{name = <<"option">>, + attrs = + [{<<"label">>, + ?T(Lang, <<"RAM and disc copy">>)}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, <<"disc_copies">>}]}]}, + #xmlel{name = <<"option">>, + attrs = + [{<<"label">>, ?T(Lang, <<"Disc only copy">>)}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, + <<"disc_only_copies">>}]}]}, + #xmlel{name = <<"option">>, + attrs = [{<<"label">>, ?T(Lang, <<"Remote copy">>)}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, <<"unknown">>}]}]}]}). - - -get_form(_Host, ["running nodes", ENode, "DB"], Lang) -> +get_form(_Host, [<<"running nodes">>, ENode, <<"DB">>], + Lang) -> case search_running_node(ENode) of - false -> - {error, ?ERR_ITEM_NOT_FOUND}; - Node -> - case rpc:call(Node, mnesia, system_info, [tables]) of - {badrpc, _Reason} -> - {error, ?ERR_INTERNAL_SERVER_ERROR}; - Tables -> - STables = lists:sort(Tables), - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - {xmlelement, "title", [], - [{xmlcdata, - ?T( - Lang, "Database Tables Configuration at ") ++ - ENode}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - ?T( - Lang, "Choose storage type of tables")}]} | - lists:map( - fun(Table) -> - case rpc:call(Node, - mnesia, - table_info, - [Table, storage_type]) of - {badrpc, _} -> - ?TABLEFIELD(Table, unknown); - Type -> - ?TABLEFIELD(Table, Type) - end - end, STables) - ]}]} - end + false -> {error, ?ERR_ITEM_NOT_FOUND}; + Node -> + case rpc:call(Node, mnesia, system_info, [tables]) of + {badrpc, _Reason} -> + {error, ?ERR_INTERNAL_SERVER_ERROR}; + Tables -> + STables = lists:sort(Tables), + {result, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + #xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, + <<(?T(Lang, + <<"Database Tables Configuration at ">>))/binary, + ENode/binary>>}]}, + #xmlel{name = <<"instructions">>, attrs = [], + children = + [{xmlcdata, + ?T(Lang, + <<"Choose storage type of tables">>)}]} + | lists:map(fun (Table) -> + case rpc:call(Node, mnesia, + table_info, + [Table, + storage_type]) + of + {badrpc, _} -> + ?TABLEFIELD(Table, + unknown); + Type -> + ?TABLEFIELD(Table, Type) + end + end, + STables)]}]} + end end; - -get_form(Host, ["running nodes", ENode, "modules", "stop"], Lang) -> +get_form(Host, + [<<"running nodes">>, ENode, <<"modules">>, <<"stop">>], + Lang) -> case search_running_node(ENode) of - false -> - {error, ?ERR_ITEM_NOT_FOUND}; - Node -> - case rpc:call(Node, gen_mod, loaded_modules, [Host]) of - {badrpc, _Reason} -> - {error, ?ERR_INTERNAL_SERVER_ERROR}; - Modules -> - SModules = lists:sort(Modules), - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - {xmlelement, "title", [], - [{xmlcdata, - ?T( - Lang, "Stop Modules at ") ++ ENode}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - ?T( - Lang, "Choose modules to stop")}]} | - lists:map(fun(M) -> - S = atom_to_list(M), - ?XFIELD("boolean", S, S, "0") - end, SModules) - ]}]} - end + false -> {error, ?ERR_ITEM_NOT_FOUND}; + Node -> + case rpc:call(Node, gen_mod, loaded_modules, [Host]) of + {badrpc, _Reason} -> + {error, ?ERR_INTERNAL_SERVER_ERROR}; + Modules -> + SModules = lists:sort(Modules), + {result, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + #xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, + <<(?T(Lang, + <<"Stop Modules at ">>))/binary, + ENode/binary>>}]}, + #xmlel{name = <<"instructions">>, attrs = [], + children = + [{xmlcdata, + ?T(Lang, + <<"Choose modules to stop">>)}]} + | lists:map(fun (M) -> + S = jlib:atom_to_binary(M), + ?XFIELD(<<"boolean">>, S, S, + <<"0">>) + end, + SModules)]}]} + end end; - -get_form(_Host, ["running nodes", ENode, "modules", "start"], Lang) -> - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - {xmlelement, "title", [], - [{xmlcdata, - ?T( - Lang, "Start Modules at ") ++ ENode}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - ?T( - Lang, "Enter list of {Module, [Options]}")}]}, - ?XFIELD("text-multi", "List of modules to start", "modules", "[].") - ]}]}; - -get_form(_Host, ["running nodes", ENode, "backup", "backup"], Lang) -> - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - {xmlelement, "title", [], - [{xmlcdata, - ?T( - Lang, "Backup to File at ") ++ ENode}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - ?T( - Lang, "Enter path to backup file")}]}, - ?XFIELD("text-single", "Path to File", "path", "") - ]}]}; - -get_form(_Host, ["running nodes", ENode, "backup", "restore"], Lang) -> - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - {xmlelement, "title", [], - [{xmlcdata, - ?T( - Lang, "Restore Backup from File at ") ++ ENode}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - ?T( - Lang, "Enter path to backup file")}]}, - ?XFIELD("text-single", "Path to File", "path", "") - ]}]}; - -get_form(_Host, ["running nodes", ENode, "backup", "textfile"], Lang) -> - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - {xmlelement, "title", [], - [{xmlcdata, - ?T( - Lang, "Dump Backup to Text File at ") ++ ENode}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - ?T( - Lang, "Enter path to text file")}]}, - ?XFIELD("text-single", "Path to File", "path", "") - ]}]}; - -get_form(_Host, ["running nodes", ENode, "import", "file"], Lang) -> - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - {xmlelement, "title", [], - [{xmlcdata, - ?T( - Lang, "Import User from File at ") ++ ENode}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - ?T( - Lang, "Enter path to jabberd14 spool file")}]}, - ?XFIELD("text-single", "Path to File", "path", "") - ]}]}; - -get_form(_Host, ["running nodes", ENode, "import", "dir"], Lang) -> - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - {xmlelement, "title", [], - [{xmlcdata, - ?T( - Lang, "Import Users from Dir at ") ++ ENode}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - ?T( - Lang, "Enter path to jabberd14 spool dir")}]}, - ?XFIELD("text-single", "Path to Dir", "path", "") - ]}]}; - -get_form(_Host, ["running nodes", _ENode, "restart"], Lang) -> - Make_option = - fun(LabelNum, LabelUnit, Value)-> - {xmlelement, "option", - [{"label", LabelNum ++ ?T(Lang, LabelUnit)}], - [{xmlelement, "value", [], [{xmlcdata, Value}]}]} - end, - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - {xmlelement, "title", [], - [{xmlcdata, ?T(Lang, "Restart Service")}]}, - {xmlelement, "field", - [{"type", "list-single"}, - {"label", ?T(Lang, "Time delay")}, - {"var", "delay"}], - [Make_option("", "immediately", "1"), - Make_option("15 ", "seconds", "15"), - Make_option("30 ", "seconds", "30"), - Make_option("60 ", "seconds", "60"), - Make_option("90 ", "seconds", "90"), - Make_option("2 ", "minutes", "120"), - Make_option("3 ", "minutes", "180"), - Make_option("4 ", "minutes", "240"), - Make_option("5 ", "minutes", "300"), - Make_option("10 ", "minutes", "600"), - Make_option("15 ", "minutes", "900"), - Make_option("30 ", "minutes", "1800"), - {xmlelement, "required", [], []} - ]}, - {xmlelement, "field", - [{"type", "fixed"}, - {"label", ?T(Lang, "Send announcement to all online users on all hosts")}], - []}, - {xmlelement, "field", - [{"var", "subject"}, - {"type", "text-single"}, - {"label", ?T(Lang, "Subject")}], - []}, - {xmlelement, "field", - [{"var", "announcement"}, - {"type", "text-multi"}, - {"label", ?T(Lang, "Message body")}], - []} - ]}]}; - -get_form(_Host, ["running nodes", _ENode, "shutdown"], Lang) -> - Make_option = - fun(LabelNum, LabelUnit, Value)-> - {xmlelement, "option", - [{"label", LabelNum ++ ?T(Lang, LabelUnit)}], - [{xmlelement, "value", [], [{xmlcdata, Value}]}]} - end, - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - {xmlelement, "title", [], - [{xmlcdata, ?T(Lang, "Shut Down Service")}]}, - {xmlelement, "field", - [{"type", "list-single"}, - {"label", ?T(Lang, "Time delay")}, - {"var", "delay"}], - [Make_option("", "immediately", "1"), - Make_option("15 ", "seconds", "15"), - Make_option("30 ", "seconds", "30"), - Make_option("60 ", "seconds", "60"), - Make_option("90 ", "seconds", "90"), - Make_option("2 ", "minutes", "120"), - Make_option("3 ", "minutes", "180"), - Make_option("4 ", "minutes", "240"), - Make_option("5 ", "minutes", "300"), - Make_option("10 ", "minutes", "600"), - Make_option("15 ", "minutes", "900"), - Make_option("30 ", "minutes", "1800"), - {xmlelement, "required", [], []} - ]}, - {xmlelement, "field", - [{"type", "fixed"}, - {"label", ?T(Lang, "Send announcement to all online users on all hosts")}], - []}, - {xmlelement, "field", - [{"var", "subject"}, - {"type", "text-single"}, - {"label", ?T(Lang, "Subject")}], - []}, - {xmlelement, "field", - [{"var", "announcement"}, - {"type", "text-multi"}, - {"label", ?T(Lang, "Message body")}], - []} - ]}]}; - -get_form(Host, ["config", "acls"], Lang) -> - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - {xmlelement, "title", [], - [{xmlcdata, - ?T( - Lang, "Access Control List Configuration")}]}, - {xmlelement, "field", [{"type", "text-multi"}, - {"label", - ?T( - Lang, "Access control lists")}, - {"var", "acls"}], - lists:map(fun(S) -> - {xmlelement, "value", [], [{xmlcdata, S}]} - end, - string:tokens( - lists:flatten( - io_lib:format( - "~p.", - [ets:select(acl, - [{{acl, {'$1', '$2'}, '$3'}, - [{'==', '$2', Host}], - [{{acl, '$1', '$3'}}]}]) - ])), - "\n")) - } - ]}]}; - -get_form(Host, ["config", "access"], Lang) -> - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - {xmlelement, "title", [], - [{xmlcdata, - ?T( - Lang, "Access Configuration")}]}, - {xmlelement, "field", [{"type", "text-multi"}, - {"label", - ?T( - Lang, "Access rules")}, - {"var", "access"}], - lists:map(fun(S) -> - {xmlelement, "value", [], [{xmlcdata, S}]} - end, - string:tokens( - lists:flatten( - io_lib:format( - "~p.", - [ets:select(config, - [{{config, {access, '$1', '$2'}, '$3'}, - [{'==', '$2', Host}], - [{{access, '$1', '$3'}}]}]) - ])), - "\n")) - } - ]}]}; - -get_form(_Host, ?NS_ADMINL("add-user"), Lang) -> - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - {xmlelement, "title", [], - [{xmlcdata, ?T(Lang, "Add User")}]}, - {xmlelement, "field", - [{"type", "jid-single"}, - {"label", ?T(Lang, "Jabber ID")}, - {"var", "accountjid"}], - [{xmlelement, "required", [], []}]}, - {xmlelement, "field", - [{"type", "text-private"}, - {"label", ?T(Lang, "Password")}, - {"var", "password"}], - [{xmlelement, "required", [], []}]}, - {xmlelement, "field", - [{"type", "text-private"}, - {"label", ?T(Lang, "Password Verification")}, - {"var", "password-verify"}], - [{xmlelement, "required", [], []}]} - ]}]}; - -get_form(_Host, ?NS_ADMINL("delete-user"), Lang) -> - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - {xmlelement, "title", [], - [{xmlcdata, ?T(Lang, "Delete User")}]}, - {xmlelement, "field", - [{"type", "jid-multi"}, - {"label", ?T(Lang, "Jabber ID")}, - {"var", "accountjids"}], - [{xmlelement, "required", [], []}]} - ]}]}; - -get_form(_Host, ?NS_ADMINL("end-user-session"), Lang) -> - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - {xmlelement, "title", [], - [{xmlcdata, ?T(Lang, "End User Session")}]}, - {xmlelement, "field", - [{"type", "jid-single"}, - {"label", ?T(Lang, "Jabber ID")}, - {"var", "accountjid"}], - [{xmlelement, "required", [], []}]} - ]}]}; - -get_form(_Host, ?NS_ADMINL("get-user-password"), Lang) -> - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - {xmlelement, "title", [], - [{xmlcdata, ?T(Lang, "Get User Password")}]}, - {xmlelement, "field", - [{"type", "jid-single"}, - {"label", ?T(Lang, "Jabber ID")}, - {"var", "accountjid"}], - [{xmlelement, "required", [], []}]} - ]}]}; - -get_form(_Host, ?NS_ADMINL("change-user-password"), Lang) -> - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - {xmlelement, "title", [], - [{xmlcdata, ?T(Lang, "Get User Password")}]}, - {xmlelement, "field", - [{"type", "jid-single"}, - {"label", ?T(Lang, "Jabber ID")}, - {"var", "accountjid"}], - [{xmlelement, "required", [], []}]}, - {xmlelement, "field", - [{"type", "text-private"}, - {"label", ?T(Lang, "Password")}, - {"var", "password"}], - [{xmlelement, "required", [], []}]} - ]}]}; - -get_form(_Host, ?NS_ADMINL("get-user-lastlogin"), Lang) -> - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - {xmlelement, "title", [], - [{xmlcdata, ?T(Lang, "Get User Last Login Time")}]}, - {xmlelement, "field", - [{"type", "jid-single"}, - {"label", ?T(Lang, "Jabber ID")}, - {"var", "accountjid"}], - [{xmlelement, "required", [], []}]} - ]}]}; - -get_form(_Host, ?NS_ADMINL("user-stats"), Lang) -> - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - {xmlelement, "title", [], - [{xmlcdata, ?T(Lang, "Get User Statistics")}]}, - {xmlelement, "field", - [{"type", "jid-single"}, - {"label", ?T(Lang, "Jabber ID")}, - {"var", "accountjid"}], - [{xmlelement, "required", [], []}]} - ]}]}; - -get_form(Host, ?NS_ADMINL("get-registered-users-num"), Lang) -> - [Num] = io_lib:format("~p", [ejabberd_auth:get_vh_registered_users_number(Host)]), +get_form(_Host, + [<<"running nodes">>, ENode, <<"modules">>, + <<"start">>], + Lang) -> + {result, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + #xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, + <<(?T(Lang, <<"Start Modules at ">>))/binary, + ENode/binary>>}]}, + #xmlel{name = <<"instructions">>, attrs = [], + children = + [{xmlcdata, + ?T(Lang, + <<"Enter list of {Module, [Options]}">>)}]}, + ?XFIELD(<<"text-multi">>, + <<"List of modules to start">>, <<"modules">>, + <<"[].">>)]}]}; +get_form(_Host, + [<<"running nodes">>, ENode, <<"backup">>, + <<"backup">>], + Lang) -> + {result, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + #xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, + <<(?T(Lang, <<"Backup to File at ">>))/binary, + ENode/binary>>}]}, + #xmlel{name = <<"instructions">>, attrs = [], + children = + [{xmlcdata, + ?T(Lang, <<"Enter path to backup file">>)}]}, + ?XFIELD(<<"text-single">>, <<"Path to File">>, + <<"path">>, <<"">>)]}]}; +get_form(_Host, + [<<"running nodes">>, ENode, <<"backup">>, + <<"restore">>], + Lang) -> + {result, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + #xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, + <<(?T(Lang, + <<"Restore Backup from File at ">>))/binary, + ENode/binary>>}]}, + #xmlel{name = <<"instructions">>, attrs = [], + children = + [{xmlcdata, + ?T(Lang, <<"Enter path to backup file">>)}]}, + ?XFIELD(<<"text-single">>, <<"Path to File">>, + <<"path">>, <<"">>)]}]}; +get_form(_Host, + [<<"running nodes">>, ENode, <<"backup">>, + <<"textfile">>], + Lang) -> + {result, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + #xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, + <<(?T(Lang, + <<"Dump Backup to Text File at ">>))/binary, + ENode/binary>>}]}, + #xmlel{name = <<"instructions">>, attrs = [], + children = + [{xmlcdata, + ?T(Lang, <<"Enter path to text file">>)}]}, + ?XFIELD(<<"text-single">>, <<"Path to File">>, + <<"path">>, <<"">>)]}]}; +get_form(_Host, + [<<"running nodes">>, ENode, <<"import">>, <<"file">>], + Lang) -> + {result, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + #xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, + <<(?T(Lang, + <<"Import User from File at ">>))/binary, + ENode/binary>>}]}, + #xmlel{name = <<"instructions">>, attrs = [], + children = + [{xmlcdata, + ?T(Lang, + <<"Enter path to jabberd14 spool file">>)}]}, + ?XFIELD(<<"text-single">>, <<"Path to File">>, + <<"path">>, <<"">>)]}]}; +get_form(_Host, + [<<"running nodes">>, ENode, <<"import">>, <<"dir">>], + Lang) -> + {result, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + #xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, + <<(?T(Lang, + <<"Import Users from Dir at ">>))/binary, + ENode/binary>>}]}, + #xmlel{name = <<"instructions">>, attrs = [], + children = + [{xmlcdata, + ?T(Lang, + <<"Enter path to jabberd14 spool dir">>)}]}, + ?XFIELD(<<"text-single">>, <<"Path to Dir">>, + <<"path">>, <<"">>)]}]}; +get_form(_Host, + [<<"running nodes">>, _ENode, <<"restart">>], Lang) -> + Make_option = fun (LabelNum, LabelUnit, Value) -> + #xmlel{name = <<"option">>, + attrs = + [{<<"label">>, + <>}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Value}]}]} + end, + {result, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + #xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, ?T(Lang, <<"Restart Service">>)}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"list-single">>}, + {<<"label">>, ?T(Lang, <<"Time delay">>)}, + {<<"var">>, <<"delay">>}], + children = + [Make_option(<<"">>, <<"immediately">>, <<"1">>), + Make_option(<<"15 ">>, <<"seconds">>, <<"15">>), + Make_option(<<"30 ">>, <<"seconds">>, <<"30">>), + Make_option(<<"60 ">>, <<"seconds">>, <<"60">>), + Make_option(<<"90 ">>, <<"seconds">>, <<"90">>), + Make_option(<<"2 ">>, <<"minutes">>, <<"120">>), + Make_option(<<"3 ">>, <<"minutes">>, <<"180">>), + Make_option(<<"4 ">>, <<"minutes">>, <<"240">>), + Make_option(<<"5 ">>, <<"minutes">>, <<"300">>), + Make_option(<<"10 ">>, <<"minutes">>, <<"600">>), + Make_option(<<"15 ">>, <<"minutes">>, <<"900">>), + Make_option(<<"30 ">>, <<"minutes">>, <<"1800">>), + #xmlel{name = <<"required">>, attrs = [], + children = []}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"fixed">>}, + {<<"label">>, + ?T(Lang, + <<"Send announcement to all online users " + "on all hosts">>)}], + children = []}, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"subject">>}, + {<<"type">>, <<"text-single">>}, + {<<"label">>, ?T(Lang, <<"Subject">>)}], + children = []}, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"announcement">>}, + {<<"type">>, <<"text-multi">>}, + {<<"label">>, ?T(Lang, <<"Message body">>)}], + children = []}]}]}; +get_form(_Host, + [<<"running nodes">>, _ENode, <<"shutdown">>], Lang) -> + Make_option = fun (LabelNum, LabelUnit, Value) -> + #xmlel{name = <<"option">>, + attrs = + [{<<"label">>, + <>}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Value}]}]} + end, + {result, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + #xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, ?T(Lang, <<"Shut Down Service">>)}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"list-single">>}, + {<<"label">>, ?T(Lang, <<"Time delay">>)}, + {<<"var">>, <<"delay">>}], + children = + [Make_option(<<"">>, <<"immediately">>, <<"1">>), + Make_option(<<"15 ">>, <<"seconds">>, <<"15">>), + Make_option(<<"30 ">>, <<"seconds">>, <<"30">>), + Make_option(<<"60 ">>, <<"seconds">>, <<"60">>), + Make_option(<<"90 ">>, <<"seconds">>, <<"90">>), + Make_option(<<"2 ">>, <<"minutes">>, <<"120">>), + Make_option(<<"3 ">>, <<"minutes">>, <<"180">>), + Make_option(<<"4 ">>, <<"minutes">>, <<"240">>), + Make_option(<<"5 ">>, <<"minutes">>, <<"300">>), + Make_option(<<"10 ">>, <<"minutes">>, <<"600">>), + Make_option(<<"15 ">>, <<"minutes">>, <<"900">>), + Make_option(<<"30 ">>, <<"minutes">>, <<"1800">>), + #xmlel{name = <<"required">>, attrs = [], + children = []}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"fixed">>}, + {<<"label">>, + ?T(Lang, + <<"Send announcement to all online users " + "on all hosts">>)}], + children = []}, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"subject">>}, + {<<"type">>, <<"text-single">>}, + {<<"label">>, ?T(Lang, <<"Subject">>)}], + children = []}, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"announcement">>}, + {<<"type">>, <<"text-multi">>}, + {<<"label">>, ?T(Lang, <<"Message body">>)}], + children = []}]}]}; +get_form(Host, [<<"config">>, <<"acls">>], Lang) -> + {result, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + #xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, + ?T(Lang, + <<"Access Control List Configuration">>)}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"text-multi">>}, + {<<"label">>, + ?T(Lang, <<"Access control lists">>)}, + {<<"var">>, <<"acls">>}], + children = + lists:map(fun (S) -> + #xmlel{name = <<"value">>, + attrs = [], + children = + [{xmlcdata, S}]} + end, + str:tokens(iolist_to_binary(io_lib:format("~p.", + [ets:select(acl, + [{{acl, + {'$1', + '$2'}, + '$3'}, + [{'==', + '$2', + Host}], + [{{acl, + '$1', + '$3'}}]}])])), + <<"\n">>))}]}]}; +get_form(Host, [<<"config">>, <<"access">>], Lang) -> + {result, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + #xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, + ?T(Lang, <<"Access Configuration">>)}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"text-multi">>}, + {<<"label">>, ?T(Lang, <<"Access rules">>)}, + {<<"var">>, <<"access">>}], + children = + lists:map(fun (S) -> + #xmlel{name = <<"value">>, + attrs = [], + children = + [{xmlcdata, S}]} + end, + str:tokens(iolist_to_binary(io_lib:format("~p.", + [ets:select(config, + [{{config, + {access, + '$1', + '$2'}, + '$3'}, + [{'==', + '$2', + Host}], + [{{access, + '$1', + '$3'}}]}])])), + <<"\n">>))}]}]}; +get_form(_Host, ?NS_ADMINL(<<"add-user">>), Lang) -> + {result, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + #xmlel{name = <<"title">>, attrs = [], + children = [{xmlcdata, ?T(Lang, <<"Add User">>)}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"jid-single">>}, + {<<"label">>, ?T(Lang, <<"Jabber ID">>)}, + {<<"var">>, <<"accountjid">>}], + children = + [#xmlel{name = <<"required">>, attrs = [], + children = []}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"text-private">>}, + {<<"label">>, ?T(Lang, <<"Password">>)}, + {<<"var">>, <<"password">>}], + children = + [#xmlel{name = <<"required">>, attrs = [], + children = []}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"text-private">>}, + {<<"label">>, + ?T(Lang, <<"Password Verification">>)}, + {<<"var">>, <<"password-verify">>}], + children = + [#xmlel{name = <<"required">>, attrs = [], + children = []}]}]}]}; +get_form(_Host, ?NS_ADMINL(<<"delete-user">>), Lang) -> + {result, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + #xmlel{name = <<"title">>, attrs = [], + children = [{xmlcdata, ?T(Lang, <<"Delete User">>)}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"jid-multi">>}, + {<<"label">>, ?T(Lang, <<"Jabber ID">>)}, + {<<"var">>, <<"accountjids">>}], + children = + [#xmlel{name = <<"required">>, attrs = [], + children = []}]}]}]}; +get_form(_Host, ?NS_ADMINL(<<"end-user-session">>), + Lang) -> + {result, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + #xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, ?T(Lang, <<"End User Session">>)}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"jid-single">>}, + {<<"label">>, ?T(Lang, <<"Jabber ID">>)}, + {<<"var">>, <<"accountjid">>}], + children = + [#xmlel{name = <<"required">>, attrs = [], + children = []}]}]}]}; +get_form(_Host, ?NS_ADMINL(<<"get-user-password">>), + Lang) -> + {result, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + #xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, ?T(Lang, <<"Get User Password">>)}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"jid-single">>}, + {<<"label">>, ?T(Lang, <<"Jabber ID">>)}, + {<<"var">>, <<"accountjid">>}], + children = + [#xmlel{name = <<"required">>, attrs = [], + children = []}]}]}]}; +get_form(_Host, ?NS_ADMINL(<<"change-user-password">>), + Lang) -> + {result, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + #xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, ?T(Lang, <<"Get User Password">>)}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"jid-single">>}, + {<<"label">>, ?T(Lang, <<"Jabber ID">>)}, + {<<"var">>, <<"accountjid">>}], + children = + [#xmlel{name = <<"required">>, attrs = [], + children = []}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"text-private">>}, + {<<"label">>, ?T(Lang, <<"Password">>)}, + {<<"var">>, <<"password">>}], + children = + [#xmlel{name = <<"required">>, attrs = [], + children = []}]}]}]}; +get_form(_Host, ?NS_ADMINL(<<"get-user-lastlogin">>), + Lang) -> + {result, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + #xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, + ?T(Lang, <<"Get User Last Login Time">>)}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"jid-single">>}, + {<<"label">>, ?T(Lang, <<"Jabber ID">>)}, + {<<"var">>, <<"accountjid">>}], + children = + [#xmlel{name = <<"required">>, attrs = [], + children = []}]}]}]}; +get_form(_Host, ?NS_ADMINL(<<"user-stats">>), Lang) -> + {result, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + #xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, ?T(Lang, <<"Get User Statistics">>)}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"jid-single">>}, + {<<"label">>, ?T(Lang, <<"Jabber ID">>)}, + {<<"var">>, <<"accountjid">>}], + children = + [#xmlel{name = <<"required">>, attrs = [], + children = []}]}]}]}; +get_form(Host, + ?NS_ADMINL(<<"get-registered-users-num">>), Lang) -> + Num = list_to_binary( + io_lib:format("~p", + [ejabberd_auth:get_vh_registered_users_number(Host)])), {result, completed, - [{xmlelement, "x", - [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - {xmlelement, - "field", - [{"type", "text-single"}, - {"label", ?T(Lang, "Number of registered users")}, - {"var", "registeredusersnum"}], - [{xmlelement, "value", [], [{xmlcdata, Num}]}] - }]}]}; - -get_form(Host, ?NS_ADMINL("get-online-users-num"), Lang) -> - Num = io_lib:format("~p", [length(ejabberd_sm:get_vh_session_list(Host))]), + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"text-single">>}, + {<<"label">>, + ?T(Lang, <<"Number of registered users">>)}, + {<<"var">>, <<"registeredusersnum">>}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Num}]}]}]}]}; +get_form(Host, ?NS_ADMINL(<<"get-online-users-num">>), + Lang) -> + Num = list_to_binary( + io_lib:format("~p", + [length(ejabberd_sm:get_vh_session_list(Host))])), {result, completed, - [{xmlelement, "x", - [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - {xmlelement, - "field", - [{"type", "text-single"}, - {"label", ?T(Lang, "Number of online users")}, - {"var", "onlineusersnum"}], - [{xmlelement, "value", [], [{xmlcdata, Num}]}] - }]}]}; - + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"text-single">>}, + {<<"label">>, + ?T(Lang, <<"Number of online users">>)}, + {<<"var">>, <<"onlineusersnum">>}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Num}]}]}]}]}; get_form(_Host, _, _Lang) -> {error, ?ERR_SERVICE_UNAVAILABLE}. - - -set_form(_From, _Host, ["running nodes", ENode, "DB"], _Lang, XData) -> +set_form(_From, _Host, + [<<"running nodes">>, ENode, <<"DB">>], _Lang, XData) -> case search_running_node(ENode) of - false -> - {error, ?ERR_ITEM_NOT_FOUND}; - Node -> - lists:foreach( - fun({SVar, SVals}) -> - %% We believe that this is allowed only for good people - Table = list_to_atom(SVar), - Type = case SVals of - ["unknown"] -> unknown; - ["ram_copies"] -> ram_copies; - ["disc_copies"] -> disc_copies; - ["disc_only_copies"] -> disc_only_copies; - _ -> false - end, - if - Type == false -> - ok; - Type == unknown -> - mnesia:del_table_copy(Table, Node); - true -> - case mnesia:add_table_copy(Table, Node, Type) of - {aborted, _} -> - mnesia:change_table_copy_type( - Table, Node, Type); - _ -> - ok - end - end - end, XData), - {result, []} + false -> {error, ?ERR_ITEM_NOT_FOUND}; + Node -> + lists:foreach(fun ({SVar, SVals}) -> + Table = jlib:binary_to_atom(SVar), + Type = case SVals of + [<<"unknown">>] -> unknown; + [<<"ram_copies">>] -> ram_copies; + [<<"disc_copies">>] -> disc_copies; + [<<"disc_only_copies">>] -> + disc_only_copies; + _ -> false + end, + if Type == false -> ok; + Type == unknown -> + mnesia:del_table_copy(Table, Node); + true -> + case mnesia:add_table_copy(Table, Node, + Type) + of + {aborted, _} -> + mnesia:change_table_copy_type(Table, + Node, + Type); + _ -> ok + end + end + end, + XData), + {result, []} end; - -set_form(_From, Host, ["running nodes", ENode, "modules", "stop"], _Lang, XData) -> +set_form(_From, Host, + [<<"running nodes">>, ENode, <<"modules">>, <<"stop">>], + _Lang, XData) -> case search_running_node(ENode) of - false -> - {error, ?ERR_ITEM_NOT_FOUND}; - Node -> - lists:foreach( - fun({Var, Vals}) -> - case Vals of - ["1"] -> - Module = list_to_atom(Var), - rpc:call(Node, gen_mod, stop_module, [Host, Module]); - _ -> - ok - end - end, XData), - {result, []} + false -> {error, ?ERR_ITEM_NOT_FOUND}; + Node -> + lists:foreach(fun ({Var, Vals}) -> + case Vals of + [<<"1">>] -> + Module = jlib:binary_to_atom(Var), + rpc:call(Node, gen_mod, stop_module, + [Host, Module]); + _ -> ok + end + end, + XData), + {result, []} end; - -set_form(_From, Host, ["running nodes", ENode, "modules", "start"], _Lang, XData) -> +set_form(_From, Host, + [<<"running nodes">>, ENode, <<"modules">>, + <<"start">>], + _Lang, XData) -> case search_running_node(ENode) of - false -> - {error, ?ERR_ITEM_NOT_FOUND}; - Node -> - case lists:keysearch("modules", 1, XData) of - false -> - {error, ?ERR_BAD_REQUEST}; - {value, {_, Strings}} -> - String = lists:foldl(fun(S, Res) -> - Res ++ S ++ "\n" - end, "", Strings), - case erl_scan:string(String) of - {ok, Tokens, _} -> - case erl_parse:parse_term(Tokens) of - {ok, Modules} -> - lists:foreach( - fun({Module, Args}) -> - rpc:call(Node, - gen_mod, - start_module, - [Host, Module, Args]) - end, Modules), - {result, []}; - _ -> - {error, ?ERR_BAD_REQUEST} - end; - _ -> - {error, ?ERR_BAD_REQUEST} - end - end + false -> {error, ?ERR_ITEM_NOT_FOUND}; + Node -> + case lists:keysearch(<<"modules">>, 1, XData) of + false -> {error, ?ERR_BAD_REQUEST}; + {value, {_, Strings}} -> + String = lists:foldl(fun (S, Res) -> + <> + end, + <<"">>, Strings), + case erl_scan:string(binary_to_list(String)) of + {ok, Tokens, _} -> + case erl_parse:parse_term(Tokens) of + {ok, Modules} -> + lists:foreach(fun ({Module, Args}) -> + rpc:call(Node, gen_mod, + start_module, + [Host, Module, Args]) + end, + Modules), + {result, []}; + _ -> {error, ?ERR_BAD_REQUEST} + end; + _ -> {error, ?ERR_BAD_REQUEST} + end + end end; - - -set_form(_From, _Host, ["running nodes", ENode, "backup", "backup"], _Lang, XData) -> +set_form(_From, _Host, + [<<"running nodes">>, ENode, <<"backup">>, + <<"backup">>], + _Lang, XData) -> case search_running_node(ENode) of - false -> - {error, ?ERR_ITEM_NOT_FOUND}; - Node -> - case lists:keysearch("path", 1, XData) of - false -> - {error, ?ERR_BAD_REQUEST}; - {value, {_, [String]}} -> - case rpc:call(Node, mnesia, backup, [String]) of - {badrpc, _Reason} -> - {error, ?ERR_INTERNAL_SERVER_ERROR}; - {error, _Reason} -> - {error, ?ERR_INTERNAL_SERVER_ERROR}; - _ -> - {result, []} - end; - _ -> - {error, ?ERR_BAD_REQUEST} - end + false -> {error, ?ERR_ITEM_NOT_FOUND}; + Node -> + case lists:keysearch(<<"path">>, 1, XData) of + false -> {error, ?ERR_BAD_REQUEST}; + {value, {_, [String]}} -> + case rpc:call(Node, mnesia, backup, [String]) of + {badrpc, _Reason} -> + {error, ?ERR_INTERNAL_SERVER_ERROR}; + {error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR}; + _ -> {result, []} + end; + _ -> {error, ?ERR_BAD_REQUEST} + end end; - - -set_form(_From, _Host, ["running nodes", ENode, "backup", "restore"], _Lang, XData) -> +set_form(_From, _Host, + [<<"running nodes">>, ENode, <<"backup">>, + <<"restore">>], + _Lang, XData) -> case search_running_node(ENode) of - false -> - {error, ?ERR_ITEM_NOT_FOUND}; - Node -> - case lists:keysearch("path", 1, XData) of - false -> - {error, ?ERR_BAD_REQUEST}; - {value, {_, [String]}} -> - case rpc:call(Node, ejabberd_admin, restore, [String]) of - {badrpc, _Reason} -> - {error, ?ERR_INTERNAL_SERVER_ERROR}; - {error, _Reason} -> - {error, ?ERR_INTERNAL_SERVER_ERROR}; - _ -> - {result, []} - end; - _ -> - {error, ?ERR_BAD_REQUEST} - end + false -> {error, ?ERR_ITEM_NOT_FOUND}; + Node -> + case lists:keysearch(<<"path">>, 1, XData) of + false -> {error, ?ERR_BAD_REQUEST}; + {value, {_, [String]}} -> + case rpc:call(Node, ejabberd_admin, restore, [String]) + of + {badrpc, _Reason} -> + {error, ?ERR_INTERNAL_SERVER_ERROR}; + {error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR}; + _ -> {result, []} + end; + _ -> {error, ?ERR_BAD_REQUEST} + end end; - - -set_form(_From, _Host, ["running nodes", ENode, "backup", "textfile"], _Lang, XData) -> +set_form(_From, _Host, + [<<"running nodes">>, ENode, <<"backup">>, + <<"textfile">>], + _Lang, XData) -> case search_running_node(ENode) of - false -> - {error, ?ERR_ITEM_NOT_FOUND}; - Node -> - case lists:keysearch("path", 1, XData) of - false -> - {error, ?ERR_BAD_REQUEST}; - {value, {_, [String]}} -> - case rpc:call(Node, ejabberd_admin, dump_to_textfile, [String]) of - {badrpc, _Reason} -> - {error, ?ERR_INTERNAL_SERVER_ERROR}; - {error, _Reason} -> - {error, ?ERR_INTERNAL_SERVER_ERROR}; - _ -> - {result, []} - end; - _ -> - {error, ?ERR_BAD_REQUEST} - end + false -> {error, ?ERR_ITEM_NOT_FOUND}; + Node -> + case lists:keysearch(<<"path">>, 1, XData) of + false -> {error, ?ERR_BAD_REQUEST}; + {value, {_, [String]}} -> + case rpc:call(Node, ejabberd_admin, dump_to_textfile, + [String]) + of + {badrpc, _Reason} -> + {error, ?ERR_INTERNAL_SERVER_ERROR}; + {error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR}; + _ -> {result, []} + end; + _ -> {error, ?ERR_BAD_REQUEST} + end end; - - -set_form(_From, _Host, ["running nodes", ENode, "import", "file"], _Lang, XData) -> +set_form(_From, _Host, + [<<"running nodes">>, ENode, <<"import">>, <<"file">>], + _Lang, XData) -> case search_running_node(ENode) of - false -> - {error, ?ERR_ITEM_NOT_FOUND}; - Node -> - case lists:keysearch("path", 1, XData) of - false -> - {error, ?ERR_BAD_REQUEST}; - {value, {_, [String]}} -> - rpc:call(Node, jd2ejd, import_file, [String]), - {result, []}; - _ -> - {error, ?ERR_BAD_REQUEST} - end + false -> {error, ?ERR_ITEM_NOT_FOUND}; + Node -> + case lists:keysearch(<<"path">>, 1, XData) of + false -> {error, ?ERR_BAD_REQUEST}; + {value, {_, [String]}} -> + rpc:call(Node, jd2ejd, import_file, [String]), + {result, []}; + _ -> {error, ?ERR_BAD_REQUEST} + end end; - - -set_form(_From, _Host, ["running nodes", ENode, "import", "dir"], _Lang, XData) -> +set_form(_From, _Host, + [<<"running nodes">>, ENode, <<"import">>, <<"dir">>], + _Lang, XData) -> case search_running_node(ENode) of - false -> - {error, ?ERR_ITEM_NOT_FOUND}; - Node -> - case lists:keysearch("path", 1, XData) of - false -> - {error, ?ERR_BAD_REQUEST}; - {value, {_, [String]}} -> - rpc:call(Node, jd2ejd, import_dir, [String]), - {result, []}; - _ -> - {error, ?ERR_BAD_REQUEST} - end + false -> {error, ?ERR_ITEM_NOT_FOUND}; + Node -> + case lists:keysearch(<<"path">>, 1, XData) of + false -> {error, ?ERR_BAD_REQUEST}; + {value, {_, [String]}} -> + rpc:call(Node, jd2ejd, import_dir, [String]), + {result, []}; + _ -> {error, ?ERR_BAD_REQUEST} + end end; - -set_form(From, Host, ["running nodes", ENode, "restart"], _Lang, XData) -> +set_form(From, Host, + [<<"running nodes">>, ENode, <<"restart">>], _Lang, + XData) -> stop_node(From, Host, ENode, restart, XData); - -set_form(From, Host, ["running nodes", ENode, "shutdown"], _Lang, XData) -> +set_form(From, Host, + [<<"running nodes">>, ENode, <<"shutdown">>], _Lang, + XData) -> stop_node(From, Host, ENode, stop, XData); - -set_form(_From, Host, ["config", "acls"], _Lang, XData) -> - case lists:keysearch("acls", 1, XData) of - {value, {_, Strings}} -> - String = lists:foldl(fun(S, Res) -> - Res ++ S ++ "\n" - end, "", Strings), - case erl_scan:string(String) of - {ok, Tokens, _} -> - case erl_parse:parse_term(Tokens) of - {ok, ACLs} -> - case acl:add_list(Host, ACLs, true) of - ok -> - {result, []}; - _ -> - {error, ?ERR_BAD_REQUEST} - end; - _ -> - {error, ?ERR_BAD_REQUEST} - end; - _ -> - {error, ?ERR_BAD_REQUEST} - end; - _ -> - {error, ?ERR_BAD_REQUEST} +set_form(_From, Host, [<<"config">>, <<"acls">>], _Lang, + XData) -> + case lists:keysearch(<<"acls">>, 1, XData) of + {value, {_, Strings}} -> + String = lists:foldl(fun (S, Res) -> + <> + end, + <<"">>, Strings), + case erl_scan:string(binary_to_list(String)) of + {ok, Tokens, _} -> + case erl_parse:parse_term(Tokens) of + {ok, ACLs} -> + case acl:add_list(Host, ACLs, true) of + ok -> {result, []}; + _ -> {error, ?ERR_BAD_REQUEST} + end; + _ -> {error, ?ERR_BAD_REQUEST} + end; + _ -> {error, ?ERR_BAD_REQUEST} + end; + _ -> {error, ?ERR_BAD_REQUEST} end; - -set_form(_From, Host, ["config", "access"], _Lang, XData) -> - SetAccess = - fun(Rs) -> - mnesia:transaction( - fun() -> - Os = mnesia:select(config, - [{{config, {access, '$1', '$2'}, '$3'}, - [{'==', '$2', Host}], - ['$_']}]), - lists:foreach(fun(O) -> - mnesia:delete_object(O) - end, Os), - lists:foreach( - fun({access, Name, Rules}) -> - mnesia:write({config, - {access, Name, Host}, - Rules}) - end, Rs) - end) - end, - case lists:keysearch("access", 1, XData) of - {value, {_, Strings}} -> - String = lists:foldl(fun(S, Res) -> - Res ++ S ++ "\n" - end, "", Strings), - case erl_scan:string(String) of - {ok, Tokens, _} -> - case erl_parse:parse_term(Tokens) of - {ok, Rs} -> - case SetAccess(Rs) of - {atomic, _} -> - {result, []}; - _ -> - {error, ?ERR_BAD_REQUEST} - end; - _ -> - {error, ?ERR_BAD_REQUEST} - end; - _ -> - {error, ?ERR_BAD_REQUEST} - end; - _ -> - {error, ?ERR_BAD_REQUEST} +set_form(_From, Host, [<<"config">>, <<"access">>], + _Lang, XData) -> + SetAccess = fun (Rs) -> + mnesia:transaction(fun () -> + Os = mnesia:select(config, + [{{config, + {access, + '$1', + '$2'}, + '$3'}, + [{'==', + '$2', + Host}], + ['$_']}]), + lists:foreach(fun (O) -> + mnesia:delete_object(O) + end, + Os), + lists:foreach(fun ({access, + Name, + Rules}) -> + mnesia:write({config, + {access, + Name, + Host}, + Rules}) + end, + Rs) + end) + end, + case lists:keysearch(<<"access">>, 1, XData) of + {value, {_, Strings}} -> + String = lists:foldl(fun (S, Res) -> + <> + end, + <<"">>, Strings), + case erl_scan:string(binary_to_list(String)) of + {ok, Tokens, _} -> + case erl_parse:parse_term(Tokens) of + {ok, Rs} -> + case SetAccess(Rs) of + {atomic, _} -> {result, []}; + _ -> {error, ?ERR_BAD_REQUEST} + end; + _ -> {error, ?ERR_BAD_REQUEST} + end; + _ -> {error, ?ERR_BAD_REQUEST} + end; + _ -> {error, ?ERR_BAD_REQUEST} end; - -set_form(From, Host, ?NS_ADMINL("add-user"), _Lang, XData) -> - AccountString = get_value("accountjid", XData), - Password = get_value("password", XData), - Password = get_value("password-verify", XData), +set_form(From, Host, ?NS_ADMINL(<<"add-user">>), _Lang, + XData) -> + AccountString = get_value(<<"accountjid">>, XData), + Password = get_value(<<"password">>, XData), + Password = get_value(<<"password-verify">>, XData), AccountJID = jlib:string_to_jid(AccountString), User = AccountJID#jid.luser, Server = AccountJID#jid.lserver, true = lists:member(Server, ?MYHOSTS), - true = (Server == Host) orelse (get_permission_level(From) == global), + true = Server == Host orelse + get_permission_level(From) == global, ejabberd_auth:try_register(User, Server, Password), {result, []}; - -set_form(From, Host, ?NS_ADMINL("delete-user"), _Lang, XData) -> - AccountStringList = get_values("accountjids", XData), - [_|_] = AccountStringList, - ASL2 = lists:map( - fun(AccountString) -> - JID = jlib:string_to_jid(AccountString), - [_|_] = JID#jid.luser, - User = JID#jid.luser, - Server = JID#jid.lserver, - true = (Server == Host) orelse (get_permission_level(From) == global), - true = ejabberd_auth:is_user_exists(User, Server), - {User, Server} - end, - AccountStringList), - [ejabberd_auth:remove_user(User, Server) || {User, Server} <- ASL2], +set_form(From, Host, ?NS_ADMINL(<<"delete-user">>), + _Lang, XData) -> + AccountStringList = get_values(<<"accountjids">>, + XData), + [_ | _] = AccountStringList, + ASL2 = lists:map(fun (AccountString) -> + JID = jlib:string_to_jid(AccountString), + User = JID#jid.luser, + Server = JID#jid.lserver, + true = Server == Host orelse + get_permission_level(From) == global, + true = ejabberd_auth:is_user_exists(User, Server), + {User, Server} + end, + AccountStringList), + [ejabberd_auth:remove_user(User, Server) + || {User, Server} <- ASL2], {result, []}; - -set_form(From, Host, ?NS_ADMINL("end-user-session"), _Lang, XData) -> - AccountString = get_value("accountjid", XData), +set_form(From, Host, ?NS_ADMINL(<<"end-user-session">>), + _Lang, XData) -> + AccountString = get_value(<<"accountjid">>, XData), JID = jlib:string_to_jid(AccountString), - [_|_] = JID#jid.luser, - LUser = JID#jid.luser, - LServer = JID#jid.lserver, - true = (LServer == Host) orelse (get_permission_level(From) == global), - %% Code copied from ejabberd_sm.erl + LUser = JID#jid.luser, + LServer = JID#jid.lserver, + true = LServer == Host orelse + get_permission_level(From) == global, case JID#jid.lresource of - [] -> - SIDs = mnesia:dirty_select(session, - [{#session{sid = '$1', usr = {LUser, LServer, '_'}, _ = '_'}, [], ['$1']}]), - [Pid ! replaced || {_, Pid} <- SIDs]; - R -> - [{_, Pid}] = mnesia:dirty_select(session, - [{#session{sid = '$1', usr = {LUser, LServer, R}, _ = '_'}, [], ['$1']}]), - Pid ! replaced - end, + <<>> -> + SIDs = mnesia:dirty_select(session, + [{#session{sid = '$1', + usr = {LUser, LServer, '_'}, + _ = '_'}, + [], ['$1']}]), + [Pid ! replaced || {_, Pid} <- SIDs]; + R -> + [{_, Pid}] = mnesia:dirty_select(session, + [{#session{sid = '$1', + usr = {LUser, LServer, R}, + _ = '_'}, + [], ['$1']}]), + Pid ! replaced + end, {result, []}; - -set_form(From, Host, ?NS_ADMINL("get-user-password"), Lang, XData) -> - AccountString = get_value("accountjid", XData), +set_form(From, Host, + ?NS_ADMINL(<<"get-user-password">>), Lang, XData) -> + AccountString = get_value(<<"accountjid">>, XData), JID = jlib:string_to_jid(AccountString), - [_|_] = JID#jid.luser, - User = JID#jid.luser, - Server = JID#jid.lserver, - true = (Server == Host) orelse (get_permission_level(From) == global), + User = JID#jid.luser, + Server = JID#jid.lserver, + true = Server == Host orelse + get_permission_level(From) == global, Password = ejabberd_auth:get_password(User, Server), - true = is_list(Password), - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - ?XFIELD("jid-single", "Jabber ID", "accountjid", AccountString), - ?XFIELD("text-single", "Password", "password", Password) - ]}]}; - -set_form(From, Host, ?NS_ADMINL("change-user-password"), _Lang, XData) -> - AccountString = get_value("accountjid", XData), - Password = get_value("password", XData), + true = is_binary(Password), + {result, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + ?XFIELD(<<"jid-single">>, <<"Jabber ID">>, + <<"accountjid">>, AccountString), + ?XFIELD(<<"text-single">>, <<"Password">>, + <<"password">>, Password)]}]}; +set_form(From, Host, + ?NS_ADMINL(<<"change-user-password">>), _Lang, XData) -> + AccountString = get_value(<<"accountjid">>, XData), + Password = get_value(<<"password">>, XData), JID = jlib:string_to_jid(AccountString), - [_|_] = JID#jid.luser, - User = JID#jid.luser, - Server = JID#jid.lserver, - true = (Server == Host) orelse (get_permission_level(From) == global), + User = JID#jid.luser, + Server = JID#jid.lserver, + true = Server == Host orelse + get_permission_level(From) == global, true = ejabberd_auth:is_user_exists(User, Server), ejabberd_auth:set_password(User, Server, Password), {result, []}; - -set_form(From, Host, ?NS_ADMINL("get-user-lastlogin"), Lang, XData) -> - AccountString = get_value("accountjid", XData), +set_form(From, Host, + ?NS_ADMINL(<<"get-user-lastlogin">>), Lang, XData) -> + AccountString = get_value(<<"accountjid">>, XData), JID = jlib:string_to_jid(AccountString), - [_|_] = JID#jid.luser, - User = JID#jid.luser, - Server = JID#jid.lserver, - true = (Server == Host) orelse (get_permission_level(From) == global), - - %% Code copied from web/ejabberd_web_admin.erl - %% TODO: Update time format to XEP-0202: Entity Time - FLast = - case ejabberd_sm:get_user_resources(User, Server) of - [] -> - _US = {User, Server}, - case get_last_info(User, Server) of - not_found -> - ?T(Lang, "Never"); + User = JID#jid.luser, + Server = JID#jid.lserver, + true = Server == Host orelse + get_permission_level(From) == global, + FLast = case ejabberd_sm:get_user_resources(User, + Server) + of + [] -> + _US = {User, Server}, + case get_last_info(User, Server) of + not_found -> ?T(Lang, <<"Never">>); {ok, Timestamp, _Status} -> Shift = Timestamp, - TimeStamp = {Shift div 1000000, - Shift rem 1000000, - 0}, + TimeStamp = {Shift div 1000000, Shift rem 1000000, 0}, {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:now_to_local_time(TimeStamp), - lists:flatten( - io_lib:format( - "~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w", - [Year, Month, Day, Hour, Minute, Second])) - end; - _ -> - ?T(Lang, "Online") - end, - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "result"}], - [?HFIELD(), - ?XFIELD("jid-single", "Jabber ID", "accountjid", AccountString), - ?XFIELD("text-single", "Last login", "lastlogin", FLast) - ]}]}; - -set_form(From, Host, ?NS_ADMINL("user-stats"), Lang, XData) -> - AccountString = get_value("accountjid", XData), + iolist_to_binary(io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w", + [Year, Month, Day, Hour, + Minute, Second])) + end; + _ -> ?T(Lang, <<"Online">>) + end, + {result, + [#xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}], + children = + [?HFIELD(), + ?XFIELD(<<"jid-single">>, <<"Jabber ID">>, + <<"accountjid">>, AccountString), + ?XFIELD(<<"text-single">>, <<"Last login">>, + <<"lastlogin">>, FLast)]}]}; +set_form(From, Host, ?NS_ADMINL(<<"user-stats">>), Lang, + XData) -> + AccountString = get_value(<<"accountjid">>, XData), JID = jlib:string_to_jid(AccountString), - [_|_] = JID#jid.luser, - User = JID#jid.luser, - Server = JID#jid.lserver, - true = (Server == Host) orelse (get_permission_level(From) == global), - - Resources = ejabberd_sm:get_user_resources(User, Server), - IPs1 = [ejabberd_sm:get_user_ip(User, Server, Resource) || Resource <- Resources], - IPs = [inet_parse:ntoa(IP)++":"++integer_to_list(Port) || {IP, Port} <- IPs1], - - Items = ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]), - Rostersize = integer_to_list(erlang:length(Items)), - - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - ?XFIELD("jid-single", "Jabber ID", "accountjid", AccountString), - ?XFIELD("text-single", "Roster size", "rostersize", Rostersize), - ?XMFIELD("text-multi", "IP addresses", "ipaddresses", IPs), - ?XMFIELD("text-multi", "Resources", "onlineresources", Resources) - ]}]}; - + User = JID#jid.luser, + Server = JID#jid.lserver, + true = Server == Host orelse + get_permission_level(From) == global, + Resources = ejabberd_sm:get_user_resources(User, + Server), + IPs1 = [ejabberd_sm:get_user_ip(User, Server, Resource) + || Resource <- Resources], + IPs = [<<(jlib:ip_to_list(IP))/binary, ":", + (jlib:integer_to_binary(Port))/binary>> + || {IP, Port} <- IPs1], + Items = ejabberd_hooks:run_fold(roster_get, Server, [], + [{User, Server}]), + Rostersize = jlib:integer_to_binary(erlang:length(Items)), + {result, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + ?XFIELD(<<"jid-single">>, <<"Jabber ID">>, + <<"accountjid">>, AccountString), + ?XFIELD(<<"text-single">>, <<"Roster size">>, + <<"rostersize">>, Rostersize), + ?XMFIELD(<<"text-multi">>, <<"IP addresses">>, + <<"ipaddresses">>, IPs), + ?XMFIELD(<<"text-multi">>, <<"Resources">>, + <<"onlineresources">>, Resources)]}]}; set_form(_From, _Host, _, _Lang, _XData) -> {error, ?ERR_SERVICE_UNAVAILABLE}. -get_value(Field, XData) -> - hd(get_values(Field, XData)). -get_values(Field, XData) -> - {value, {_, ValueList}} = lists:keysearch(Field, 1, XData), +get_value(Field, XData) -> hd(get_values(Field, XData)). + +get_values(Field, XData) -> + {value, {_, ValueList}} = lists:keysearch(Field, 1, + XData), ValueList. - search_running_node(SNode) -> - search_running_node(SNode, mnesia:system_info(running_db_nodes)). + search_running_node(SNode, + mnesia:system_info(running_db_nodes)). -search_running_node(_, []) -> - false; +search_running_node(_, []) -> false; search_running_node(SNode, [Node | Nodes]) -> - case atom_to_list(Node) of - SNode -> - Node; - _ -> - search_running_node(SNode, Nodes) + case iolist_to_binary(atom_to_list(Node)) of + SNode -> Node; + _ -> search_running_node(SNode, Nodes) end. stop_node(From, Host, ENode, Action, XData) -> - Delay = list_to_integer(get_value("delay", XData)), - Subject = case get_value("subject", XData) of - [] -> []; - S -> [{xmlelement, "field", [{"var","subject"}], - [{xmlelement,"value",[],[{xmlcdata,S}]}]}] + Delay = jlib:binary_to_integer(get_value(<<"delay">>, + XData)), + Subject = case get_value(<<"subject">>, XData) of + <<"">> -> []; + S -> + [#xmlel{name = <<"field">>, + attrs = [{<<"var">>, <<"subject">>}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, S}]}]}] end, - Announcement = case get_values("announcement", XData) of - [] -> []; - As -> [{xmlelement, "field", [{"var","body"}], - [{xmlelement,"value",[],[{xmlcdata,Line}]} || Line <- As] }] + Announcement = case get_values(<<"announcement">>, + XData) + of + [] -> []; + As -> + [#xmlel{name = <<"field">>, + attrs = [{<<"var">>, <<"body">>}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Line}]} + || Line <- As]}] end, case Subject ++ Announcement of - [] -> ok; - SubEls -> - Request = #adhoc_request{ - node = ?NS_ADMINX("announce-allhosts"), - action = "complete", - xdata = {xmlelement, "x", - [{"xmlns","jabber:x:data"},{"type","submit"}], - SubEls}, - others= [{xmlelement, "x", - [{"xmlns","jabber:x:data"},{"type","submit"}], - SubEls}] - }, - To = jlib:make_jid("", Host, ""), - mod_announce:announce_commands(empty, From, To, Request) + [] -> ok; + SubEls -> + Request = #adhoc_request{node = + ?NS_ADMINX(<<"announce-allhosts">>), + action = <<"complete">>, + xdata = + #xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, + <<"jabber:x:data">>}, + {<<"type">>, <<"submit">>}], + children = SubEls}, + others = + [#xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, + <<"jabber:x:data">>}, + {<<"type">>, <<"submit">>}], + children = SubEls}]}, + To = jlib:make_jid(<<"">>, Host, <<"">>), + mod_announce:announce_commands(empty, From, To, Request) end, Time = timer:seconds(Delay), - Node = list_to_atom(ENode), - {ok, _} = timer:apply_after(Time, rpc, call, [Node, init, Action, []]), + Node = jlib:binary_to_atom(ENode), + {ok, _} = timer:apply_after(Time, rpc, call, + [Node, init, Action, []]), {result, []}. - get_last_info(User, Server) -> case gen_mod:is_loaded(Server, mod_last) of - true -> - mod_last:get_last_info(User, Server); - false -> - not_found + true -> mod_last:get_last_info(User, Server); + false -> not_found end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% adhoc_sm_commands(_Acc, From, - #jid{user = User, server = Server, lserver = LServer} = _To, - #adhoc_request{lang = Lang, - node = "config", - action = Action, - xdata = XData} = Request) -> + #jid{user = User, server = Server, lserver = LServer} = + _To, + #adhoc_request{lang = Lang, node = <<"config">>, + action = Action, xdata = XData} = + Request) -> case acl:match_rule(LServer, configure, From) of - deny -> - {error, ?ERR_FORBIDDEN}; - allow -> - %% If the "action" attribute is not present, it is - %% understood as "execute". If there was no - %% element in the first response (which there isn't in our - %% case), "execute" and "complete" are equivalent. - ActionIsExecute = lists:member(Action, - ["", "execute", "complete"]), - if Action == "cancel" -> - %% User cancels request - adhoc:produce_response( - Request, - #adhoc_response{status = canceled}); - XData == false, ActionIsExecute -> - %% User requests form - case get_sm_form(User, Server, "config", Lang) of - {result, Form} -> - adhoc:produce_response( - Request, - #adhoc_response{status = executing, - elements = Form}); - {error, Error} -> - {error, Error} - end; - XData /= false, ActionIsExecute -> - %% User returns form. - case jlib:parse_xdata_submit(XData) of - invalid -> - {error, ?ERR_BAD_REQUEST}; - Fields -> - set_sm_form(User, Server, "config", Request, Fields) - end; - true -> - {error, ?ERR_BAD_REQUEST} - end + deny -> {error, ?ERR_FORBIDDEN}; + allow -> + ActionIsExecute = lists:member(Action, + [<<"">>, <<"execute">>, + <<"complete">>]), + if Action == <<"cancel">> -> + adhoc:produce_response(Request, + #adhoc_response{status = canceled}); + XData == false, ActionIsExecute -> + case get_sm_form(User, Server, <<"config">>, Lang) of + {result, Form} -> + adhoc:produce_response(Request, + #adhoc_response{status = + executing, + elements = Form}); + {error, Error} -> {error, Error} + end; + XData /= false, ActionIsExecute -> + case jlib:parse_xdata_submit(XData) of + invalid -> {error, ?ERR_BAD_REQUEST}; + Fields -> + set_sm_form(User, Server, <<"config">>, Request, Fields) + end; + true -> {error, ?ERR_BAD_REQUEST} + end end; +adhoc_sm_commands(Acc, _From, _To, _Request) -> Acc. -adhoc_sm_commands(Acc, _From, _To, _Request) -> - Acc. - -get_sm_form(User, Server, "config", Lang) -> - {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [?HFIELD(), - {xmlelement, "title", [], - [{xmlcdata, - ?T( - Lang, "Administration of ") ++ User}]}, - {xmlelement, "field", - [{"type", "list-single"}, - {"label", ?T(Lang, "Action on user")}, - {"var", "action"}], - [{xmlelement, "value", [], [{xmlcdata, "edit"}]}, - {xmlelement, "option", - [{"label", ?T(Lang, "Edit Properties")}], - [{xmlelement, "value", [], [{xmlcdata, "edit"}]}]}, - {xmlelement, "option", - [{"label", ?T(Lang, "Remove User")}], - [{xmlelement, "value", [], [{xmlcdata, "remove"}]}]} - ]}, - ?XFIELD("text-private", "Password", "password", - ejabberd_auth:get_password_s(User, Server)) - ]}]}; - +get_sm_form(User, Server, <<"config">>, Lang) -> + {result, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [?HFIELD(), + #xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, + <<(?T(Lang, <<"Administration of ">>))/binary, + User/binary>>}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"list-single">>}, + {<<"label">>, ?T(Lang, <<"Action on user">>)}, + {<<"var">>, <<"action">>}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, <<"edit">>}]}, + #xmlel{name = <<"option">>, + attrs = + [{<<"label">>, + ?T(Lang, <<"Edit Properties">>)}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, + <<"edit">>}]}]}, + #xmlel{name = <<"option">>, + attrs = + [{<<"label">>, + ?T(Lang, <<"Remove User">>)}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, + <<"remove">>}]}]}]}, + ?XFIELD(<<"text-private">>, <<"Password">>, + <<"password">>, + (ejabberd_auth:get_password_s(User, Server)))]}]}; get_sm_form(_User, _Server, _Node, _Lang) -> {error, ?ERR_SERVICE_UNAVAILABLE}. - -set_sm_form(User, Server, "config", - #adhoc_request{lang = Lang, - node = Node, - sessionid = SessionID}, XData) -> - Response = #adhoc_response{lang = Lang, - node = Node, - sessionid = SessionID, - status = completed}, - case lists:keysearch("action", 1, XData) of - {value, {_, ["edit"]}} -> - case lists:keysearch("password", 1, XData) of - {value, {_, [Password]}} -> - ejabberd_auth:set_password(User, Server, Password), - adhoc:produce_response(Response); - _ -> - {error, ?ERR_NOT_ACCEPTABLE} - end; - {value, {_, ["remove"]}} -> - catch ejabberd_auth:remove_user(User, Server), - adhoc:produce_response(Response); - _ -> - {error, ?ERR_NOT_ACCEPTABLE} +set_sm_form(User, Server, <<"config">>, + #adhoc_request{lang = Lang, node = Node, + sessionid = SessionID}, + XData) -> + Response = #adhoc_response{lang = Lang, node = Node, + sessionid = SessionID, status = completed}, + case lists:keysearch(<<"action">>, 1, XData) of + {value, {_, [<<"edit">>]}} -> + case lists:keysearch(<<"password">>, 1, XData) of + {value, {_, [Password]}} -> + ejabberd_auth:set_password(User, Server, Password), + adhoc:produce_response(Response); + _ -> {error, ?ERR_NOT_ACCEPTABLE} + end; + {value, {_, [<<"remove">>]}} -> + catch ejabberd_auth:remove_user(User, Server), + adhoc:produce_response(Response); + _ -> {error, ?ERR_NOT_ACCEPTABLE} end; - set_sm_form(_User, _Server, _Node, _Request, _Fields) -> {error, ?ERR_SERVICE_UNAVAILABLE}. - - - diff --git a/src/mod_configure2.erl b/src/mod_configure2.erl index 77bbca57c..366538632 100644 --- a/src/mod_configure2.erl +++ b/src/mod_configure2.erl @@ -25,146 +25,173 @@ %%%---------------------------------------------------------------------- -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]}; - allow -> - case Type of - set -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]}; - %%case xml:get_tag_attr_s("type", SubEl) of - %% "cancel" -> - %% IQ#iq{type = result, - %% sub_el = [{xmlelement, "query", - %% [{"xmlns", XMLNS}], []}]}; - %% "submit" -> - %% XData = jlib:parse_xdata_submit(SubEl), - %% case XData of - %% invalid -> - %% IQ#iq{type = error, - %% sub_el = [SubEl, ?ERR_BAD_REQUEST]}; - %% _ -> - %% Node = - %% string:tokens( - %% xml:get_tag_attr_s("node", SubEl), - %% "/"), - %% case set_form(Node, Lang, XData) of - %% {result, Res} -> - %% IQ#iq{type = result, - %% sub_el = [{xmlelement, "query", - %% [{"xmlns", XMLNS}], - %% Res - %% }]}; - %% {error, Error} -> - %% IQ#iq{type = error, - %% sub_el = [SubEl, Error]} - %% end - %% end; - %% _ -> - %% IQ#iq{type = error, - %% sub_el = [SubEl, ?ERR_NOT_ALLOWED]} - %%end; - get -> - case process_get(SubEl) of - {result, Res} -> - IQ#iq{type = result, sub_el = [Res]}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end - end + deny -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; + allow -> + case Type of + set -> + IQ#iq{type = error, + sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]}; + %%case xml:get_tag_attr_s("type", SubEl) of + %% "cancel" -> + %% IQ#iq{type = result, + %% sub_el = [{xmlelement, "query", + %% [{"xmlns", XMLNS}], []}]}; + %% "submit" -> + %% XData = jlib:parse_xdata_submit(SubEl), + %% case XData of + %% invalid -> + %% IQ#iq{type = error, + %% sub_el = [SubEl, ?ERR_BAD_REQUEST]}; + %% _ -> + %% Node = + %% string:tokens( + %% xml:get_tag_attr_s("node", SubEl), + %% "/"), + %% case set_form(Node, Lang, XData) of + %% {result, Res} -> + %% IQ#iq{type = result, + %% sub_el = [{xmlelement, "query", + %% [{"xmlns", XMLNS}], + %% Res + %% }]}; + %% {error, Error} -> + %% IQ#iq{type = error, + %% sub_el = [SubEl, Error]} + %% end + %% end; + %% _ -> + %% IQ#iq{type = error, + %% sub_el = [SubEl, ?ERR_NOT_ALLOWED]} + %%end; + get -> + case process_get(SubEl) of + {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; - _ -> {"", ""} - 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.", - [ets:select(config, - [{{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 - {'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}]}} + 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, + #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'}, + [], + [{{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 = 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}. diff --git a/src/mod_disco.erl b/src/mod_disco.erl index 3d623e2b6..c883e0782 100644 --- a/src/mod_disco.erl +++ b/src/mod_disco.erl @@ -25,446 +25,469 @@ %%%---------------------------------------------------------------------- -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), - Host = To#jid.lserver, - - case ejabberd_hooks:run_fold(disco_local_items, - Host, - empty, - [From, To, Node, Lang]) of - {result, Items} -> - ANode = case Node of - "" -> []; - _ -> [{"node", Node}] - end, - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", ?NS_DISCO_ITEMS} | ANode], - Items - }]}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end + set -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; + get -> + 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 + {result, Items} -> + ANode = case Node of + <<"">> -> []; + _ -> [{<<"node">>, Node}] + end, + IQ#iq{type = result, + 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), - Identity = ejabberd_hooks:run_fold(disco_local_identity, - 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 - {result, Features} -> - ANode = case Node of - "" -> []; - _ -> [{"node", Node}] - end, - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", ?NS_DISCO_INFO} | ANode], - Identity ++ - Info ++ - features_to_xml(Features) - }]}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end + set -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; + get -> + Host = To#jid.lserver, + Node = xml:get_tag_attr_s(<<"node">>, SubEl), + Identity = ejabberd_hooks:run_fold(disco_local_identity, + 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 + {result, Features} -> + ANode = case Node of + <<"">> -> []; + _ -> [{<<"node">>, Node}] + end, + IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = + [{<<"xmlns">>, ?NS_DISCO_INFO} | ANode], + children = + Identity ++ + 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 -> [] + {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}}) -> - Feature; - (Feature) when is_list(Feature) -> - Feature - end, FeatureList))]. + [#xmlel{name = <<"feature">>, + attrs = [{<<"var">>, Feat}], children = []} + || Feat + <- lists:usort(lists:map(fun ({{Feature, _Host}}) -> + Feature; + (Feature) when is_binary(Feature) -> + Feature + 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 -> [] + {result, Its} -> Its; + empty -> [] end, Host = To#jid.lserver, {result, - 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) -> + 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) -> Acc; - get_local_services(empty, _From, _To, _Node, _Lang) -> {error, ?ERR_ITEM_NOT_FOUND}. get_vh_services(Host) -> - Hosts = lists:sort(fun(H1, H2) -> length(H1) >= length(H2) end, ?MYHOSTS), - lists:filter(fun(H) -> - case lists:dropwhile( - fun(VH) -> - not lists:suffix("." ++ VH, H) - end, Hosts) of - [] -> - false; - [VH | _] -> - VH == Host + Hosts = lists:sort(fun (H1, H2) -> + byte_size(H1) >= byte_size(H2) + end, + ?MYHOSTS), + lists:filter(fun (H) -> + case lists:dropwhile(fun (VH) -> + not + str:suffix( + <<".", VH/binary>>, + H) + end, + Hosts) + of + [] -> false; + [VH | _] -> VH == Host end - end, ejabberd_router:dirty_get_all_routes()). + end, + ejabberd_router:dirty_get_all_routes()). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -process_sm_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> +process_sm_iq_items(From, To, + #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> case Type of - set -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; - get -> - 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 - {result, Items} -> - ANode = case Node of - "" -> []; - _ -> [{"node", Node}] - end, - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", ?NS_DISCO_ITEMS} | ANode], - Items - }]}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end; - false -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]} - end + set -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; + get -> + 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 + {result, Items} -> + ANode = case Node of + <<"">> -> []; + _ -> [{<<"node">>, Node}] + end, + IQ#iq{type = result, + 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]} + 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 -> [] + {result, Its} -> Its; + empty -> [] end, Items1 = case is_presence_subscribed(From, To) of - true -> - get_user_resources(User, Server); - _ -> - [] - end, + 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 -> - true; - true -> - false - end - end, - ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}])) - orelse User == LUser andalso Server == LServer. +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 + end + end, + 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]}; - get -> - case is_presence_subscribed(From, To) of - true -> - Host = To#jid.lserver, - Node = xml:get_tag_attr_s("node", SubEl), - Identity = ejabberd_hooks:run_fold(disco_sm_identity, - Host, - [], - [From, To, Node, Lang]), - case ejabberd_hooks:run_fold(disco_sm_features, - Host, - empty, - [From, To, Node, Lang]) of - {result, Features} -> - ANode = case Node of - "" -> []; - _ -> [{"node", Node}] - end, - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", ?NS_DISCO_INFO} | ANode], - Identity ++ - 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]} - end - end. - -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"}], []}]; - _ -> - [] + set -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; + get -> + case is_presence_subscribed(From, To) of + true -> + Host = To#jid.lserver, + Node = xml:get_tag_attr_s(<<"node">>, SubEl), + Identity = ejabberd_hooks:run_fold(disco_sm_identity, + Host, [], + [From, To, Node, Lang]), + case ejabberd_hooks:run_fold(disco_sm_features, Host, + empty, [From, To, Node, Lang]) + of + {result, Features} -> + ANode = case Node of + <<"">> -> []; + _ -> [{<<"node">>, Node}] + end, + IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = + [{<<"xmlns">>, ?NS_DISCO_INFO} + | ANode], + children = + Identity ++ + 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]} + end end. +get_sm_identity(Acc, _From, + #jid{luser = LUser, lserver = LServer}, _Node, _Lang) -> + Acc ++ + case ejabberd_auth:is_user_exists(LUser, LServer) of + true -> + [#xmlel{name = <<"identity">>, + attrs = + [{<<"category">>, <<"account">>}, + {<<"type">>, <<"registered">>}], + children = []}]; + _ -> [] + end. get_sm_features(empty, From, To, _Node, _Lang) -> #jid{luser = LFrom, lserver = LSFrom} = From, #jid{luser = LTo, lserver = LSTo} = To, case {LFrom, LSFrom} of - {LTo, LSTo} -> - {error, ?ERR_ITEM_NOT_FOUND}; - _ -> - {error, ?ERR_NOT_ALLOWED} + {LTo, LSTo} -> {error, ?ERR_ITEM_NOT_FOUND}; + _ -> {error, ?ERR_NOT_ALLOWED} end; - -get_sm_features(Acc, _From, _To, _Node, _Lang) -> - Acc. +get_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc. get_user_resources(User, Server) -> Rs = ejabberd_sm:get_user_resources(User, Server), - lists:map(fun(R) -> - {xmlelement, "item", - [{"jid", User ++ "@" ++ Server ++ "/" ++ R}, - {"name", User}], []} - end, lists:sort(Rs)). + lists:map(fun (R) -> + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, + <>}, + {<<"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, _, _}) -> - case Modules of - all -> true; - Modules -> lists:member(Module, Modules) - end - end, - Fields), - + 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) + end + end, + Fields), fields_to_xml(Fields_good). fields_to_xml(Fields) -> - [ field_to_xml(Field) || Field <- Fields]. + [field_to_xml(Field) || Field <- Fields]. field_to_xml({_, Var, Values}) -> Values_xml = values_to_xml(Values), - {xmlelement, "field", - [{"var", Var}], - Values_xml - }. + #xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}], + children = Values_xml}. values_to_xml(Values) -> - lists:map( - fun(Value) -> - {xmlelement, "value", - [], - [{xmlcdata, Value}] - } - end, - Values - ). + lists:map(fun (Value) -> + #xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Value}]} + end, + Values). diff --git a/src/mod_echo.erl b/src/mod_echo.erl index 9c7259481..15df69244 100644 --- a/src/mod_echo.erl +++ b/src/mod_echo.erl @@ -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} | @@ -117,15 +116,15 @@ handle_cast(_Msg, State) -> %% Description: Handling all non call/cast messages %%-------------------------------------------------------------------- handle_info({route, From, To, Packet}, State) -> - Packet2 = case From#jid.user of - "" -> jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST); + Packet2 = case From#jid.user of + <<"">> -> + jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST); _ -> Packet - end, - do_client_version(disabled, To, From), % Put 'enabled' to enable it + end, + 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 - 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"), - List - after 5000 -> % Timeout in miliseconds: 5 seconds - [] + Packet = #xmlel{name = <<"iq">>, + attrs = [{<<"to">>, ToS}, {<<"type">>, <<"get">>}], + children = + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, ?NS_VERSION}], + children = []}]}, + ejabberd_router:route(From2, To, Packet), + 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]). diff --git a/src/mod_ip_blacklist.erl b/src/mod_ip_blacklist.erl index 5d2380c6e..073dd5af0 100644 --- a/src/mod_ip_blacklist.erl +++ b/src/mod_ip_blacklist.erl @@ -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. + Pid = spawn(?MODULE, preinit, [self(), #state{}]), + receive {ok, Pid, PreinitResult} -> PreinitResult end. preinit(Parent, State) -> Pid = self(), try register(?PROCNAME, Pid) of - true -> - Parent ! {ok, Pid, true}, - init(State) - catch error:_ -> - Parent ! {ok, Pid, true} + true -> Parent ! {ok, Pid, true}, init(State) + catch + error:_ -> Parent ! {ok, Pid, true} end. %% TODO: -stop(_Host) -> - ok. +stop(_Host) -> ok. -init(State)-> +init(State) -> inets:start(), - ets:new(bl_c2s, [named_table, public, {keypos, #bl_c2s.ip}]), + ets:new(bl_c2s, + [named_table, public, {keypos, #bl_c2s.ip}]), update_bl_c2s(), - %% Register hooks for blacklist - ejabberd_hooks:add(check_bl_c2s, ?MODULE, is_ip_in_c2s_blacklist, 50), - %% Set timer: Download the blacklist file every 6 hours - timer:apply_interval(timer:hours(?UPDATE_INTERVAL), ?MODULE, update_bl_c2s, []), + ejabberd_hooks:add(check_bl_c2s, ?MODULE, + is_ip_in_c2s_blacklist, 50), + timer:apply_interval(timer:hours(?UPDATE_INTERVAL), + ?MODULE, update_bl_c2s, []), loop(State). %% Remove timer when stop is received. -loop(_State) -> - receive - stop -> - ok - end. +loop(_State) -> receive stop -> ok end. %% Download blacklist file from ProcessOne XAAI %% and update the table internal table @@ -93,16 +89,18 @@ 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"), - ets:delete_all_objects(bl_c2s), - lists:foreach( - fun(IP) -> - ets:insert(bl_c2s, #bl_c2s{ip=list_to_binary(IP)}) - end, IPs); - {error, Reason} -> - ?ERROR_MSG("Cannot download C2S blacklist file. Reason: ~p", - [Reason]) + {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 = IP}) + end, + IPs); + {error, Reason} -> + ?ERROR_MSG("Cannot download C2S blacklist file. " + "Reason: ~p", + [Reason]) end. %% Hook is run with: @@ -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} + [] -> %% Not in blacklist + false; + [_] -> {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. + diff --git a/src/mod_irc/Makefile.in b/src/mod_irc/Makefile.in index e1551f929..9dcf9f182 100644 --- a/src/mod_irc/Makefile.in +++ b/src/mod_irc/Makefile.in @@ -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 diff --git a/src/mod_irc/iconv.erl b/src/mod_irc/iconv.erl index b93bb2ea3..4d8180539 100644 --- a/src/mod_irc/iconv.erl +++ b/src/mod_irc/iconv.erl @@ -25,6 +25,7 @@ %%%---------------------------------------------------------------------- -module(iconv). + -author('alexey@process-one.net'). -behaviour(gen_server). @@ -32,63 +33,50 @@ -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 - ok -> ok; - {error, already_loaded} -> ok + case erl_ddll:load_driver(ejabberd:get_so_path(), + iconv_erl) + of + ok -> ok; + {error, already_loaded} -> ok end, Port = open_port({spawn, "iconv_erl"}, []), ets:new(iconv_table, [set, public, named_table]), 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). diff --git a/src/mod_irc/mod_irc.erl b/src/mod_irc/mod_irc.erl index 554a75c6b..53069671d 100644 --- a/src/mod_irc/mod_irc.erl +++ b/src/mod_irc/mod_irc.erl @@ -25,34 +25,52 @@ %%%---------------------------------------------------------------------- -module(mod_irc). + -author('alexey@process-one.net'). -behaviour(gen_server). + -behaviour(gen_mod). %% API --export([start_link/2, - start/2, - stop/1, - closed_connection/3, - get_connection_params/3]). +-export([start_link/2, start/2, stop/1, export/1, + closed_connection/3, get_connection_params/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"). + -include("adhoc.hrl"). --define(DEFAULT_IRC_ENCODING, "iso8859-1"). +-define(DEFAULT_IRC_ENCODING, <<"iso8859-1">>). + -define(DEFAULT_IRC_PORT, 6667). --define(POSSIBLE_ENCODINGS, ["koi8-r", "iso8859-1", "iso8859-2", "utf-8", "utf-8+latin-1"]). --record(irc_connection, {jid_server_host, pid}). --record(irc_custom, {us_host, data}). +-define(POSSIBLE_ENCODINGS, + [<<"koi8-r">>, <<"iso8859-1">>, <<"iso8859-2">>, + <<"utf-8">>, <<"utf-8+latin-1">>]). --record(state, {host, server_host, access}). +-type conn_param() :: {binary(), binary(), inet:port_number(), binary()} | + {binary(), binary(), inet:port_number()} | + {binary(), binary()}. + +-record(irc_connection, + {jid_server_host = {#jid{}, <<"">>, <<"">>} :: {jid(), binary(), binary()}, + pid = self() :: pid()}). + +-record(irc_custom, + {us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()}, + binary()}, + data = [] :: [{username, binary()} | + {connections_params, [conn_param()]}]}). + +-record(state, {host = <<"">> :: binary(), + server_host = <<"">> :: binary(), + access = all :: atom()}). -define(PROCNAME, ejabberd_mod_irc). @@ -65,18 +83,14 @@ %%-------------------------------------------------------------------- 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) -> start_supervisor(Host), 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) -> @@ -98,25 +112,26 @@ stop(Host) -> %%-------------------------------------------------------------------- init([Host, Opts]) -> iconv:start(), - MyHost = gen_mod:get_opt_host(Host, Opts, "irc.@HOST@"), + MyHost = gen_mod:get_opt_host(Host, Opts, + <<"irc.@HOST@">>), case gen_mod:db_type(Opts) of - mnesia -> - mnesia:create_table(irc_custom, - [{disc_copies, [node()]}, - {attributes, - record_info(fields, irc_custom)}]), - update_table(MyHost); - _ -> - ok + mnesia -> + mnesia:create_table(irc_custom, + [{disc_copies, [node()]}, + {attributes, record_info(fields, irc_custom)}]), + update_table(); + _ -> ok end, - Access = gen_mod:get_opt(access, Opts, all), - catch ets:new(irc_connection, [named_table, - public, - {keypos, #irc_connection.jid_server_host}]), + Access = gen_mod:get_opt(access, Opts, + fun(A) when is_atom(A) -> A end, + all), + catch ets:new(irc_connection, + [named_table, public, + {keypos, #irc_connection.jid_server_host}]), ejabberd_router:register_route(MyHost), - {ok, #state{host = MyHost, - server_host = Host, - access = Access}}. + {ok, + #state{host = MyHost, server_host = Host, + access = Access}}. %%-------------------------------------------------------------------- %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | @@ -136,8 +151,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} | @@ -146,18 +160,17 @@ handle_cast(_Msg, State) -> %% Description: Handling all non call/cast messages %%-------------------------------------------------------------------- handle_info({route, From, To, Packet}, - #state{host = Host, - server_host = ServerHost, - access = Access} = State) -> - case catch do_route(Host, ServerHost, Access, From, To, Packet) of - {'EXIT', Reason} -> - ?ERROR_MSG("~p", [Reason]); - _ -> - ok + #state{host = Host, server_host = ServerHost, + access = Access} = + State) -> + case catch do_route(Host, ServerHost, Access, From, To, + Packet) + of + {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); + _ -> ok end, {noreply, State}; -handle_info(_Info, State) -> - {noreply, State}. +handle_info(_Info, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: terminate(Reason, State) -> void() @@ -167,919 +180,1110 @@ 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}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- start_supervisor(Host) -> - Proc = gen_mod:get_module_proc(Host, ejabberd_mod_irc_sup), - ChildSpec = - {Proc, - {ejabberd_tmp_sup, start_link, - [Proc, mod_irc_connection]}, - permanent, - infinity, - supervisor, - [ejabberd_tmp_sup]}, + Proc = gen_mod:get_module_proc(Host, + ejabberd_mod_irc_sup), + ChildSpec = {Proc, + {ejabberd_tmp_sup, start_link, + [Proc, mod_irc_connection]}, + permanent, infinity, supervisor, [ejabberd_tmp_sup]}, supervisor:start_child(ejabberd_sup, ChildSpec). stop_supervisor(Host) -> - Proc = gen_mod:get_module_proc(Host, ejabberd_mod_irc_sup), + Proc = gen_mod:get_module_proc(Host, + ejabberd_mod_irc_sup), supervisor:terminate_child(ejabberd_sup, Proc), supervisor:delete_child(ejabberd_sup, Proc). do_route(Host, ServerHost, Access, From, To, Packet) -> case acl:match_rule(ServerHost, Access, From) of - allow -> - do_route1(Host, ServerHost, From, To, Packet); - _ -> - {xmlelement, _Name, Attrs, _Els} = Packet, - Lang = xml:get_attr_s("xml:lang", Attrs), - ErrText = "Access denied by service policy", - Err = jlib:make_error_reply(Packet, - ?ERRT_FORBIDDEN(Lang, ErrText)), - ejabberd_router:route(To, From, Err) + allow -> do_route1(Host, ServerHost, From, To, Packet); + _ -> + #xmlel{attrs = Attrs} = Packet, + Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), + ErrText = <<"Access denied by service policy">>, + Err = jlib:make_error_reply(Packet, + ?ERRT_FORBIDDEN(Lang, ErrText)), + ejabberd_router:route(To, From, Err) end. do_route1(Host, ServerHost, From, To, Packet) -> #jid{user = ChanServ, resource = Resource} = To, - {xmlelement, _Name, _Attrs, _Els} = Packet, + #xmlel{} = Packet, case ChanServ of - "" -> - case Resource of - "" -> - case jlib:iq_query_info(Packet) of - #iq{type = get, xmlns = ?NS_DISCO_INFO = XMLNS, - sub_el = SubEl, lang = Lang} = IQ -> - Node = xml:get_tag_attr_s("node", SubEl), - Info = ejabberd_hooks:run_fold( - disco_info, ServerHost, [], - [ServerHost, ?MODULE, "", ""]), - case iq_disco(ServerHost, Node, Lang) of - [] -> - Res = IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - []}]}, - ejabberd_router:route(To, - From, - jlib:iq_to_xml(Res)); - DiscoInfo -> - Res = IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - DiscoInfo ++ Info}]}, - ejabberd_router:route(To, - From, - jlib:iq_to_xml(Res)) - end; - #iq{type = get, xmlns = ?NS_DISCO_ITEMS = XMLNS, - sub_el = SubEl, lang = Lang} = IQ -> - Node = xml:get_tag_attr_s("node", SubEl), - case Node of - [] -> - ResIQ = IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - []}]}, - Res = jlib:iq_to_xml(ResIQ); - "join" -> - ResIQ = IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - []}]}, - Res = jlib:iq_to_xml(ResIQ); - "register" -> - ResIQ = IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - []}]}, - Res = jlib:iq_to_xml(ResIQ); - ?NS_COMMANDS -> - ResIQ = IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}, - {"node", Node}], - command_items(ServerHost, - Host, Lang)}]}, - Res = jlib:iq_to_xml(ResIQ); - _ -> - Res = jlib:make_error_reply( - Packet, ?ERR_ITEM_NOT_FOUND) - end, - ejabberd_router:route(To, - From, - Res); - #iq{xmlns = ?NS_REGISTER} = IQ -> - process_register(ServerHost, Host, From, To, IQ); - #iq{type = get, xmlns = ?NS_VCARD = XMLNS, - lang = Lang} = IQ -> + <<"">> -> + case Resource of + <<"">> -> + case jlib:iq_query_info(Packet) of + #iq{type = get, xmlns = (?NS_DISCO_INFO) = XMLNS, + sub_el = SubEl, lang = Lang} = + IQ -> + Node = xml:get_tag_attr_s(<<"node">>, SubEl), + Info = ejabberd_hooks:run_fold(disco_info, ServerHost, + [], + [ServerHost, ?MODULE, + <<"">>, <<"">>]), + case iq_disco(ServerHost, Node, Lang) of + [] -> Res = IQ#iq{type = result, sub_el = - [{xmlelement, "vCard", - [{"xmlns", XMLNS}], - iq_get_vcard(Lang)}]}, - ejabberd_router:route(To, - From, - jlib:iq_to_xml(Res)); - #iq{type = set, xmlns = ?NS_COMMANDS, - lang = _Lang, sub_el = SubEl} = IQ -> - Request = adhoc:parse_request(IQ), - case lists:keysearch(Request#adhoc_request.node, - 1, commands(ServerHost)) of - {value, {_, _, Function}} -> - case catch Function(From, To, Request) of - {'EXIT', Reason} -> - ?ERROR_MSG("~p~nfor ad-hoc handler of ~p", - [Reason, {From, To, IQ}]), - Res = IQ#iq{type = error, sub_el = [SubEl, - ?ERR_INTERNAL_SERVER_ERROR]}; - ignore -> - Res = ignore; - {error, Error} -> - Res = IQ#iq{type = error, sub_el = [SubEl, Error]}; - Command -> - Res = IQ#iq{type = result, sub_el = [Command]} - end, - if Res /= ignore -> - ejabberd_router:route(To, From, jlib:iq_to_xml(Res)); - true -> - ok - end; - _ -> - Err = jlib:make_error_reply( - Packet, ?ERR_ITEM_NOT_FOUND), - ejabberd_router:route(To, From, Err) - end; - #iq{} = _IQ -> - Err = jlib:make_error_reply( - Packet, ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); + [#xmlel{name = <<"query">>, + attrs = + [{<<"xmlns">>, XMLNS}], + children = []}]}, + ejabberd_router:route(To, From, + jlib:iq_to_xml(Res)); + DiscoInfo -> + Res = IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = + [{<<"xmlns">>, XMLNS}], + children = + DiscoInfo ++ Info}]}, + ejabberd_router:route(To, From, jlib:iq_to_xml(Res)) + end; + #iq{type = get, xmlns = (?NS_DISCO_ITEMS) = XMLNS, + sub_el = SubEl, lang = Lang} = + IQ -> + Node = xml:get_tag_attr_s(<<"node">>, SubEl), + case Node of + <<>> -> + ResIQ = IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = + [{<<"xmlns">>, + XMLNS}], + children = []}]}, + Res = jlib:iq_to_xml(ResIQ); + <<"join">> -> + ResIQ = IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = + [{<<"xmlns">>, + XMLNS}], + children = []}]}, + Res = jlib:iq_to_xml(ResIQ); + <<"register">> -> + ResIQ = IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = + [{<<"xmlns">>, + XMLNS}], + children = []}]}, + Res = jlib:iq_to_xml(ResIQ); + ?NS_COMMANDS -> + ResIQ = IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = + [{<<"xmlns">>, XMLNS}, + {<<"node">>, Node}], + children = + command_items(ServerHost, + Host, + Lang)}]}, + Res = jlib:iq_to_xml(ResIQ); _ -> - ok - end; - _ -> - Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST), - ejabberd_router:route(To, From, Err) - end; - _ -> - case string:tokens(ChanServ, "%") of - [[_ | _] = Channel, [_ | _] = Server] -> - case ets:lookup(irc_connection, {From, Server, Host}) of + Res = jlib:make_error_reply(Packet, + ?ERR_ITEM_NOT_FOUND) + end, + ejabberd_router:route(To, From, Res); + #iq{xmlns = ?NS_REGISTER} = IQ -> + process_register(ServerHost, Host, From, To, IQ); + #iq{type = get, xmlns = (?NS_VCARD) = XMLNS, + lang = Lang} = + IQ -> + Res = IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"vCard">>, + attrs = [{<<"xmlns">>, XMLNS}], + children = iq_get_vcard(Lang)}]}, + ejabberd_router:route(To, From, jlib:iq_to_xml(Res)); + #iq{type = set, xmlns = ?NS_COMMANDS, lang = _Lang, + sub_el = SubEl} = + IQ -> + Request = adhoc:parse_request(IQ), + case lists:keysearch(Request#adhoc_request.node, 1, + commands(ServerHost)) + of + {value, {_, _, Function}} -> + case catch Function(From, To, Request) of + {'EXIT', Reason} -> + ?ERROR_MSG("~p~nfor ad-hoc handler of ~p", + [Reason, {From, To, IQ}]), + Res = IQ#iq{type = error, + sub_el = + [SubEl, + ?ERR_INTERNAL_SERVER_ERROR]}; + ignore -> Res = ignore; + {error, Error} -> + Res = IQ#iq{type = error, + sub_el = [SubEl, Error]}; + Command -> + Res = IQ#iq{type = result, sub_el = [Command]} + end, + if Res /= ignore -> + ejabberd_router:route(To, From, + jlib:iq_to_xml(Res)); + true -> ok + end; + _ -> + Err = jlib:make_error_reply(Packet, + ?ERR_ITEM_NOT_FOUND), + ejabberd_router:route(To, From, Err) + end; + #iq{} = _IQ -> + Err = jlib:make_error_reply(Packet, + ?ERR_FEATURE_NOT_IMPLEMENTED), + ejabberd_router:route(To, From, Err); + _ -> ok + end; + _ -> + Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST), + ejabberd_router:route(To, From, Err) + end; + _ -> + case str:tokens(ChanServ, <<"%">>) of + [<<_, _/binary>> = Channel, <<_, _/binary>> = Server] -> + case ets:lookup(irc_connection, {From, Server, Host}) of + [] -> + ?DEBUG("open new connection~n", []), + {Username, Encoding, Port, Password} = + get_connection_params(Host, ServerHost, From, Server), + ConnectionUsername = case Packet of + %% If the user tries to join a + %% chatroom, the packet for sure + %% contains the desired username. + #xmlel{name = <<"presence">>} -> + Resource; + %% Otherwise, there is no firm + %% conclusion from the packet. + %% Better to use the configured + %% username (which defaults to the + %% username part of the JID). + _ -> Username + end, + {ok, Pid} = mod_irc_connection:start(From, Host, + ServerHost, Server, + ConnectionUsername, + Encoding, Port, + Password, ?MODULE), + ets:insert(irc_connection, + #irc_connection{jid_server_host = + {From, Server, Host}, + pid = Pid}), + mod_irc_connection:route_chan(Pid, Channel, Resource, + Packet), + ok; + [R] -> + Pid = R#irc_connection.pid, + ?DEBUG("send to process ~p~n", [Pid]), + mod_irc_connection:route_chan(Pid, Channel, Resource, + Packet), + ok + end; + _ -> + case str:tokens(ChanServ, <<"!">>) of + [<<_, _/binary>> = Nick, <<_, _/binary>> = Server] -> + case ets:lookup(irc_connection, {From, Server, Host}) of [] -> - ?DEBUG("open new connection~n", []), - {Username, Encoding, Port, Password} = get_connection_params( - Host, ServerHost, From, Server), - ConnectionUsername = - case Packet of - %% If the user tries to join a - %% chatroom, the packet for sure - %% contains the desired username. - {xmlelement, "presence", _, _} -> - Resource; - %% Otherwise, there is no firm - %% conclusion from the packet. - %% Better to use the configured - %% username (which defaults to the - %% username part of the JID). - _ -> - Username - end, - {ok, Pid} = mod_irc_connection:start( - From, Host, ServerHost, Server, - ConnectionUsername, Encoding, Port, - Password, ?MODULE), - ets:insert( - irc_connection, - #irc_connection{jid_server_host = {From, Server, Host}, - pid = Pid}), - mod_irc_connection:route_chan( - Pid, Channel, Resource, Packet), - ok; + Err = jlib:make_error_reply(Packet, + ?ERR_SERVICE_UNAVAILABLE), + ejabberd_router:route(To, From, Err); [R] -> Pid = R#irc_connection.pid, - ?DEBUG("send to process ~p~n", - [Pid]), - mod_irc_connection:route_chan( - Pid, Channel, Resource, Packet), + ?DEBUG("send to process ~p~n", [Pid]), + mod_irc_connection:route_nick(Pid, Nick, Packet), ok - end; - _ -> - case string:tokens(ChanServ, "!") of - [[_ | _] = Nick, [_ | _] = Server] -> - case ets:lookup(irc_connection, {From, Server, Host}) of - [] -> - Err = jlib:make_error_reply( - Packet, ?ERR_SERVICE_UNAVAILABLE), - ejabberd_router:route(To, From, Err); - [R] -> - Pid = R#irc_connection.pid, - ?DEBUG("send to process ~p~n", - [Pid]), - mod_irc_connection:route_nick( - Pid, Nick, Packet), - ok - end; - _ -> - Err = jlib:make_error_reply( - Packet, ?ERR_BAD_REQUEST), - ejabberd_router:route(To, From, Err) - end - end + end; + _ -> + Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST), + ejabberd_router:route(To, From, Err) + end + end end. - closed_connection(Host, From, Server) -> ets:delete(irc_connection, {From, Server, Host}). - -iq_disco(_ServerHost, [], Lang) -> - [{xmlelement, "identity", - [{"category", "conference"}, - {"type", "irc"}, - {"name", translate:translate(Lang, "IRC Transport")}], []}, - {xmlelement, "feature", [{"var", ?NS_DISCO_INFO}], []}, - {xmlelement, "feature", [{"var", ?NS_MUC}], []}, - {xmlelement, "feature", [{"var", ?NS_REGISTER}], []}, - {xmlelement, "feature", [{"var", ?NS_VCARD}], []}, - {xmlelement, "feature", [{"var", ?NS_COMMANDS}], []}]; +iq_disco(_ServerHost, <<>>, Lang) -> + [#xmlel{name = <<"identity">>, + attrs = + [{<<"category">>, <<"conference">>}, + {<<"type">>, <<"irc">>}, + {<<"name">>, + translate:translate(Lang, <<"IRC Transport">>)}], + children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_DISCO_INFO}], children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_MUC}], children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_REGISTER}], children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_VCARD}], children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_COMMANDS}], children = []}]; iq_disco(ServerHost, Node, Lang) -> case lists:keysearch(Node, 1, commands(ServerHost)) of - {value, {_, Name, _}} -> - [{xmlelement, "identity", - [{"category", "automation"}, - {"type", "command-node"}, - {"name", translate:translate(Lang, Name)}], []}, - {xmlelement, "feature", - [{"var", ?NS_COMMANDS}], []}, - {xmlelement, "feature", - [{"var", ?NS_XDATA}], []}]; - _ -> - [] + {value, {_, Name, _}} -> + [#xmlel{name = <<"identity">>, + attrs = + [{<<"category">>, <<"automation">>}, + {<<"type">>, <<"command-node">>}, + {<<"name">>, translate:translate(Lang, Name)}], + children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_COMMANDS}], children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_XDATA}], children = []}]; + _ -> [] end. iq_get_vcard(Lang) -> - [{xmlelement, "FN", [], - [{xmlcdata, "ejabberd/mod_irc"}]}, - {xmlelement, "URL", [], - [{xmlcdata, ?EJABBERD_URI}]}, - {xmlelement, "DESC", [], - [{xmlcdata, translate:translate(Lang, "ejabberd IRC module") ++ - "\nCopyright (c) 2003-2013 ProcessOne"}]}]. + [#xmlel{name = <<"FN">>, attrs = [], + children = [{xmlcdata, <<"ejabberd/mod_irc">>}]}, + #xmlel{name = <<"URL">>, attrs = [], + children = [{xmlcdata, ?EJABBERD_URI}]}, + #xmlel{name = <<"DESC">>, attrs = [], + children = + [{xmlcdata, + <<(translate:translate(Lang, + <<"ejabberd IRC module">>))/binary, + "\nCopyright (c) 2003-2013 ProcessOne">>}]}]. command_items(ServerHost, Host, Lang) -> - lists:map(fun({Node, Name, _Function}) - -> {xmlelement, "item", - [{"jid", Host}, - {"node", Node}, - {"name", translate:translate(Lang, Name)}], []} - end, commands(ServerHost)). + lists:map(fun ({Node, Name, _Function}) -> + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, Host}, {<<"node">>, Node}, + {<<"name">>, + translate:translate(Lang, Name)}], + children = []} + end, + commands(ServerHost)). commands(ServerHost) -> - [{"join", "Join channel", fun adhoc_join/3}, - {"register", "Configure username, encoding, port and password", - fun(From, To, Request) -> - adhoc_register(ServerHost, From, To, Request) + [{<<"join">>, <<"Join channel">>, fun adhoc_join/3}, + {<<"register">>, + <<"Configure username, encoding, port and " + "password">>, + fun (From, To, Request) -> + adhoc_register(ServerHost, From, To, Request) end}]. -process_register(ServerHost, Host, From, To, #iq{} = IQ) -> - case catch process_irc_register(ServerHost, Host, From, To, IQ) of - {'EXIT', Reason} -> - ?ERROR_MSG("~p", [Reason]); - ResIQ -> - if - ResIQ /= ignore -> - ejabberd_router:route(To, From, - jlib:iq_to_xml(ResIQ)); - true -> - ok - end +process_register(ServerHost, Host, From, To, + #iq{} = IQ) -> + case catch process_irc_register(ServerHost, Host, From, + To, IQ) + of + {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); + ResIQ -> + if ResIQ /= ignore -> + ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)); + true -> ok + end end. -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([#xmlel{name = Name, attrs = Attrs, + children = SubEls} + | Els]) -> + case xml:get_attr_s(<<"xmlns">>, Attrs) of + ?NS_XDATA -> + #xmlel{name = Name, attrs = Attrs, children = SubEls}; + _ -> find_xdata_el1(Els) end; - -find_xdata_el1([_ | Els]) -> - find_xdata_el1(Els). +find_xdata_el1([_ | Els]) -> find_xdata_el1(Els). process_irc_register(ServerHost, Host, From, _To, - #iq{type = Type, xmlns = XMLNS, - lang = Lang, sub_el = SubEl} = IQ) -> + #iq{type = Type, xmlns = XMLNS, lang = Lang, + sub_el = SubEl} = + IQ) -> case Type of - set -> - XDataEl = find_xdata_el(SubEl), - case XDataEl of - false -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ACCEPTABLE]}; - {xmlelement, _Name, Attrs, _SubEls} -> - case xml:get_attr_s("type", Attrs) of - "cancel" -> - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], []}]}; - "submit" -> - XData = jlib:parse_xdata_submit(XDataEl), - case XData of - invalid -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_BAD_REQUEST]}; - _ -> - Node = string:tokens( - xml:get_tag_attr_s("node", SubEl), - "/"), - case set_form( - ServerHost, Host, From, - Node, Lang, XData) of - {result, Res} -> - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - Res - }]}; - {error, Error} -> - IQ#iq{type = error, - sub_el = [SubEl, Error]} - end - end; - _ -> + set -> + XDataEl = find_xdata_el(SubEl), + case XDataEl of + false -> + IQ#iq{type = error, + sub_el = [SubEl, ?ERR_NOT_ACCEPTABLE]}; + #xmlel{attrs = Attrs} -> + case xml:get_attr_s(<<"type">>, Attrs) of + <<"cancel">> -> + IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, XMLNS}], + children = []}]}; + <<"submit">> -> + XData = jlib:parse_xdata_submit(XDataEl), + case XData of + invalid -> IQ#iq{type = error, - sub_el = [SubEl, ?ERR_BAD_REQUEST]} - end - end; - get -> - Node = - string:tokens(xml:get_tag_attr_s("node", SubEl), "/"), - case get_form(ServerHost, Host, From, Node, Lang) of - {result, Res} -> - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - Res - }]}; - {error, Error} -> - IQ#iq{type = error, - sub_el = [SubEl, Error]} - end + sub_el = [SubEl, ?ERR_BAD_REQUEST]}; + _ -> + Node = str:tokens(xml:get_tag_attr_s(<<"node">>, + SubEl), + <<"/">>), + case set_form(ServerHost, Host, From, Node, Lang, + XData) + of + {result, Res} -> + IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = + [{<<"xmlns">>, XMLNS}], + children = Res}]}; + {error, Error} -> + IQ#iq{type = error, sub_el = [SubEl, Error]} + end + end; + _ -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]} + end + end; + get -> + Node = str:tokens(xml:get_tag_attr_s(<<"node">>, SubEl), + <<"/">>), + case get_form(ServerHost, Host, From, Node, Lang) of + {result, Res} -> + IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, XMLNS}], + children = Res}]}; + {error, Error} -> + IQ#iq{type = error, sub_el = [SubEl, Error]} + end end. get_data(ServerHost, Host, From) -> LServer = jlib:nameprep(ServerHost), - get_data(LServer, Host, From, gen_mod:db_type(LServer, ?MODULE)). + get_data(LServer, Host, From, + gen_mod:db_type(LServer, ?MODULE)). get_data(_LServer, Host, From, mnesia) -> #jid{luser = LUser, lserver = LServer} = From, US = {LUser, LServer}, - case catch mnesia:dirty_read({irc_custom, {US, Host}}) of - {'EXIT', _Reason} -> - error; - [] -> - empty; - [#irc_custom{data = Data}] -> - Data + case catch mnesia:dirty_read({irc_custom, {US, Host}}) + of + {'EXIT', _Reason} -> error; + [] -> empty; + [#irc_custom{data = Data}] -> Data end; get_data(LServer, Host, From, odbc) -> - SJID = ejabberd_odbc:escape( - jlib:jid_to_string( - jlib:jid_tolower( - jlib:jid_remove_resource(From)))), + SJID = + ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From)))), SHost = ejabberd_odbc:escape(Host), - case catch ejabberd_odbc:sql_query( - LServer, - ["select data from irc_custom where " - "jid='", SJID, "' and host='", SHost, "';"]) of - {selected, ["data"], [{SData}]} -> - ejabberd_odbc:decode_term(SData); - {'EXIT', _} -> - error; - {selected, _, _} -> - empty + case catch ejabberd_odbc:sql_query(LServer, + [<<"select data from irc_custom where jid='">>, + SJID, <<"' and host='">>, SHost, + <<"';">>]) + of + {selected, [<<"data">>], [[SData]]} -> + data_to_binary(ejabberd_odbc:decode_term(SData)); + {'EXIT', _} -> error; + {selected, _, _} -> empty end. get_form(ServerHost, Host, From, [], Lang) -> #jid{user = User, server = Server} = From, DefaultEncoding = get_default_encoding(Host), - Customs = - case get_data(ServerHost, Host, From) of - error -> - {error, ?ERR_INTERNAL_SERVER_ERROR}; - empty -> - {User, []}; - Data -> - {xml:get_attr_s(username, Data), - xml:get_attr_s(connections_params, Data)} - end, + Customs = case get_data(ServerHost, Host, From) of + error -> {error, ?ERR_INTERNAL_SERVER_ERROR}; + empty -> {User, []}; + Data -> get_username_and_connection_params(Data) + end, case Customs of - {error, _Error} -> - Customs; - {Username, ConnectionsParams} -> - {result, - [{xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, - "You need an x:data capable client " - "to configure mod_irc settings")}]}, - {xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [{xmlelement, "title", [], - [{xmlcdata, - translate:translate( - Lang, - "Registration in mod_irc for ") ++ User ++ "@" ++ Server}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, - "Enter username, encodings, ports and passwords you wish to use for " - "connecting to IRC servers")}]}, - {xmlelement, "field", [{"type", "text-single"}, - {"label", - translate:translate( - Lang, "IRC Username")}, - {"var", "username"}], - [{xmlelement, "value", [], [{xmlcdata, Username}]}]}, - {xmlelement, "field", [{"type", "fixed"}], - [{xmlelement, "value", [], - [{xmlcdata, - lists:flatten( - io_lib:format( - translate:translate( - Lang, - "If you want to specify different ports, " - "passwords, encodings for IRC servers, fill " - "this list with values in format " - "'{\"irc server\", \"encoding\", port, \"password\"}'. " - "By default this service use \"~s\" encoding, port ~p, " - "empty password."), - [DefaultEncoding, ?DEFAULT_IRC_PORT]))}]}]}, - {xmlelement, "field", [{"type", "fixed"}], - [{xmlelement, "value", [], - [{xmlcdata, - translate:translate( - Lang, - "Example: [{\"irc.lucky.net\", \"koi8-r\", 6667, \"secret\"}, " - "{\"vendetta.fef.net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}]." - )}]}]}, - {xmlelement, "field", [{"type", "text-multi"}, - {"label", - translate:translate(Lang, "Connections parameters")}, - {"var", "connections_params"}], - lists:map( - fun(S) -> - {xmlelement, "value", [], [{xmlcdata, S}]} - end, - string:tokens( - lists:flatten( - io_lib:format("~p.", [ConnectionsParams])), - "\n")) - } - ]}]} + {error, _Error} -> Customs; + {Username, ConnectionsParams} -> + {result, + [#xmlel{name = <<"instructions">>, attrs = [], + children = + [{xmlcdata, + translate:translate(Lang, + <<"You need an x:data capable client to " + "configure mod_irc settings">>)}]}, + #xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [#xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, + <<(translate:translate(Lang, + <<"Registration in mod_irc for ">>))/binary, + User/binary, "@", Server/binary>>}]}, + #xmlel{name = <<"instructions">>, attrs = [], + children = + [{xmlcdata, + translate:translate(Lang, + <<"Enter username, encodings, ports and " + "passwords you wish to use for connecting " + "to IRC servers">>)}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"text-single">>}, + {<<"label">>, + translate:translate(Lang, + <<"IRC Username">>)}, + {<<"var">>, <<"username">>}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Username}]}]}, + #xmlel{name = <<"field">>, + attrs = [{<<"type">>, <<"fixed">>}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, + iolist_to_binary( + io_lib:format( + translate:translate( + Lang, + <<"If you want to specify" + " different ports, " + "passwords, encodings " + "for IRC servers, " + "fill this list with " + "values in format " + "'{\"irc server\", " + "\"encoding\", port, " + "\"password\"}'. " + "By default this " + "service use \"~s\" " + "encoding, port ~p, " + "empty password.">>), + [DefaultEncoding, + ?DEFAULT_IRC_PORT]))}]}]}, + #xmlel{name = <<"field">>, + attrs = [{<<"type">>, <<"fixed">>}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, + translate:translate(Lang, + <<"Example: [{\"irc.lucky.net\", \"koi8-r\", " + "6667, \"secret\"}, {\"vendetta.fef.net\", " + "\"iso8859-1\", 7000}, {\"irc.sometestserver.n" + "et\", \"utf-8\"}].">>)}]}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"text-multi">>}, + {<<"label">>, + translate:translate(Lang, + <<"Connections parameters">>)}, + {<<"var">>, <<"connections_params">>}], + children = + lists:map(fun (S) -> + #xmlel{name = <<"value">>, + attrs = [], + children = + [{xmlcdata, S}]} + end, + str:tokens(list_to_binary( + io_lib:format( + "~p.", + [conn_params_to_list( + ConnectionsParams)])), + <<"\n">>))}]}]} end; - get_form(_ServerHost, _Host, _, _, _Lang) -> {error, ?ERR_SERVICE_UNAVAILABLE}. - set_data(ServerHost, Host, From, Data) -> LServer = jlib:nameprep(ServerHost), - set_data(LServer, Host, From, Data, gen_mod:db_type(LServer, ?MODULE)). + set_data(LServer, Host, From, data_to_binary(Data), + gen_mod:db_type(LServer, ?MODULE)). set_data(_LServer, Host, From, Data, mnesia) -> {LUser, LServer, _} = jlib:jid_tolower(From), US = {LUser, LServer}, - F = fun() -> - mnesia:write(#irc_custom{us_host = {US, Host}, data = Data}) - end, + F = fun () -> + mnesia:write(#irc_custom{us_host = {US, Host}, + data = Data}) + end, mnesia:transaction(F); set_data(LServer, Host, From, Data, odbc) -> - SJID = ejabberd_odbc:escape( - jlib:jid_to_string( - jlib:jid_tolower( - jlib:jid_remove_resource(From)))), + SJID = + ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From)))), SHost = ejabberd_odbc:escape(Host), SData = ejabberd_odbc:encode_term(Data), - F = fun() -> - odbc_queries:update_t( - "irc_custom", - ["jid", "host", "data"], - [SJID, SHost, SData], - ["jid='", SJID, - "' and host='", - SHost, "'"]), - ok - end, + F = fun () -> + odbc_queries:update_t(<<"irc_custom">>, + [<<"jid">>, <<"host">>, <<"data">>], + [SJID, SHost, SData], + [<<"jid='">>, SJID, <<"' and host='">>, + SHost, <<"'">>]), + ok + end, ejabberd_odbc:sql_transaction(LServer, F). set_form(ServerHost, Host, From, [], _Lang, XData) -> - case {lists:keysearch("username", 1, XData), - lists:keysearch("connections_params", 1, XData)} of - {{value, {_, [Username]}}, {value, {_, Strings}}} -> - EncString = lists:foldl(fun(S, Res) -> - Res ++ S ++ "\n" - end, "", Strings), - case erl_scan:string(EncString) of - {ok, Tokens, _} -> - case erl_parse:parse_term(Tokens) of - {ok, ConnectionsParams} -> - case set_data(ServerHost, Host, From, - [{username, - Username}, - {connections_params, - ConnectionsParams}]) of - {atomic, _} -> - {result, []}; - _ -> - {error, ?ERR_NOT_ACCEPTABLE} - end; - _ -> - {error, ?ERR_NOT_ACCEPTABLE} - end; - _ -> - {error, ?ERR_NOT_ACCEPTABLE} - end; - _ -> - {error, ?ERR_NOT_ACCEPTABLE} + case {lists:keysearch(<<"username">>, 1, XData), + lists:keysearch(<<"connections_params">>, 1, XData)} + of + {{value, {_, [Username]}}, {value, {_, Strings}}} -> + EncString = lists:foldl(fun (S, Res) -> + <> + end, + <<"">>, Strings), + case erl_scan:string(binary_to_list(EncString)) of + {ok, Tokens, _} -> + case erl_parse:parse_term(Tokens) of + {ok, ConnectionsParams} -> + case set_data(ServerHost, Host, From, + [{username, Username}, + {connections_params, ConnectionsParams}]) + of + {atomic, _} -> {result, []}; + _ -> {error, ?ERR_NOT_ACCEPTABLE} + end; + _ -> {error, ?ERR_NOT_ACCEPTABLE} + end; + _ -> {error, ?ERR_NOT_ACCEPTABLE} + end; + _ -> {error, ?ERR_NOT_ACCEPTABLE} end; - - set_form(_ServerHost, _Host, _, _, _Lang, _XData) -> {error, ?ERR_SERVICE_UNAVAILABLE}. - %% Host = "irc.example.com" %% ServerHost = "example.com" get_connection_params(Host, From, IRCServer) -> - [_ | HostTail] = string:tokens(Host, "."), - ServerHost = string:join(HostTail, "."), - get_connection_params(Host, ServerHost, From, IRCServer). + [_ | HostTail] = str:tokens(Host, <<".">>), + ServerHost = str:join(HostTail, <<".">>), + get_connection_params(Host, ServerHost, From, + IRCServer). get_default_encoding(ServerHost) -> - Result = gen_mod:get_module_opt( - ServerHost, ?MODULE, default_encoding, - ?DEFAULT_IRC_ENCODING), - ?INFO_MSG("The default_encoding configured for host ~p is: ~p~n", [ServerHost, Result]), + Result = gen_mod:get_module_opt(ServerHost, ?MODULE, default_encoding, + fun iolist_to_binary/1, + ?DEFAULT_IRC_ENCODING), + ?INFO_MSG("The default_encoding configured for " + "host ~p is: ~p~n", + [ServerHost, Result]), Result. -get_connection_params(Host, ServerHost, From, IRCServer) -> +get_connection_params(Host, ServerHost, From, + IRCServer) -> #jid{user = User, server = _Server} = From, DefaultEncoding = get_default_encoding(ServerHost), case get_data(ServerHost, Host, From) of - error -> - {User, DefaultEncoding, ?DEFAULT_IRC_PORT, ""}; - empty -> - {User, DefaultEncoding, ?DEFAULT_IRC_PORT, ""}; - Data -> - Username = xml:get_attr_s(username, Data), - {NewUsername, NewEncoding, NewPort, NewPassword} = - case lists:keysearch(IRCServer, 1, xml:get_attr_s(connections_params, Data)) of - {value, {_, Encoding, Port, Password}} -> - {Username, Encoding, Port, Password}; - {value, {_, Encoding, Port}} -> - {Username, Encoding, Port, ""}; - {value, {_, Encoding}} -> - {Username, Encoding, ?DEFAULT_IRC_PORT, ""}; - _ -> - {Username, DefaultEncoding, ?DEFAULT_IRC_PORT, ""} - end, - {NewUsername, - NewEncoding, - if - NewPort >= 0 andalso NewPort =< 65535 -> - NewPort; - true -> - ?DEFAULT_IRC_PORT - end, - NewPassword} - end. - -adhoc_join(_From, _To, #adhoc_request{action = "cancel"} = Request) -> - adhoc:produce_response(Request, - #adhoc_response{status = canceled}); -adhoc_join(From, To, #adhoc_request{lang = Lang, - node = _Node, - action = _Action, - xdata = XData} = Request) -> - %% Access control has already been taken care of in do_route. - if XData == false -> - Form = - {xmlelement, "x", - [{"xmlns", ?NS_XDATA}, - {"type", "form"}], - [{xmlelement, "title", [], [{xmlcdata, translate:translate(Lang, "Join IRC channel")}]}, - {xmlelement, "field", - [{"var", "channel"}, - {"type", "text-single"}, - {"label", translate:translate(Lang, "IRC channel (don't put the first #)")}], - [{xmlelement, "required", [], []}]}, - {xmlelement, "field", - [{"var", "server"}, - {"type", "text-single"}, - {"label", translate:translate(Lang, "IRC server")}], - [{xmlelement, "required", [], []}]}]}, - adhoc:produce_response(Request, - #adhoc_response{status = executing, - elements = [Form]}); - true -> - case jlib:parse_xdata_submit(XData) of - invalid -> - {error, ?ERR_BAD_REQUEST}; - Fields -> - Channel = case lists:keysearch("channel", 1, Fields) of - {value, {"channel", C}} -> - C; - _ -> - false - end, - Server = case lists:keysearch("server", 1, Fields) of - {value, {"server", S}} -> - S; - _ -> - false - end, - if Channel /= false, - Server /= false -> - RoomJID = Channel ++ "%" ++ Server ++ "@" ++ To#jid.server, - Invite = {xmlelement, "message", [], - [{xmlelement, "x", - [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "invite", - [{"from", jlib:jid_to_string(From)}], - [{xmlelement, "reason", [], - [{xmlcdata, - translate:translate(Lang, - "Join the IRC channel here.")}]}]}]}, - {xmlelement, "x", - [{"xmlns", ?NS_XCONFERENCE}], - [{xmlcdata, translate:translate(Lang, - "Join the IRC channel here.")}]}, - {xmlelement, "body", [], - [{xmlcdata, io_lib:format( - translate:translate(Lang, - "Join the IRC channel in this Jabber ID: ~s"), - [RoomJID])}]}]}, - ejabberd_router:route(jlib:string_to_jid(RoomJID), From, Invite), - adhoc:produce_response(Request, #adhoc_response{status = completed}); - true -> - {error, ?ERR_BAD_REQUEST} - end - end + error -> + {User, DefaultEncoding, ?DEFAULT_IRC_PORT, <<"">>}; + empty -> + {User, DefaultEncoding, ?DEFAULT_IRC_PORT, <<"">>}; + Data -> + {Username, ConnParams} = get_username_and_connection_params(Data), + {NewUsername, NewEncoding, NewPort, NewPassword} = case + lists:keysearch(IRCServer, + 1, + ConnParams) + of + {value, + {_, Encoding, + Port, + Password}} -> + {Username, + Encoding, + Port, + Password}; + {value, + {_, Encoding, + Port}} -> + {Username, + Encoding, + Port, + <<"">>}; + {value, + {_, + Encoding}} -> + {Username, + Encoding, + ?DEFAULT_IRC_PORT, + <<"">>}; + _ -> + {Username, + DefaultEncoding, + ?DEFAULT_IRC_PORT, + <<"">>} + end, + {iolist_to_binary(NewUsername), + iolist_to_binary(NewEncoding), + if NewPort >= 0 andalso NewPort =< 65535 -> NewPort; + true -> ?DEFAULT_IRC_PORT + end, + iolist_to_binary(NewPassword)} end. -adhoc_register(_ServerHost, _From, _To, #adhoc_request{action = "cancel"} = Request) -> +adhoc_join(_From, _To, + #adhoc_request{action = <<"cancel">>} = Request) -> adhoc:produce_response(Request, #adhoc_response{status = canceled}); -adhoc_register(ServerHost, From, To, #adhoc_request{lang = Lang, - node = _Node, - xdata = XData, - action = Action} = Request) -> +adhoc_join(From, To, + #adhoc_request{lang = Lang, node = _Node, + action = _Action, xdata = XData} = + Request) -> + if XData == false -> + Form = #xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, ?NS_XDATA}, + {<<"type">>, <<"form">>}], + children = + [#xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, + translate:translate(Lang, + <<"Join IRC channel">>)}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"channel">>}, + {<<"type">>, <<"text-single">>}, + {<<"label">>, + translate:translate(Lang, + <<"IRC channel (don't put the first #)">>)}], + children = + [#xmlel{name = <<"required">>, + attrs = [], children = []}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"server">>}, + {<<"type">>, <<"text-single">>}, + {<<"label">>, + translate:translate(Lang, + <<"IRC server">>)}], + children = + [#xmlel{name = <<"required">>, + attrs = [], children = []}]}]}, + adhoc:produce_response(Request, + #adhoc_response{status = executing, + elements = [Form]}); + true -> + case jlib:parse_xdata_submit(XData) of + invalid -> {error, ?ERR_BAD_REQUEST}; + Fields -> + Channel = case lists:keysearch(<<"channel">>, 1, Fields) + of + {value, {<<"channel">>, [C]}} -> C; + _ -> false + end, + Server = case lists:keysearch(<<"server">>, 1, Fields) + of + {value, {<<"server">>, [S]}} -> S; + _ -> false + end, + if Channel /= false, Server /= false -> + RoomJID = <>, + Invite = #xmlel{name = <<"message">>, attrs = [], + children = + [#xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, + ?NS_MUC_USER}], + children = + [#xmlel{name = + <<"invite">>, + attrs = + [{<<"from">>, + jlib:jid_to_string(From)}], + children = + [#xmlel{name + = + <<"reason">>, + attrs + = + [], + children + = + [{xmlcdata, + translate:translate(Lang, + <<"Join the IRC channel here.">>)}]}]}]}, + #xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, + ?NS_XCONFERENCE}], + children = + [{xmlcdata, + translate:translate(Lang, + <<"Join the IRC channel here.">>)}]}, + #xmlel{name = <<"body">>, + attrs = [], + children = + [{xmlcdata, + iolist_to_binary( + io_lib:format( + translate:translate( + Lang, + <<"Join the IRC channel in this Jabber ID: ~s">>), + [RoomJID]))}]}]}, + ejabberd_router:route(jlib:string_to_jid(RoomJID), From, + Invite), + adhoc:produce_response(Request, + #adhoc_response{status = + completed}); + true -> {error, ?ERR_BAD_REQUEST} + end + end + end. + +adhoc_register(_ServerHost, _From, _To, + #adhoc_request{action = <<"cancel">>} = Request) -> + adhoc:produce_response(Request, + #adhoc_response{status = canceled}); +adhoc_register(ServerHost, From, To, + #adhoc_request{lang = Lang, node = _Node, xdata = XData, + action = Action} = + Request) -> #jid{user = User} = From, #jid{lserver = Host} = To, - %% Generate form for setting username and encodings. If the user - %% hasn't begun to fill out the form, generate an initial form - %% based on current values. if XData == false -> - case get_data(ServerHost, Host, From) of - error -> - Username = User, - ConnectionsParams = []; - empty -> - Username = User, - ConnectionsParams = []; - Data -> - Username = xml:get_attr_s(username, Data), - ConnectionsParams = xml:get_attr_s(connections_params, Data) - end, - Error = false; + case get_data(ServerHost, Host, From) of + error -> Username = User, ConnectionsParams = []; + empty -> Username = User, ConnectionsParams = []; + Data -> + {Username, ConnectionsParams} = + get_username_and_connection_params(Data) + end, + Error = false; true -> - case jlib:parse_xdata_submit(XData) of - invalid -> - Error = {error, ?ERR_BAD_REQUEST}, - Username = false, - ConnectionsParams = false; - Fields -> - Username = case lists:keysearch("username", 1, Fields) of - {value, {"username", U}} -> - U; - _ -> - User - end, - ConnectionsParams = parse_connections_params(Fields), - Error = false - end + case jlib:parse_xdata_submit(XData) of + invalid -> + Error = {error, ?ERR_BAD_REQUEST}, + Username = false, + ConnectionsParams = false; + Fields -> + Username = case lists:keysearch(<<"username">>, 1, + Fields) + of + {value, {<<"username">>, U}} -> U; + _ -> User + end, + ConnectionsParams = parse_connections_params(Fields), + Error = false + end end, - - if Error /= false -> - Error; - Action == "complete" -> - case set_data(ServerHost, Host, From, - [{username, - Username}, - {connections_params, - ConnectionsParams}]) of - {atomic, _} -> - adhoc:produce_response(Request, #adhoc_response{status = completed}); - _ -> - {error, ?ERR_INTERNAL_SERVER_ERROR} - end; + if Error /= false -> Error; + Action == <<"complete">> -> + case set_data(ServerHost, Host, From, + [{username, Username}, + {connections_params, ConnectionsParams}]) + of + {atomic, _} -> + adhoc:produce_response(Request, + #adhoc_response{status = completed}); + _ -> {error, ?ERR_INTERNAL_SERVER_ERROR} + end; true -> - Form = generate_adhoc_register_form(Lang, Username, ConnectionsParams), - adhoc:produce_response(Request, - #adhoc_response{status = executing, - elements = [Form], - actions = ["next", "complete"]}) + Form = generate_adhoc_register_form(Lang, Username, + ConnectionsParams), + adhoc:produce_response(Request, + #adhoc_response{status = executing, + elements = [Form], + actions = + [<<"next">>, + <<"complete">>]}) end. -generate_adhoc_register_form(Lang, Username, ConnectionsParams) -> - {xmlelement, "x", - [{"xmlns", ?NS_XDATA}, - {"type", "form"}], - [{xmlelement, "title", [], [{xmlcdata, translate:translate(Lang, "IRC settings")}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, - "Enter username and encodings you wish to use for " - "connecting to IRC servers. Press 'Next' to get more fields " - "to fill in. Press 'Complete' to save settings.")}]}, - {xmlelement, "field", - [{"var", "username"}, - {"type", "text-single"}, - {"label", translate:translate(Lang, "IRC username")}], - [{xmlelement, "required", [], []}, - {xmlelement, "value", [], [{xmlcdata, Username}]}]}] ++ - generate_connection_params_fields(Lang, ConnectionsParams, 1, [])}. +generate_adhoc_register_form(Lang, Username, + ConnectionsParams) -> + #xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}], + children = + [#xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, + translate:translate(Lang, <<"IRC settings">>)}]}, + #xmlel{name = <<"instructions">>, attrs = [], + children = + [{xmlcdata, + translate:translate(Lang, + <<"Enter username and encodings you wish " + "to use for connecting to IRC servers. " + " Press 'Next' to get more fields to " + "fill in. Press 'Complete' to save settings.">>)}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"username">>}, + {<<"type">>, <<"text-single">>}, + {<<"label">>, + translate:translate(Lang, <<"IRC username">>)}], + children = + [#xmlel{name = <<"required">>, attrs = [], + children = []}, + #xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Username}]}]}] + ++ + generate_connection_params_fields(Lang, + ConnectionsParams, 1, [])}. -generate_connection_params_fields(Lang, [], Number, Acc) -> - Field = generate_connection_params_field(Lang, "", "", -1, "", Number), +generate_connection_params_fields(Lang, [], Number, + Acc) -> + Field = generate_connection_params_field(Lang, <<"">>, + <<"">>, -1, <<"">>, Number), lists:reverse(Field ++ Acc); - -generate_connection_params_fields(Lang, [ConnectionParams | ConnectionsParams], Number, Acc) -> +generate_connection_params_fields(Lang, + [ConnectionParams | ConnectionsParams], + Number, Acc) -> case ConnectionParams of - {Server, Encoding, Port, Password} -> - Field = generate_connection_params_field(Lang, Server, Encoding, Port, Password, Number), - generate_connection_params_fields(Lang, ConnectionsParams, Number + 1, Field ++ Acc); - {Server, Encoding, Port} -> - Field = generate_connection_params_field(Lang, Server, Encoding, Port, [], Number), - generate_connection_params_fields(Lang, ConnectionsParams, Number + 1, Field ++ Acc); - {Server, Encoding} -> - Field = generate_connection_params_field(Lang, Server, Encoding, [], [], Number), - generate_connection_params_fields(Lang, ConnectionsParams, Number + 1, Field ++ Acc); - _ -> - [] + {Server, Encoding, Port, Password} -> + Field = generate_connection_params_field(Lang, Server, + Encoding, Port, Password, + Number), + generate_connection_params_fields(Lang, + ConnectionsParams, Number + 1, + Field ++ Acc); + {Server, Encoding, Port} -> + Field = generate_connection_params_field(Lang, Server, + Encoding, Port, <<"">>, Number), + generate_connection_params_fields(Lang, + ConnectionsParams, Number + 1, + Field ++ Acc); + {Server, Encoding} -> + Field = generate_connection_params_field(Lang, Server, + Encoding, -1, <<"">>, Number), + generate_connection_params_fields(Lang, + ConnectionsParams, Number + 1, + Field ++ Acc); + _ -> [] end. -generate_connection_params_field(Lang, Server, Encoding, Port, Password, Number) -> +generate_connection_params_field(Lang, Server, Encoding, + Port, Password, Number) -> EncodingUsed = case Encoding of - [] -> - get_default_encoding(Server); - _ -> - Encoding + <<>> -> get_default_encoding(Server); + _ -> Encoding end, - PortUsedInt = if - Port >= 0 andalso Port =< 65535 -> - Port; - true -> - ?DEFAULT_IRC_PORT - end, - PortUsed = integer_to_list(PortUsedInt), + PortUsedInt = if Port >= 0 andalso Port =< 65535 -> + Port; + true -> ?DEFAULT_IRC_PORT + end, + PortUsed = + iolist_to_binary(integer_to_list(PortUsedInt)), PasswordUsed = case Password of - [] -> - ""; - _ -> - Password - end, - NumberString = integer_to_list(Number), - %% Fields are in reverse order, as they will be reversed again later. - [{xmlelement, "field", - [{"var", "password" ++ NumberString}, - {"type", "text-single"}, - {"label", io_lib:format(translate:translate(Lang, "Password ~b"), [Number])}], - [{xmlelement, "value", [], [{xmlcdata, PasswordUsed}]}]}, - {xmlelement, "field", - [{"var", "port" ++ NumberString}, - {"type", "text-single"}, - {"label", io_lib:format(translate:translate(Lang, "Port ~b"), [Number])}], - [{xmlelement, "value", [], [{xmlcdata, PortUsed}]}]}, - {xmlelement, "field", - [{"var", "encoding" ++ NumberString}, - {"type", "list-single"}, - {"label", io_lib:format(translate:translate(Lang, "Encoding for server ~b"), [Number])}], - [{xmlelement, "value", [], [{xmlcdata, EncodingUsed}]} | - lists:map(fun(E) -> - {xmlelement, "option", [{"label", E}], - [{xmlelement, "value", [], [{xmlcdata, E}]}]} - end, ?POSSIBLE_ENCODINGS)]}, - {xmlelement, "field", - [{"var", "server" ++ NumberString}, - {"type", "text-single"}, - {"label", io_lib:format(translate:translate(Lang, "Server ~b"), [Number])}], - [{xmlelement, "value", [], [{xmlcdata, Server}]}]}]. + <<>> -> <<>>; + _ -> Password + end, + NumberString = + iolist_to_binary(integer_to_list(Number)), + [#xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"password", NumberString/binary>>}, + {<<"type">>, <<"text-single">>}, + {<<"label">>, + iolist_to_binary( + io_lib:format( + translate:translate(Lang, <<"Password ~b">>), + [Number]))}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, PasswordUsed}]}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"port", NumberString/binary>>}, + {<<"type">>, <<"text-single">>}, + {<<"label">>, + iolist_to_binary( + io_lib:format(translate:translate(Lang, <<"Port ~b">>), + [Number]))}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, PortUsed}]}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"encoding", NumberString/binary>>}, + {<<"type">>, <<"list-single">>}, + {<<"label">>, + list_to_binary( + io_lib:format(translate:translate( + Lang, + <<"Encoding for server ~b">>), + [Number]))}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, EncodingUsed}]} + | lists:map(fun (E) -> + #xmlel{name = <<"option">>, + attrs = [{<<"label">>, E}], + children = + [#xmlel{name = <<"value">>, + attrs = [], + children = + [{xmlcdata, E}]}]} + end, + ?POSSIBLE_ENCODINGS)]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"server", NumberString/binary>>}, + {<<"type">>, <<"text-single">>}, + {<<"label">>, + list_to_binary( + io_lib:format(translate:translate(Lang, <<"Server ~b">>), + [Number]))}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Server}]}]}]. parse_connections_params(Fields) -> - %% Find all fields staring with serverN, encodingN, portN and passwordN for any values - %% of N, and generate lists of {"N", Value}. - Servers = lists:sort( - [{lists:nthtail(6, Var), lists:flatten(Value)} || {Var, Value} <- Fields, - lists:prefix("server", Var)]), - Encodings = lists:sort( - [{lists:nthtail(8, Var), lists:flatten(Value)} || {Var, Value} <- Fields, - lists:prefix("encoding", Var)]), - - Ports = lists:sort( - [{lists:nthtail(4, Var), lists:flatten(Value)} || {Var, Value} <- Fields, - lists:prefix("port", Var)]), - - Passwords = lists:sort( - [{lists:nthtail(8, Var), lists:flatten(Value)} || {Var, Value} <- Fields, - lists:prefix("password", Var)]), - - %% Now sort the lists, and find the corresponding pairs. - parse_connections_params(Servers, Encodings, Ports, Passwords). - -retrieve_connections_params(ConnectionParams, ServerN) -> - case ConnectionParams of - [{ConnectionParamN, ConnectionParam} | ConnectionParamsTail] -> - if - ServerN == ConnectionParamN -> - {ConnectionParam, ConnectionParamsTail}; - ServerN < ConnectionParamN -> - {[], [{ConnectionParamN, ConnectionParam} | ConnectionParamsTail]}; - ServerN > ConnectionParamN -> - {[], ConnectionParamsTail} - end; - _ -> - {[], []} - end. - -parse_connections_params([], _, _, _) -> - []; -parse_connections_params(_, [], [], []) -> - []; + Servers = lists:flatmap( + fun({<<"server", Var/binary>>, Value}) -> + [{Var, Value}]; + (_) -> + [] + end, Fields), + Encodings = lists:flatmap( + fun({<<"encoding", Var/binary>>, Value}) -> + [{Var, Value}]; + (_) -> + [] + end, Fields), + Ports = lists:flatmap( + fun({<<"port", Var/binary>>, Value}) -> + [{Var, Value}]; + (_) -> + [] + end, Fields), + Passwords = lists:flatmap( + fun({<<"password", Var/binary>>, Value}) -> + [{Var, Value}]; + (_) -> + [] + end, Fields), + parse_connections_params(Servers, Encodings, Ports, + Passwords). -parse_connections_params([{ServerN, Server} | Servers], Encodings, Ports, Passwords) -> - %% Try to match matches of servers, ports, passwords and encodings, no matter what fields - %% the client might have left out. - {NewEncoding, NewEncodings} = retrieve_connections_params(Encodings, ServerN), - {NewPort, NewPorts} = retrieve_connections_params(Ports, ServerN), - {NewPassword, NewPasswords} = retrieve_connections_params(Passwords, ServerN), - [{Server, NewEncoding, NewPort, NewPassword} | parse_connections_params(Servers, NewEncodings, NewPorts, NewPasswords)]. - -update_table(Host) -> +retrieve_connections_params(ConnectionParams, + ServerN) -> + case ConnectionParams of + [{ConnectionParamN, ConnectionParam} + | ConnectionParamsTail] -> + if ServerN == ConnectionParamN -> + {ConnectionParam, ConnectionParamsTail}; + ServerN < ConnectionParamN -> + {[], + [{ConnectionParamN, ConnectionParam} + | ConnectionParamsTail]}; + ServerN > ConnectionParamN -> {[], ConnectionParamsTail} + end; + _ -> {[], []} + end. + +parse_connections_params([], _, _, _) -> []; +parse_connections_params(_, [], [], []) -> []; +parse_connections_params([{ServerN, Server} | Servers], + Encodings, Ports, Passwords) -> + {NewEncoding, NewEncodings} = + retrieve_connections_params(Encodings, ServerN), + {NewPort, NewPorts} = retrieve_connections_params(Ports, + ServerN), + {NewPassword, NewPasswords} = + retrieve_connections_params(Passwords, ServerN), + [{Server, NewEncoding, NewPort, NewPassword} + | parse_connections_params(Servers, NewEncodings, + NewPorts, NewPasswords)]. + +get_username_and_connection_params(Data) -> + Username = case lists:keysearch(username, 1, Data) of + {value, {_, U}} when is_binary(U) -> + U; + _ -> + <<"">> + end, + ConnParams = case lists:keysearch(connections_params, 1, Data) of + {value, {_, L}} when is_list(L) -> + L; + _ -> + [] + end, + {Username, ConnParams}. + +data_to_binary(Data) -> + lists:map( + fun({username, U}) -> + {username, iolist_to_binary(U)}; + ({connections_params, Params}) -> + {connections_params, + lists:map( + fun({S, E}) -> + {iolist_to_binary(S), iolist_to_binary(E)}; + ({S, E, Port}) -> + {iolist_to_binary(S), iolist_to_binary(E), Port}; + ({S, E, Port, P}) -> + {iolist_to_binary(S), iolist_to_binary(E), + Port, iolist_to_binary(P)} + end, Params)}; + (Opt) -> + Opt + end, Data). + +conn_params_to_list(Params) -> + lists:map( + fun({S, E}) -> + {binary_to_list(S), binary_to_list(E)}; + ({S, E, Port}) -> + {binary_to_list(S), binary_to_list(E), Port}; + ({S, E, Port, P}) -> + {binary_to_list(S), binary_to_list(E), + Port, binary_to_list(P)} + end, Params). + +update_table() -> Fields = record_info(fields, irc_custom), case mnesia:table_info(irc_custom, attributes) of - Fields -> - ok; - [userserver, data] -> - ?INFO_MSG("Converting irc_custom table from " - "{userserver, data} format", []), - {atomic, ok} = mnesia:create_table( - mod_irc_tmp_table, - [{disc_only_copies, [node()]}, - {type, bag}, - {local_content, true}, - {record_name, irc_custom}, - {attributes, record_info(fields, irc_custom)}]), - mnesia:transform_table(irc_custom, ignore, Fields), - F1 = fun() -> - mnesia:write_lock_table(mod_irc_tmp_table), - mnesia:foldl( - fun(#irc_custom{us_host = US} = R, _) -> - mnesia:dirty_write( - mod_irc_tmp_table, - R#irc_custom{us_host = {US, Host}}) - end, ok, irc_custom) - end, - mnesia:transaction(F1), - mnesia:clear_table(irc_custom), - F2 = fun() -> - mnesia:write_lock_table(irc_custom), - mnesia:foldl( - fun(R, _) -> - mnesia:dirty_write(R) - end, ok, mod_irc_tmp_table) - end, - mnesia:transaction(F2), - mnesia:delete_table(mod_irc_tmp_table); - _ -> - ?INFO_MSG("Recreating irc_custom table", []), - mnesia:transform_table(irc_custom, ignore, Fields) + Fields -> + ejabberd_config:convert_table_to_binary( + irc_custom, Fields, set, + fun(#irc_custom{us_host = {_, H}}) -> H end, + fun(#irc_custom{us_host = {{U, S}, H}, + data = Data} = R) -> + R#irc_custom{us_host = {{iolist_to_binary(U), + iolist_to_binary(S)}, + iolist_to_binary(H)}, + data = data_to_binary(Data)} + end); + _ -> + ?INFO_MSG("Recreating irc_custom table", []), + mnesia:transform_table(irc_custom, ignore, Fields) end. + +export(_Server) -> + [{irc_custom, + fun(Host, #irc_custom{us_host = {{U, S}, IRCHost}, + data = Data}) -> + case str: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}]. diff --git a/src/mod_irc/mod_irc_connection.erl b/src/mod_irc/mod_irc_connection.erl index 1c9bd0c95..ba0cb4345 100644 --- a/src/mod_irc/mod_irc_connection.erl +++ b/src/mod_irc/mod_irc_connection.erl @@ -25,53 +25,71 @@ %%%---------------------------------------------------------------------- -module(mod_irc_connection). + -author('alexey@process-one.net'). -behaviour(gen_fsm). %% External exports --export([start_link/8, start/9, route_chan/4, route_nick/3]). +-export([start_link/8, start/9, route_chan/4, + route_nick/3]). %% gen_fsm callbacks --export([init/1, - open_socket/2, - wait_for_registration/2, - stream_established/2, - handle_event/3, - handle_sync_event/4, - handle_info/3, - terminate/3, +-export([init/1, open_socket/2, wait_for_registration/2, + stream_established/2, handle_event/3, + handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). -include("ejabberd.hrl"). + -include("jlib.hrl"). -define(SETS, gb_sets). --record(state, {socket, encoding, port, password, - queue, user, host, server, nick, - channels = dict:new(), - nickchannel, mod, - inbuf = "", outbuf = ""}). +-record(state, + {socket :: inet:socket(), + encoding = <<"">> :: binary(), + port = 0 :: inet:port_number(), + password = <<"">> :: binary(), + queue = queue:new() :: queue(), + user = #jid{} :: jid(), + host = <<"">> :: binary(), + server = <<"">> :: binary(), + nick = <<"">> :: binary(), + channels = dict:new() :: dict(), + nickchannel :: binary(), + mod = mod_irc :: atom(), + inbuf = <<"">> :: binary(), + outbuf = <<"">> :: binary()}). %-define(DBGFSM, true). -ifdef(DBGFSM). + -define(FSMOPTS, [{debug, [trace]}]). + -else. + -define(FSMOPTS, []). --endif. %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- -start(From, Host, ServerHost, Server, Username, Encoding, Port, Password, Mod) -> - Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_irc_sup), - supervisor:start_child( - Supervisor, [From, Host, Server, Username, Encoding, Port, Password, Mod]). +-endif. -start_link(From, Host, Server, Username, Encoding, Port, Password, Mod) -> - gen_fsm:start_link(?MODULE, [From, Host, Server, Username, Encoding, Port, Password, Mod], +start(From, Host, ServerHost, Server, Username, + Encoding, Port, Password, Mod) -> + Supervisor = gen_mod:get_module_proc(ServerHost, + ejabberd_mod_irc_sup), + supervisor:start_child(Supervisor, + [From, Host, Server, Username, Encoding, Port, + Password, Mod]). + +start_link(From, Host, Server, Username, Encoding, Port, + Password, Mod) -> + gen_fsm:start_link(?MODULE, + [From, Host, Server, Username, Encoding, Port, Password, + Mod], ?FSMOPTS). %%%---------------------------------------------------------------------- @@ -85,17 +103,14 @@ start_link(From, Host, Server, Username, Encoding, Port, Password, Mod) -> %% ignore | %% {stop, StopReason} %%---------------------------------------------------------------------- -init([From, Host, Server, Username, Encoding, Port, Password, Mod]) -> +init([From, Host, Server, Username, Encoding, Port, + Password, Mod]) -> gen_fsm:send_event(self(), init), - {ok, open_socket, #state{queue = queue:new(), - mod = Mod, - encoding = Encoding, - port = Port, - password = Password, - user = From, - nick = Username, - host = Host, - server = Server}}. + {ok, open_socket, + #state{queue = queue:new(), mod = Mod, + encoding = Encoding, port = Port, password = Password, + user = From, nick = Username, host = Host, + server = Server}}. %%---------------------------------------------------------------------- %% Func: StateName/2 @@ -107,42 +122,42 @@ open_socket(init, StateData) -> Addr = StateData#state.server, Port = StateData#state.port, ?DEBUG("Connecting with IPv6 to ~s:~p", [Addr, Port]), - Connect6 = gen_tcp:connect(Addr, Port, [inet6, binary, {packet, 0}]), + Connect6 = gen_tcp:connect(binary_to_list(Addr), Port, + [inet6, binary, {packet, 0}]), Connect = case Connect6 of - {error, _} -> - ?DEBUG("Connection with IPv6 to ~s:~p failed. Now using IPv4.", [Addr, Port]), - gen_tcp:connect(Addr, Port, [inet, binary, {packet, 0}]); - _ -> - Connect6 + {error, _} -> + ?DEBUG("Connection with IPv6 to ~s:~p failed. " + "Now using IPv4.", + [Addr, Port]), + gen_tcp:connect(binary_to_list(Addr), Port, + [inet, binary, {packet, 0}]); + _ -> Connect6 end, case Connect of - {ok, Socket} -> - NewStateData = StateData#state{socket = Socket}, - if - StateData#state.password /= "" -> - send_text(NewStateData, - io_lib:format("PASS ~s\r\n", [StateData#state.password])); - true -> true - end, - send_text(NewStateData, - io_lib:format("NICK ~s\r\n", [StateData#state.nick])), - send_text(NewStateData, - io_lib:format( - "USER ~s ~s ~s :~s\r\n", - [StateData#state.nick, - StateData#state.nick, - StateData#state.host, - StateData#state.nick])), - {next_state, wait_for_registration, - NewStateData}; - {error, Reason} -> - ?DEBUG("connect return ~p~n", [Reason]), - Text = case Reason of - timeout -> "Server Connect Timeout"; - _ -> "Server Connect Failed" - end, - bounce_messages(Text), - {stop, normal, StateData} + {ok, Socket} -> + NewStateData = StateData#state{socket = Socket}, + if StateData#state.password /= <<"">> -> + send_text(NewStateData, + io_lib:format("PASS ~s\r\n", + [StateData#state.password])); + true -> true + end, + send_text(NewStateData, + io_lib:format("NICK ~s\r\n", [StateData#state.nick])), + send_text(NewStateData, + io_lib:format("USER ~s ~s ~s :~s\r\n", + [StateData#state.nick, StateData#state.nick, + StateData#state.host, + StateData#state.nick])), + {next_state, wait_for_registration, NewStateData}; + {error, Reason} -> + ?DEBUG("connect return ~p~n", [Reason]), + Text = case Reason of + timeout -> <<"Server Connect Timeout">>; + _ -> <<"Server Connect Failed">> + end, + bounce_messages(Text), + {stop, normal, StateData} end. wait_for_registration(closed, StateData) -> @@ -150,15 +165,11 @@ wait_for_registration(closed, StateData) -> stream_established({xmlstreamend, _Name}, StateData) -> {stop, normal, StateData}; - stream_established(timeout, StateData) -> {stop, normal, StateData}; - stream_established(closed, StateData) -> {stop, normal, StateData}. - - %%---------------------------------------------------------------------- %% Func: StateName/3 %% Returns: {next_state, NextStateName, NextStateData} | @@ -190,46 +201,43 @@ 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}. -define(SEND(S), - if - StateName == stream_established -> - send_text(StateData, S), - StateData; - true -> - StateData#state{outbuf = StateData#state.outbuf ++ S} + if StateName == stream_established -> + send_text(StateData, S), StateData; + true -> + StateData#state{outbuf = <<(StateData#state.outbuf)/binary, + (iolist_to_binary(S))/binary>>} end). -get_password_from_presence({xmlelement, "presence", _Attrs, Els}) -> - case lists:filter(fun(El) -> - case El of - {xmlelement, "x", Attrs, _Els} -> - case xml:get_attr_s("xmlns", Attrs) of - ?NS_MUC -> - true; - _ -> - false - end; - _ -> - false - end - end, Els) of - [ElXMUC | _] -> - case xml:get_subtag(ElXMUC, "password") of - {xmlelement, "password", _, _} = PasswordTag -> - {true, xml:get_tag_cdata(PasswordTag)}; - _ -> - false - end; - _ -> - false - end. +get_password_from_presence(#xmlel{name = <<"presence">>, + children = Els}) -> + case lists:filter(fun (El) -> + case El of + #xmlel{name = <<"x">>, attrs = Attrs} -> + case xml:get_attr_s(<<"xmlns">>, Attrs) of + ?NS_MUC -> true; + _ -> false + end; + _ -> false + end + end, + Els) + of + [ElXMUC | _] -> + case xml:get_subtag(ElXMUC, <<"password">>) of + #xmlel{name = <<"password">>} = PasswordTag -> + {true, xml:get_tag_cdata(PasswordTag)}; + _ -> false + end; + _ -> false + end. %%---------------------------------------------------------------------- %% Func: handle_info/3 @@ -238,432 +246,438 @@ get_password_from_presence({xmlelement, "presence", _Attrs, Els}) -> %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- handle_info({route_chan, Channel, Resource, - {xmlelement, "presence", Attrs, _Els} = Presence}, + #xmlel{name = <<"presence">>, attrs = Attrs} = + Presence}, StateName, StateData) -> - NewStateData = - case xml:get_attr_s("type", Attrs) of - "unavailable" -> - send_stanza_unavailable(Channel, StateData), - S1 = ?SEND(io_lib:format("PART #~s\r\n", [Channel])), - S1#state{channels = - dict:erase(Channel, S1#state.channels)}; - "subscribe" -> StateData; - "subscribed" -> StateData; - "unsubscribe" -> StateData; - "unsubscribed" -> StateData; - "error" -> stop; - _ -> - Nick = case Resource of - "" -> - StateData#state.nick; + NewStateData = case xml:get_attr_s(<<"type">>, Attrs) of + <<"unavailable">> -> + send_stanza_unavailable(Channel, StateData), + S1 = (?SEND((io_lib:format("PART #~s\r\n", + [Channel])))), + S1#state{channels = + dict:erase(Channel, S1#state.channels)}; + <<"subscribe">> -> StateData; + <<"subscribed">> -> StateData; + <<"unsubscribe">> -> StateData; + <<"unsubscribed">> -> StateData; + <<"error">> -> stop; + _ -> + Nick = case Resource of + <<"">> -> StateData#state.nick; + _ -> Resource + end, + S1 = if Nick /= StateData#state.nick -> + S11 = (?SEND((io_lib:format("NICK ~s\r\n", + [Nick])))), + S11#state{nickchannel = Channel}; + true -> StateData + end, + case dict:is_key(Channel, S1#state.channels) of + true -> S1; _ -> - Resource - end, - S1 = if - Nick /= StateData#state.nick -> - S11 = ?SEND(io_lib:format("NICK ~s\r\n", [Nick])), - % The server reply will change the copy of the - % nick in the state (or indicate a clash). - S11#state{nickchannel = Channel}; - true -> - StateData - end, - case dict:is_key(Channel, S1#state.channels) of - true -> - S1; - _ -> - case get_password_from_presence(Presence) of - {true, Password} -> - S2 = ?SEND(io_lib:format("JOIN #~s ~s\r\n", [Channel, Password])); - _ -> - S2 = ?SEND(io_lib:format("JOIN #~s\r\n", [Channel])) - end, - S2#state{channels = - dict:store(Channel, ?SETS:new(), - S1#state.channels)} - end - end, - if - NewStateData == stop -> - {stop, normal, StateData}; - true -> - case dict:fetch_keys(NewStateData#state.channels) of - [] -> {stop, normal, NewStateData}; - _ -> {next_state, StateName, NewStateData} - end + case get_password_from_presence(Presence) of + {true, Password} -> + S2 = + (?SEND((io_lib:format("JOIN #~s ~s\r\n", + [Channel, + Password])))); + _ -> + S2 = (?SEND((io_lib:format("JOIN #~s\r\n", + [Channel])))) + end, + S2#state{channels = + dict:store(Channel, (?SETS):new(), + S1#state.channels)} + end + end, + if NewStateData == stop -> {stop, normal, StateData}; + true -> + case dict:fetch_keys(NewStateData#state.channels) of + [] -> {stop, normal, NewStateData}; + _ -> {next_state, StateName, NewStateData} + end end; - handle_info({route_chan, Channel, Resource, - {xmlelement, "message", Attrs, _Els} = El}, + #xmlel{name = <<"message">>, attrs = Attrs} = El}, StateName, StateData) -> - NewStateData = - case xml:get_attr_s("type", Attrs) of - "groupchat" -> - case xml:get_path_s(El, [{elem, "subject"}, cdata]) of - "" -> - ejabberd_router:route( - jlib:make_jid( - lists:concat( - [Channel, "%", StateData#state.server]), - StateData#state.host, StateData#state.nick), - StateData#state.user, El), - Body = xml:get_path_s(El, [{elem, "body"}, cdata]), - case Body of - "/quote " ++ Rest -> - ?SEND(Rest ++ "\r\n"); - "/msg " ++ Rest -> - ?SEND("PRIVMSG " ++ Rest ++ "\r\n"); - "/me " ++ Rest -> - Strings = string:tokens(Rest, "\n"), - Res = lists:concat( - lists:map( - fun(S) -> - io_lib:format( - "PRIVMSG #~s :\001ACTION ~s\001\r\n", - [Channel, S]) - end, Strings)), - ?SEND(Res); - "/ctcp " ++ Rest -> - Words = string:tokens(Rest, " "), - case Words of - [CtcpDest | _] -> - CtcpCmd = - toupper( - string:substr( - Rest, - string:str(Rest, " ") + 1)), - Res = io_lib:format( - "PRIVMSG ~s :\001~s\001\r\n", - [CtcpDest, CtcpCmd]), - ?SEND(Res); - _ -> - ok - end; - _ -> - Strings = string:tokens(Body, "\n"), - Res = lists:concat( - lists:map( - fun(S) -> - io_lib:format( - "PRIVMSG #~s :~s\r\n", - [Channel, S]) - end, Strings)), - ?SEND(Res) - end; - Subject -> - Strings = string:tokens(Subject, "\n"), - Res = lists:concat( - lists:map( - fun(S) -> - io_lib:format("TOPIC #~s :~s\r\n", - [Channel, S]) - end, Strings)), - ?SEND(Res) - end; - Type when Type == "chat"; Type == ""; Type == "normal" -> - Body = xml:get_path_s(El, [{elem, "body"}, cdata]), - case Body of - "/quote " ++ Rest -> - ?SEND(Rest ++ "\r\n"); - "/msg " ++ Rest -> - ?SEND("PRIVMSG " ++ Rest ++ "\r\n"); - "/me " ++ Rest -> - Strings = string:tokens(Rest, "\n"), - Res = lists:concat( - lists:map( - fun(S) -> - io_lib:format( - "PRIVMSG ~s :\001ACTION ~s\001\r\n", - [Resource, S]) - end, Strings)), - ?SEND(Res); - "/ctcp " ++ Rest -> - Words = string:tokens(Rest, " "), - case Words of - [CtcpDest | _ ] -> - CtcpCmd = - toupper( - string:substr( - Rest, string:str(Rest, " ") + 1)), - Res = io_lib:format( - "PRIVMSG ~s :~s\r\n", - [CtcpDest, "\001" ++ CtcpCmd ++ "\001"]), - ?SEND(Res); - _ -> - ok - end; - _ -> - Strings = string:tokens(Body, "\n"), - Res = lists:concat( - lists:map( - fun(S) -> - io_lib:format("PRIVMSG ~s :~s\r\n", - [Resource, S]) - end, Strings)), - ?SEND(Res) - end; - "error" -> - stop; - _ -> - StateData - end, - if - NewStateData == stop -> - {stop, normal, StateData}; - true -> - {next_state, StateName, NewStateData} + NewStateData = case xml:get_attr_s(<<"type">>, Attrs) of + <<"groupchat">> -> + case xml:get_path_s(El, [{elem, <<"subject">>}, cdata]) + of + <<"">> -> + ejabberd_router:route( + jlib:make_jid( + iolist_to_binary([Channel, + <<"%">>, + StateData#state.server]), + StateData#state.host, + StateData#state.nick), + StateData#state.user, El), + Body = xml:get_path_s(El, + [{elem, <<"body">>}, + cdata]), + case Body of + <<"/quote ", Rest/binary>> -> + ?SEND(<>); + <<"/msg ", Rest/binary>> -> + ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>); + <<"/me ", Rest/binary>> -> + Strings = str:tokens(Rest, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format( + "PRIVMSG #~s :\001ACTION ~s\001\r\n", + [Channel, S]) + end, + Strings)), + ?SEND(Res); + <<"/ctcp ", Rest/binary>> -> + Words = str:tokens(Rest, <<" ">>), + case Words of + [CtcpDest | _] -> + CtcpCmd = str:to_upper( + str:substr(Rest, + str:str(Rest, + <<" ">>) + + 1)), + Res = + io_lib:format("PRIVMSG ~s :\001~s\001\r\n", + [CtcpDest, + CtcpCmd]), + ?SEND(Res); + _ -> ok + end; + _ -> + Strings = str:tokens(Body, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format("PRIVMSG #~s :~s\r\n", + [Channel, S]) + end, + Strings)), + ?SEND(Res) + end; + Subject -> + Strings = str:tokens(Subject, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format("TOPIC #~s :~s\r\n", + [Channel, S]) + end, + Strings)), + ?SEND(Res) + end; + Type + when Type == <<"chat">>; + Type == <<"">>; + Type == <<"normal">> -> + Body = xml:get_path_s(El, [{elem, <<"body">>}, cdata]), + case Body of + <<"/quote ", Rest/binary>> -> + ?SEND(<>); + <<"/msg ", Rest/binary>> -> + ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>); + <<"/me ", Rest/binary>> -> + Strings = str:tokens(Rest, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format( + "PRIVMSG ~s :\001ACTION ~s\001\r\n", + [Resource, S]) + end, + Strings)), + ?SEND(Res); + <<"/ctcp ", Rest/binary>> -> + Words = str:tokens(Rest, <<" ">>), + case Words of + [CtcpDest | _] -> + CtcpCmd = str:to_upper( + str:substr(Rest, + str:str(Rest, + <<" ">>) + + 1)), + Res = io_lib:format("PRIVMSG ~s :~s\r\n", + [CtcpDest, + <<"\001", + CtcpCmd/binary, + "\001">>]), + ?SEND(Res); + _ -> ok + end; + _ -> + Strings = str:tokens(Body, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format( + "PRIVMSG ~s :~s\r\n", + [Resource, S]) + end, + Strings)), + ?SEND(Res) + end; + <<"error">> -> stop; + _ -> StateData + end, + if NewStateData == stop -> {stop, normal, StateData}; + true -> {next_state, StateName, NewStateData} end; - - handle_info({route_chan, Channel, Resource, - {xmlelement, "iq", _Attrs, _Els} = El}, + #xmlel{name = <<"iq">>} = El}, StateName, StateData) -> From = StateData#state.user, - To = jlib:make_jid(lists:concat([Channel, "%", StateData#state.server]), + To = jlib:make_jid(iolist_to_binary([Channel, <<"%">>, + StateData#state.server]), StateData#state.host, StateData#state.nick), _ = case jlib:iq_query_info(El) of - #iq{xmlns = ?NS_MUC_ADMIN} = IQ -> - iq_admin(StateData, Channel, From, To, IQ); - #iq{xmlns = ?NS_VERSION} -> - Res = io_lib:format("PRIVMSG ~s :\001VERSION\001\r\n", - [Resource]), - _ = ?SEND(Res), - Err = jlib:make_error_reply( - El, ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - #iq{xmlns = ?NS_TIME} -> - Res = io_lib:format("PRIVMSG ~s :\001TIME\001\r\n", - [Resource]), - _ = ?SEND(Res), - Err = jlib:make_error_reply( - El, ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - #iq{xmlns = ?NS_VCARD} -> - Res = io_lib:format("WHOIS ~s \r\n", - [Resource]), - _ = ?SEND(Res), - Err = jlib:make_error_reply( - El, ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - #iq{} -> - Err = jlib:make_error_reply( - El, ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - _ -> - ok - end, + #iq{xmlns = ?NS_MUC_ADMIN} = IQ -> + iq_admin(StateData, Channel, From, To, IQ); + #iq{xmlns = ?NS_VERSION} -> + Res = io_lib:format("PRIVMSG ~s :\001VERSION\001\r\n", + [Resource]), + _ = (?SEND(Res)), + Err = jlib:make_error_reply(El, + ?ERR_FEATURE_NOT_IMPLEMENTED), + ejabberd_router:route(To, From, Err); + #iq{xmlns = ?NS_TIME} -> + Res = io_lib:format("PRIVMSG ~s :\001TIME\001\r\n", + [Resource]), + _ = (?SEND(Res)), + Err = jlib:make_error_reply(El, + ?ERR_FEATURE_NOT_IMPLEMENTED), + ejabberd_router:route(To, From, Err); + #iq{xmlns = ?NS_VCARD} -> + Res = io_lib:format("WHOIS ~s \r\n", [Resource]), + _ = (?SEND(Res)), + Err = jlib:make_error_reply(El, + ?ERR_FEATURE_NOT_IMPLEMENTED), + ejabberd_router:route(To, From, Err); + #iq{} -> + Err = jlib:make_error_reply(El, + ?ERR_FEATURE_NOT_IMPLEMENTED), + ejabberd_router:route(To, From, Err); + _ -> ok + end, {next_state, StateName, StateData}; - -handle_info({route_chan, _Channel, _Resource, _Packet}, StateName, StateData) -> - {next_state, StateName, StateData}; - - -handle_info({route_nick, Nick, - {xmlelement, "message", Attrs, _Els} = El}, +handle_info({route_chan, _Channel, _Resource, _Packet}, StateName, StateData) -> - NewStateData = - case xml:get_attr_s("type", Attrs) of - "chat" -> - Body = xml:get_path_s(El, [{elem, "body"}, cdata]), - case Body of - "/quote " ++ Rest -> - ?SEND(Rest ++ "\r\n"); - "/msg " ++ Rest -> - ?SEND("PRIVMSG " ++ Rest ++ "\r\n"); - "/me " ++ Rest -> - Strings = string:tokens(Rest, "\n"), - Res = lists:concat( - lists:map( - fun(S) -> - io_lib:format( - "PRIVMSG ~s :\001ACTION ~s\001\r\n", - [Nick, S]) - end, Strings)), - ?SEND(Res); - "/ctcp " ++ Rest -> - Words = string:tokens(Rest, " "), - case Words of - [CtcpDest | _ ] -> - CtcpCmd = toupper(string:substr(Rest, string:str(Rest, " ")+1 )), - Res = io_lib:format( - "PRIVMSG ~s :~s\r\n", - [CtcpDest, "\001" ++ CtcpCmd ++ "\001"]), - ?SEND(Res); - _ -> - ok - end; - _ -> - Strings = string:tokens(Body, "\n"), - Res = lists:concat( - lists:map( - fun(S) -> - io_lib:format("PRIVMSG ~s :~s\r\n", - [Nick, S]) - end, Strings)), - ?SEND(Res) - end; - "error" -> - stop; - _ -> - StateData - end, - if - NewStateData == stop -> - {stop, normal, StateData}; - true -> - {next_state, StateName, NewStateData} - end; - -handle_info({route_nick, _Nick, _Packet}, StateName, StateData) -> {next_state, StateName, StateData}; - - -handle_info({ircstring, [$P, $I, $N, $G, $ | ID]}, StateName, StateData) -> - send_text(StateData, "PONG " ++ ID ++ "\r\n"), - {next_state, StateName, StateData}; - -handle_info({ircstring, [$: | String]}, wait_for_registration, StateData) -> - Words = string:tokens(String, " "), - {NewState, NewStateData} = - case Words of - [_, "001" | _] -> - send_text(StateData, - io_lib:format("CODEPAGE ~s\r\n", [StateData#state.encoding])), - {stream_established, StateData}; - [_, "433" | _] -> - {error, - {error, error_nick_in_use(StateData, String), StateData}}; - [_, [$4, _, _] | _] -> - {error, - {error, error_unknown_num(StateData, String, "cancel"), - StateData}}; - [_, [$5, _, _] | _] -> - {error, - {error, error_unknown_num(StateData, String, "cancel"), - StateData}}; - _ -> - ?DEBUG("unknown irc command '~s'~n", [String]), - {wait_for_registration, StateData} - end, - % Note that we don't send any data at this stage. - if - NewState == error -> - {stop, normal, NewStateData}; - true -> - {next_state, NewState, NewStateData} +handle_info({route_nick, Nick, + #xmlel{name = <<"message">>, attrs = Attrs} = El}, + StateName, StateData) -> + NewStateData = case xml:get_attr_s(<<"type">>, Attrs) of + <<"chat">> -> + Body = xml:get_path_s(El, [{elem, <<"body">>}, cdata]), + case Body of + <<"/quote ", Rest/binary>> -> + ?SEND(<>); + <<"/msg ", Rest/binary>> -> + ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>); + <<"/me ", Rest/binary>> -> + Strings = str:tokens(Rest, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format( + "PRIVMSG ~s :\001ACTION ~s\001\r\n", + [Nick, S]) + end, + Strings)), + ?SEND(Res); + <<"/ctcp ", Rest/binary>> -> + Words = str:tokens(Rest, <<" ">>), + case Words of + [CtcpDest | _] -> + CtcpCmd = str:to_upper( + str:substr(Rest, + str:str(Rest, + <<" ">>) + + 1)), + Res = io_lib:format("PRIVMSG ~s :~s\r\n", + [CtcpDest, + <<"\001", + CtcpCmd/binary, + "\001">>]), + ?SEND(Res); + _ -> ok + end; + _ -> + Strings = str:tokens(Body, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format( + "PRIVMSG ~s :~s\r\n", + [Nick, S]) + end, + Strings)), + ?SEND(Res) + end; + <<"error">> -> stop; + _ -> StateData + end, + if NewStateData == stop -> {stop, normal, StateData}; + true -> {next_state, StateName, NewStateData} end; - -handle_info({ircstring, [$: | String]}, _StateName, StateData) -> - Words = string:tokens(String, " "), - NewStateData = - case Words of - [_, "353" | Items] -> - process_channel_list(StateData, Items); - [_, "332", _Nick, [$# | Chan] | _] -> - process_channel_topic(StateData, Chan, String), - StateData; - [_, "333", _Nick, [$# | Chan] | _] -> - process_channel_topic_who(StateData, Chan, String), - StateData; - [_, "318", _, Nick | _] -> - process_endofwhois(StateData, String, Nick), - StateData; - [_, "311", _, Nick, Ident, Irchost | _ ] -> - process_whois311(StateData, String, Nick, Ident, Irchost), - StateData; - [_, "312", _, Nick, Ircserver | _ ] -> - process_whois312(StateData, String, Nick, Ircserver), - StateData; - [_, "319", _, Nick | _ ] -> - process_whois319(StateData, String, Nick), - StateData; - [_, "433" | _] -> - process_nick_in_use(StateData, String); - % CODEPAGE isn't standard, so don't complain if it's not there. - [_, "421", _, "CODEPAGE" | _] -> - StateData; - [_, [$4, _, _] | _] -> - process_num_error(StateData, String); - [_, [$5, _, _] | _] -> - process_num_error(StateData, String); - [From, "PRIVMSG", [$# | Chan] | _] -> - process_chanprivmsg(StateData, Chan, From, String), - StateData; - [From, "NOTICE", [$# | Chan] | _] -> - process_channotice(StateData, Chan, From, String), - StateData; - [From, "PRIVMSG", Nick, ":\001VERSION\001" | _] -> - process_version(StateData, Nick, From), - StateData; - [From, "PRIVMSG", Nick, ":\001USERINFO\001" | _] -> - process_userinfo(StateData, Nick, From), - StateData; - [From, "PRIVMSG", Nick | _] -> - process_privmsg(StateData, Nick, From, String), - StateData; - [From, "NOTICE", Nick | _] -> - process_notice(StateData, Nick, From, String), - StateData; - [From, "TOPIC", [$# | Chan] | _] -> - process_topic(StateData, Chan, From, String), - StateData; - [From, "PART", [$# | Chan] | _] -> - process_part(StateData, Chan, From, String); - [From, "QUIT" | _] -> - process_quit(StateData, From, String); - [From, "JOIN", Chan | _] -> - process_join(StateData, Chan, From, String); - [From, "MODE", [$# | Chan], "+o", Nick | _] -> - process_mode_o(StateData, Chan, From, Nick, - "admin", "moderator"), - StateData; - [From, "MODE", [$# | Chan], "-o", Nick | _] -> - process_mode_o(StateData, Chan, From, Nick, - "member", "participant"), - StateData; - [From, "KICK", [$# | Chan], Nick | _] -> - process_kick(StateData, Chan, From, Nick, String), - StateData; - [From, "NICK", Nick | _] -> - process_nick(StateData, From, Nick); - _ -> - ?DEBUG("unknown irc command '~s'~n", [String]), - StateData - end, - NewStateData1 = - case StateData#state.outbuf of - "" -> - NewStateData; - Data -> - send_text(NewStateData, Data), - NewStateData#state{outbuf = ""} - end, +handle_info({route_nick, _Nick, _Packet}, StateName, + StateData) -> + {next_state, StateName, StateData}; +handle_info({ircstring, + <<$P, $I, $N, $G, $\s, ID/binary>>}, + StateName, StateData) -> + send_text(StateData, <<"PONG ", ID/binary, "\r\n">>), + {next_state, StateName, StateData}; +handle_info({ircstring, <<$:, String/binary>>}, + wait_for_registration, StateData) -> + Words = str:tokens(String, <<" ">>), + {NewState, NewStateData} = case Words of + [_, <<"001">> | _] -> + send_text(StateData, + io_lib:format("CODEPAGE ~s\r\n", + [StateData#state.encoding])), + {stream_established, StateData}; + [_, <<"433">> | _] -> + {error, + {error, + error_nick_in_use(StateData, String), + StateData}}; + [_, <<$4, _, _>> | _] -> + {error, + {error, + error_unknown_num(StateData, String, + <<"cancel">>), + StateData}}; + [_, <<$5, _, _>> | _] -> + {error, + {error, + error_unknown_num(StateData, String, + <<"cancel">>), + StateData}}; + _ -> + ?DEBUG("unknown irc command '~s'~n", + [String]), + {wait_for_registration, StateData} + end, + if NewState == error -> {stop, normal, NewStateData}; + true -> {next_state, NewState, NewStateData} + end; +handle_info({ircstring, <<$:, String/binary>>}, + _StateName, StateData) -> + Words = str:tokens(String, <<" ">>), + NewStateData = case Words of + [_, <<"353">> | Items] -> + process_channel_list(StateData, Items); + [_, <<"332">>, _Nick, <<$#, Chan/binary>> | _] -> + process_channel_topic(StateData, Chan, String), + StateData; + [_, <<"333">>, _Nick, <<$#, Chan/binary>> | _] -> + process_channel_topic_who(StateData, Chan, String), + StateData; + [_, <<"318">>, _, Nick | _] -> + process_endofwhois(StateData, String, Nick), StateData; + [_, <<"311">>, _, Nick, Ident, Irchost | _] -> + process_whois311(StateData, String, Nick, Ident, + Irchost), + StateData; + [_, <<"312">>, _, Nick, Ircserver | _] -> + process_whois312(StateData, String, Nick, Ircserver), + StateData; + [_, <<"319">>, _, Nick | _] -> + process_whois319(StateData, String, Nick), StateData; + [_, <<"433">> | _] -> + process_nick_in_use(StateData, String); + % CODEPAGE isn't standard, so don't complain if it's not there. + [_, <<"421">>, _, <<"CODEPAGE">> | _] -> StateData; + [_, <<$4, _, _>> | _] -> + process_num_error(StateData, String); + [_, <<$5, _, _>> | _] -> + process_num_error(StateData, String); + [From, <<"PRIVMSG">>, <<$#, Chan/binary>> | _] -> + process_chanprivmsg(StateData, Chan, From, String), + StateData; + [From, <<"NOTICE">>, <<$#, Chan/binary>> | _] -> + process_channotice(StateData, Chan, From, String), + StateData; + [From, <<"PRIVMSG">>, Nick, <<":\001VERSION\001">> + | _] -> + process_version(StateData, Nick, From), StateData; + [From, <<"PRIVMSG">>, Nick, <<":\001USERINFO\001">> + | _] -> + process_userinfo(StateData, Nick, From), StateData; + [From, <<"PRIVMSG">>, Nick | _] -> + process_privmsg(StateData, Nick, From, String), + StateData; + [From, <<"NOTICE">>, Nick | _] -> + process_notice(StateData, Nick, From, String), + StateData; + [From, <<"TOPIC">>, <<$#, Chan/binary>> | _] -> + process_topic(StateData, Chan, From, String), + StateData; + [From, <<"PART">>, <<$#, Chan/binary>> | _] -> + process_part(StateData, Chan, From, String); + [From, <<"QUIT">> | _] -> + process_quit(StateData, From, String); + [From, <<"JOIN">>, Chan | _] -> + process_join(StateData, Chan, From, String); + [From, <<"MODE">>, <<$#, Chan/binary>>, <<"+o">>, Nick + | _] -> + process_mode_o(StateData, Chan, From, Nick, + <<"admin">>, <<"moderator">>), + StateData; + [From, <<"MODE">>, <<$#, Chan/binary>>, <<"-o">>, Nick + | _] -> + process_mode_o(StateData, Chan, From, Nick, + <<"member">>, <<"participant">>), + StateData; + [From, <<"KICK">>, <<$#, Chan/binary>>, Nick | _] -> + process_kick(StateData, Chan, From, Nick, String), + StateData; + [From, <<"NICK">>, Nick | _] -> + process_nick(StateData, From, Nick); + _ -> + ?DEBUG("unknown irc command '~s'~n", [String]), + StateData + end, + NewStateData1 = case StateData#state.outbuf of + <<"">> -> NewStateData; + Data -> + send_text(NewStateData, Data), + NewStateData#state{outbuf = <<"">>} + end, {next_state, stream_established, NewStateData1}; - -handle_info({ircstring, [$E, $R, $R, $O, $R | _] = String}, +handle_info({ircstring, + <<$E, $R, $R, $O, $R, _/binary>> = String}, StateName, StateData) -> process_error(StateData, String), {next_state, StateName, StateData}; - - -handle_info({ircstring, String}, StateName, StateData) -> +handle_info({ircstring, String}, StateName, + StateData) -> ?DEBUG("unknown irc command '~s'~n", [String]), {next_state, StateName, StateData}; - - handle_info({send_text, Text}, StateName, StateData) -> send_text(StateData, Text), {next_state, StateName, StateData}; -handle_info({tcp, _Socket, Data}, StateName, StateData) -> - Buf = StateData#state.inbuf ++ binary_to_list(Data), - Strings = ejabberd_regexp:split([C || C <- Buf, C /= $\r], "\n"), +handle_info({tcp, _Socket, Data}, StateName, + StateData) -> + Buf = <<(StateData#state.inbuf)/binary, Data/binary>>, + Strings = ejabberd_regexp:split(<< <> + || <> <= Buf, C /= $\r >>, + <<"\n">>), ?DEBUG("strings=~p~n", [Strings]), - NewBuf = process_lines(StateData#state.encoding, Strings), - {next_state, StateName, StateData#state{inbuf = NewBuf}}; -handle_info({tcp_closed, _Socket}, StateName, StateData) -> + NewBuf = process_lines(StateData#state.encoding, + Strings), + {next_state, StateName, + StateData#state{inbuf = NewBuf}}; +handle_info({tcp_closed, _Socket}, StateName, + StateData) -> gen_fsm:send_event(self(), closed), {next_state, StateName, StateData}; -handle_info({tcp_error, _Socket, _Reason}, StateName, StateData) -> +handle_info({tcp_error, _Socket, _Reason}, StateName, + StateData) -> gen_fsm:send_event(self(), closed), {next_state, StateName, StateData}. @@ -673,67 +687,72 @@ handle_info({tcp_error, _Socket, _Reason}, StateName, StateData) -> %% Returns: any %%---------------------------------------------------------------------- terminate(_Reason, _StateName, FullStateData) -> - % Extract error message if there was one. {Error, StateData} = case FullStateData of - {error, SError, SStateData} -> - {SError, SStateData}; - _ -> - {{xmlelement, "error", [{"code", "502"}], - [{xmlcdata, "Server Connect Failed"}]}, - FullStateData} - end, + {error, SError, SStateData} -> {SError, SStateData}; + _ -> + {#xmlel{name = <<"error">>, + attrs = [{<<"code">>, <<"502">>}], + children = + [{xmlcdata, + <<"Server Connect Failed">>}]}, + FullStateData} + end, (FullStateData#state.mod):closed_connection(StateData#state.host, - StateData#state.user, - StateData#state.server), - bounce_messages("Server Connect Failed"), - lists:foreach( - fun(Chan) -> - Stanza = {xmlelement, "presence", [{"type", "error"}], - [Error]}, - send_stanza(Chan, StateData, Stanza) - end, dict:fetch_keys(StateData#state.channels)), + StateData#state.user, + StateData#state.server), + bounce_messages(<<"Server Connect Failed">>), + lists:foreach(fun (Chan) -> + Stanza = #xmlel{name = <<"presence">>, + attrs = [{<<"type">>, <<"error">>}], + children = [Error]}, + send_stanza(Chan, StateData, Stanza) + end, + dict:fetch_keys(StateData#state.channels)), case StateData#state.socket of - undefined -> - ok; - Socket -> - gen_tcp:close(Socket) + undefined -> ok; + Socket -> gen_tcp:close(Socket) end, ok. send_stanza(Chan, StateData, Stanza) -> ejabberd_router:route( jlib:make_jid( - lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, StateData#state.nick), - StateData#state.user, - Stanza). + iolist_to_binary([Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, + StateData#state.nick), + StateData#state.user, Stanza). send_stanza_unavailable(Chan, StateData) -> - Affiliation = "member", % this is a simplification - Role = "none", - Stanza = - {xmlelement, "presence", [{"type", "unavailable"}], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", - [{"affiliation", Affiliation}, - {"role", Role}], - []}, - {xmlelement, "status", - [{"code", "110"}], - []} - ]}]}, + Affiliation = <<"member">>, + Role = <<"none">>, + Stanza = #xmlel{name = <<"presence">>, + attrs = [{<<"type">>, <<"unavailable">>}], + children = + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_MUC_USER}], + children = + [#xmlel{name = <<"item">>, + attrs = + [{<<"affiliation">>, + Affiliation}, + {<<"role">>, Role}], + children = []}, + #xmlel{name = <<"status">>, + attrs = [{<<"code">>, <<"110">>}], + children = []}]}]}, send_stanza(Chan, StateData, Stanza). %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- -send_text(#state{socket = Socket, encoding = Encoding}, Text) -> - CText = iconv:convert("utf-8", Encoding, lists:flatten(Text)), - %?DEBUG("IRC OUTu: ~s~nIRC OUTk: ~s~n", [Text, CText]), +send_text(#state{socket = Socket, encoding = Encoding}, + Text) -> + CText = iconv:convert(<<"utf-8">>, Encoding, iolist_to_binary(Text)), gen_tcp:send(Socket, CText). - %send_queue(Socket, Q) -> % case queue:out(Q) of % {{value, El}, Q1} -> @@ -745,35 +764,32 @@ send_text(#state{socket = Socket, encoding = Encoding}, Text) -> bounce_messages(Reason) -> receive - {send_element, El} -> - {xmlelement, _Name, Attrs, _SubTags} = El, - case xml:get_attr_s("type", Attrs) of - "error" -> - ok; - _ -> - Err = jlib:make_error_reply(El, - "502", Reason), - From = jlib:string_to_jid(xml:get_attr_s("from", Attrs)), - To = jlib:string_to_jid(xml:get_attr_s("to", Attrs)), - ejabberd_router:route(To, From, Err) - end, - bounce_messages(Reason) - after 0 -> - ok + {send_element, El} -> + #xmlel{attrs = Attrs} = El, + case xml:get_attr_s(<<"type">>, Attrs) of + <<"error">> -> ok; + _ -> + Err = jlib:make_error_reply(El, <<"502">>, Reason), + From = jlib:string_to_jid(xml:get_attr_s(<<"from">>, + Attrs)), + To = jlib:string_to_jid(xml:get_attr_s(<<"to">>, + Attrs)), + ejabberd_router:route(To, From, Err) + end, + bounce_messages(Reason) + after 0 -> ok end. - route_chan(Pid, Channel, Resource, Packet) -> Pid ! {route_chan, Channel, Resource, Packet}. route_nick(Pid, Nick, Packet) -> Pid ! {route_nick, Nick, Packet}. - -process_lines(_Encoding, [S]) -> - S; +process_lines(_Encoding, [S]) -> S; process_lines(Encoding, [S | Ss]) -> - self() ! {ircstring, iconv:convert(Encoding, "utf-8", S)}, + self() ! + {ircstring, iconv:convert(Encoding, <<"utf-8">>, S)}, process_lines(Encoding, Ss). process_channel_list(StateData, Items) -> @@ -781,587 +797,785 @@ process_channel_list(StateData, Items) -> process_channel_list_find_chan(StateData, []) -> StateData; -process_channel_list_find_chan(StateData, [[$# | Chan] | Items]) -> +process_channel_list_find_chan(StateData, + [<<$#, Chan/binary>> | Items]) -> process_channel_list_users(StateData, Chan, Items); -process_channel_list_find_chan(StateData, [_ | Items]) -> +process_channel_list_find_chan(StateData, + [_ | Items]) -> process_channel_list_find_chan(StateData, Items). process_channel_list_users(StateData, _Chan, []) -> StateData; -process_channel_list_users(StateData, Chan, [User | Items]) -> - NewStateData = process_channel_list_user(StateData, Chan, User), +process_channel_list_users(StateData, Chan, + [User | Items]) -> + NewStateData = process_channel_list_user(StateData, + Chan, User), process_channel_list_users(NewStateData, Chan, Items). process_channel_list_user(StateData, Chan, User) -> User1 = case User of - [$: | U1] -> U1; - _ -> User + <<$:, U1/binary>> -> U1; + _ -> User end, - {User2, Affiliation, Role} = - case User1 of - [$@ | U2] -> {U2, "admin", "moderator"}; - [$+ | U2] -> {U2, "member", "participant"}; - [$\% | U2] -> {U2, "admin", "moderator"}; - [$& | U2] -> {U2, "admin", "moderator"}; - [$~ | U2] -> {U2, "admin", "moderator"}; - _ -> {User1, "member", "participant"} - end, - ejabberd_router:route( - jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, User2), - StateData#state.user, - {xmlelement, "presence", [], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", - [{"affiliation", Affiliation}, - {"role", Role}], - []}]}]}), + {User2, Affiliation, Role} = case User1 of + <<$@, U2/binary>> -> + {U2, <<"admin">>, <<"moderator">>}; + <<$+, U2/binary>> -> + {U2, <<"member">>, <<"participant">>}; + <<$%, U2/binary>> -> + {U2, <<"admin">>, <<"moderator">>}; + <<$&, U2/binary>> -> + {U2, <<"admin">>, <<"moderator">>}; + <<$~, U2/binary>> -> + {U2, <<"admin">>, <<"moderator">>}; + _ -> {User1, <<"member">>, <<"participant">>} + end, + ejabberd_router:route(jlib:make_jid(iolist_to_binary([Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, User2), + StateData#state.user, + #xmlel{name = <<"presence">>, attrs = [], + children = + [#xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, ?NS_MUC_USER}], + children = + [#xmlel{name = <<"item">>, + attrs = + [{<<"affiliation">>, + Affiliation}, + {<<"role">>, + Role}], + children = []}]}]}), case catch dict:update(Chan, - fun(Ps) -> - ?SETS:add_element(User2, Ps) - end, StateData#state.channels) of - {'EXIT', _} -> - StateData; - NS -> - StateData#state{channels = NS} + fun (Ps) -> (?SETS):add_element(User2, Ps) end, + StateData#state.channels) + of + {'EXIT', _} -> StateData; + NS -> StateData#state{channels = NS} end. - process_channel_topic(StateData, Chan, String) -> - Msg = ejabberd_regexp:replace(String, ".*332[^:]*:", ""), + Msg = ejabberd_regexp:replace(String, <<".*332[^:]*:">>, + <<"">>), Msg1 = filter_message(Msg), - ejabberd_router:route( - jlib:make_jid( - lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, ""), - StateData#state.user, - {xmlelement, "message", [{"type", "groupchat"}], - [{xmlelement, "subject", [], [{xmlcdata, Msg1}]}, - {xmlelement, "body", [], [{xmlcdata, "Topic for #" ++ Chan ++ ": " ++ Msg1}]} - ]}). + ejabberd_router:route(jlib:make_jid(iolist_to_binary([Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"groupchat">>}], + children = + [#xmlel{name = <<"subject">>, attrs = [], + children = [{xmlcdata, Msg1}]}, + #xmlel{name = <<"body">>, attrs = [], + children = + [{xmlcdata, + <<"Topic for #", Chan/binary, + ": ", Msg1/binary>>}]}]}). process_channel_topic_who(StateData, Chan, String) -> - Words = string:tokens(String, " "), + Words = str:tokens(String, <<" ">>), Msg1 = case Words of - [_, "333", _, _Chan, Whoset , Timeset] -> - {Unixtimeset, _Rest} = string:to_integer(Timeset), - "Topic for #" ++ Chan ++ " set by " ++ Whoset ++ - " at " ++ unixtime2string(Unixtimeset); - [_, "333", _, _Chan, Whoset | _] -> - "Topic for #" ++ Chan ++ " set by " ++ Whoset; - _ -> - String + [_, <<"333">>, _, _Chan, Whoset, Timeset] -> + {Unixtimeset, _Rest} = str:to_integer(Timeset), + <<"Topic for #", Chan/binary, " set by ", Whoset/binary, + " at ", (unixtime2string(Unixtimeset))/binary>>; + [_, <<"333">>, _, _Chan, Whoset | _] -> + <<"Topic for #", Chan/binary, " set by ", + Whoset/binary>>; + _ -> String end, Msg2 = filter_message(Msg1), - ejabberd_router:route( - jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, ""), - StateData#state.user, - {xmlelement, "message", [{"type", "groupchat"}], - [{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}). - + ejabberd_router:route(jlib:make_jid(iolist_to_binary([Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"groupchat">>}], + children = + [#xmlel{name = <<"body">>, attrs = [], + children = [{xmlcdata, Msg2}]}]}). error_nick_in_use(_StateData, String) -> - Msg = ejabberd_regexp:replace(String, ".*433 +[^ ]* +", ""), + Msg = ejabberd_regexp:replace(String, + <<".*433 +[^ ]* +">>, <<"">>), Msg1 = filter_message(Msg), - {xmlelement, "error", [{"code", "409"}, {"type", "cancel"}], - [{xmlelement, "conflict", [{"xmlns", ?NS_STANZAS}], []}, - {xmlelement, "text", [{"xmlns", ?NS_STANZAS}], - [{xmlcdata, Msg1}]}]}. + #xmlel{name = <<"error">>, + attrs = + [{<<"code">>, <<"409">>}, {<<"type">>, <<"cancel">>}], + children = + [#xmlel{name = <<"conflict">>, + attrs = [{<<"xmlns">>, ?NS_STANZAS}], children = []}, + #xmlel{name = <<"text">>, + attrs = [{<<"xmlns">>, ?NS_STANZAS}], + children = [{xmlcdata, Msg1}]}]}. process_nick_in_use(StateData, String) -> - % We can't use the jlib macro because we don't know the language of the - % message. Error = error_nick_in_use(StateData, String), case StateData#state.nickchannel of - undefined -> - % Shouldn't happen with a well behaved server - StateData; - Chan -> - ejabberd_router:route( - jlib:make_jid( - lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, StateData#state.nick), - StateData#state.user, - {xmlelement, "presence", [{"type", "error"}], [Error]}), - StateData#state{nickchannel = undefined} + undefined -> + % Shouldn't happen with a well behaved server + StateData; + Chan -> + ejabberd_router:route(jlib:make_jid(iolist_to_binary([Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, + StateData#state.nick), + StateData#state.user, + #xmlel{name = <<"presence">>, + attrs = [{<<"type">>, <<"error">>}], + children = [Error]}), + StateData#state{nickchannel = undefined} end. process_num_error(StateData, String) -> - Error = error_unknown_num(StateData, String, "continue"), - lists:foreach( - fun(Chan) -> - ejabberd_router:route( - jlib:make_jid( - lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, StateData#state.nick), - StateData#state.user, - {xmlelement, "message", [{"type", "error"}], - [Error]}) - end, dict:fetch_keys(StateData#state.channels)), - StateData. - -process_endofwhois(StateData, _String, Nick) -> - ejabberd_router:route( - jlib:make_jid(lists:concat([Nick, "!", StateData#state.server]), - StateData#state.host, ""), - StateData#state.user, - {xmlelement, "message", [{"type", "chat"}], - [{xmlelement, "body", [], [{xmlcdata, "End of WHOIS"}]}]}). - -process_whois311(StateData, String, Nick, Ident, Irchost) -> - Fullname = ejabberd_regexp:replace(String, ".*311[^:]*:", ""), - ejabberd_router:route( - jlib:make_jid(lists:concat([Nick, "!", StateData#state.server]), - StateData#state.host, ""), - StateData#state.user, - {xmlelement, "message", [{"type", "chat"}], - [{xmlelement, "body", [], - [{xmlcdata, lists:concat( - ["WHOIS: ", Nick, " is ", - Ident, "@" , Irchost, " : " , Fullname])}]}]}). - -process_whois312(StateData, String, Nick, Ircserver) -> - Ircserverdesc = ejabberd_regexp:replace(String, ".*312[^:]*:", ""), - ejabberd_router:route( - jlib:make_jid(lists:concat([Nick, "!", StateData#state.server]), - StateData#state.host, ""), - StateData#state.user, - {xmlelement, "message", [{"type", "chat"}], - [{xmlelement, "body", [], - [{xmlcdata, lists:concat(["WHOIS: ", Nick, " use ", - Ircserver, " : ", Ircserverdesc])}]}]}). - -process_whois319(StateData, String, Nick) -> - Chanlist = ejabberd_regexp:replace(String, ".*319[^:]*:", ""), - ejabberd_router:route( - jlib:make_jid(lists:concat([Nick, "!", StateData#state.server]), - StateData#state.host, ""), - StateData#state.user, - {xmlelement, "message", [{"type", "chat"}], - [{xmlelement, "body", [], - [{xmlcdata, lists:concat(["WHOIS: ", Nick, " is on ", - Chanlist])}]}]}). - - - -process_chanprivmsg(StateData, Chan, From, String) -> - [FromUser | _] = string:tokens(From, "!"), - Msg = ejabberd_regexp:replace(String, ".*PRIVMSG[^:]*:", ""), - Msg1 = case Msg of - [1, $A, $C, $T, $I, $O, $N, $ | Rest] -> - "/me " ++ Rest; - _ -> - Msg - end, - Msg2 = filter_message(Msg1), - ejabberd_router:route( - jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - {xmlelement, "message", [{"type", "groupchat"}], - [{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}). - - - -process_channotice(StateData, Chan, From, String) -> - [FromUser | _] = string:tokens(From, "!"), - Msg = ejabberd_regexp:replace(String, ".*NOTICE[^:]*:", ""), - Msg1 = case Msg of - [1, $A, $C, $T, $I, $O, $N, $ | Rest] -> - "/me " ++ Rest; - _ -> - "/me NOTICE: " ++ Msg - end, - Msg2 = filter_message(Msg1), - ejabberd_router:route( - jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - {xmlelement, "message", [{"type", "groupchat"}], - [{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}). - - - - -process_privmsg(StateData, _Nick, From, String) -> - [FromUser | _] = string:tokens(From, "!"), - Msg = ejabberd_regexp:replace(String, ".*PRIVMSG[^:]*:", ""), - Msg1 = case Msg of - [1, $A, $C, $T, $I, $O, $N, $ | Rest] -> - "/me " ++ Rest; - _ -> - Msg - end, - Msg2 = filter_message(Msg1), - ejabberd_router:route( - jlib:make_jid(lists:concat([FromUser, "!", StateData#state.server]), - StateData#state.host, ""), - StateData#state.user, - {xmlelement, "message", [{"type", "chat"}], - [{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}). - - -process_notice(StateData, _Nick, From, String) -> - [FromUser | _] = string:tokens(From, "!"), - Msg = ejabberd_regexp:replace(String, ".*NOTICE[^:]*:", ""), - Msg1 = case Msg of - [1, $A, $C, $T, $I, $O, $N, $ | Rest] -> - "/me " ++ Rest; - _ -> - "/me NOTICE: " ++ Msg - end, - Msg2 = filter_message(Msg1), - ejabberd_router:route( - jlib:make_jid(lists:concat([FromUser, "!", StateData#state.server]), - StateData#state.host, ""), - StateData#state.user, - {xmlelement, "message", [{"type", "chat"}], - [{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}). - - -process_version(StateData, _Nick, From) -> - [FromUser | _] = string:tokens(From, "!"), - send_text( - StateData, - io_lib:format("NOTICE ~s :\001VERSION " - "ejabberd IRC transport ~s (c) Alexey Shchepin" - "\001\r\n", - [FromUser, ?VERSION]) ++ - io_lib:format("NOTICE ~s :\001VERSION " - "http://ejabberd.jabberstudio.org/" - "\001\r\n", - [FromUser])). - - -process_userinfo(StateData, _Nick, From) -> - [FromUser | _] = string:tokens(From, "!"), - send_text( - StateData, - io_lib:format("NOTICE ~s :\001USERINFO " - "xmpp:~s" - "\001\r\n", - [FromUser, - jlib:jid_to_string(StateData#state.user)])). - - -process_topic(StateData, Chan, From, String) -> - [FromUser | _] = string:tokens(From, "!"), - Msg = ejabberd_regexp:replace(String, ".*TOPIC[^:]*:", ""), - Msg1 = filter_message(Msg), - ejabberd_router:route( - jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - {xmlelement, "message", [{"type", "groupchat"}], - [{xmlelement, "subject", [], [{xmlcdata, Msg1}]}, - {xmlelement, "body", [], - [{xmlcdata, "/me has changed the subject to: " ++ - Msg1}]}]}). - -process_part(StateData, Chan, From, String) -> - [FromUser | FromIdent] = string:tokens(From, "!"), - Msg = ejabberd_regexp:replace(String, ".*PART[^:]*:", ""), - Msg1 = filter_message(Msg), - ejabberd_router:route( - jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - {xmlelement, "presence", [{"type", "unavailable"}], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", - [{"affiliation", "member"}, - {"role", "none"}], - []}]}, - {xmlelement, "status", [], - [{xmlcdata, Msg1 ++ " (" ++ FromIdent ++ ")"}]}] - }), - case catch dict:update(Chan, - fun(Ps) -> - remove_element(FromUser, Ps) - end, StateData#state.channels) of - {'EXIT', _} -> - StateData; - NS -> - StateData#state{channels = NS} - end. - - -process_quit(StateData, From, String) -> - [FromUser | FromIdent] = string:tokens(From, "!"), - - Msg = ejabberd_regexp:replace(String, ".*QUIT[^:]*:", ""), - Msg1 = filter_message(Msg), - %%NewChans = - dict:map( - fun(Chan, Ps) -> - case ?SETS:is_member(FromUser, Ps) of - true -> + Error = error_unknown_num(StateData, String, + <<"continue">>), + lists:foreach(fun (Chan) -> ejabberd_router:route( - jlib:make_jid( - lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - {xmlelement, "presence", [{"type", "unavailable"}], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", - [{"affiliation", "member"}, - {"role", "none"}], - []}]}, - {xmlelement, "status", [], - [{xmlcdata, Msg1 ++ " (" ++ FromIdent ++ ")"}]} - ]}), - remove_element(FromUser, Ps); - _ -> - Ps - end - end, StateData#state.channels), + jlib:make_jid( + iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, + StateData#state.nick), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = + [{<<"type">>, + <<"error">>}], + children = [Error]}) + end, + dict:fetch_keys(StateData#state.channels)), StateData. +process_endofwhois(StateData, _String, Nick) -> + ejabberd_router:route(jlib:make_jid(iolist_to_binary([Nick, + <<"!">>, + StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"chat">>}], + children = + [#xmlel{name = <<"body">>, attrs = [], + children = + [{xmlcdata, + <<"End of WHOIS">>}]}]}). + +process_whois311(StateData, String, Nick, Ident, + Irchost) -> + Fullname = ejabberd_regexp:replace(String, + <<".*311[^:]*:">>, <<"">>), + ejabberd_router:route(jlib:make_jid(iolist_to_binary([Nick, + <<"!">>, + StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"chat">>}], + children = + [#xmlel{name = <<"body">>, attrs = [], + children = + [{xmlcdata, + iolist_to_binary( + [<<"WHOIS: ">>, + Nick, + <<" is ">>, + Ident, + <<"@">>, + Irchost, + <<" : ">>, + Fullname])}]}]}). + +process_whois312(StateData, String, Nick, Ircserver) -> + Ircserverdesc = ejabberd_regexp:replace(String, + <<".*312[^:]*:">>, <<"">>), + ejabberd_router:route(jlib:make_jid(iolist_to_binary([Nick, + <<"!">>, + StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"chat">>}], + children = + [#xmlel{name = <<"body">>, attrs = [], + children = + [{xmlcdata, + iolist_to_binary( + [<<"WHOIS: ">>, + Nick, + <<" use ">>, + Ircserver, + <<" : ">>, + Ircserverdesc])}]}]}). + +process_whois319(StateData, String, Nick) -> + Chanlist = ejabberd_regexp:replace(String, + <<".*319[^:]*:">>, <<"">>), + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [Nick, + <<"!">>, + StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"chat">>}], + children = + [#xmlel{name = <<"body">>, attrs = [], + children = + [{xmlcdata, + iolist_to_binary( + [<<"WHOIS: ">>, + Nick, + <<" is on ">>, + Chanlist])}]}]}). + +process_chanprivmsg(StateData, Chan, From, String) -> + [FromUser | _] = str:tokens(From, <<"!">>), + Msg = ejabberd_regexp:replace(String, + <<".*PRIVMSG[^:]*:">>, <<"">>), + Msg1 = case Msg of + <<1, $A, $C, $T, $I, $O, $N, $\s, Rest/binary>> -> + <<"/me ", Rest/binary>>; + _ -> Msg + end, + Msg2 = filter_message(Msg1), + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, FromUser), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"groupchat">>}], + children = + [#xmlel{name = <<"body">>, attrs = [], + children = [{xmlcdata, Msg2}]}]}). + +process_channotice(StateData, Chan, From, String) -> + [FromUser | _] = str:tokens(From, <<"!">>), + Msg = ejabberd_regexp:replace(String, + <<".*NOTICE[^:]*:">>, <<"">>), + Msg1 = case Msg of + <<1, $A, $C, $T, $I, $O, $N, $\s, Rest/binary>> -> + <<"/me ", Rest/binary>>; + _ -> <<"/me NOTICE: ", Msg/binary>> + end, + Msg2 = filter_message(Msg1), + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, FromUser), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"groupchat">>}], + children = + [#xmlel{name = <<"body">>, attrs = [], + children = [{xmlcdata, Msg2}]}]}). + +process_privmsg(StateData, _Nick, From, String) -> + [FromUser | _] = str:tokens(From, <<"!">>), + Msg = ejabberd_regexp:replace(String, + <<".*PRIVMSG[^:]*:">>, <<"">>), + Msg1 = case Msg of + <<1, $A, $C, $T, $I, $O, $N, $\s, Rest/binary>> -> + <<"/me ", Rest/binary>>; + _ -> Msg + end, + Msg2 = filter_message(Msg1), + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [FromUser, + <<"!">>, + StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"chat">>}], + children = + [#xmlel{name = <<"body">>, attrs = [], + children = [{xmlcdata, Msg2}]}]}). + +process_notice(StateData, _Nick, From, String) -> + [FromUser | _] = str:tokens(From, <<"!">>), + Msg = ejabberd_regexp:replace(String, + <<".*NOTICE[^:]*:">>, <<"">>), + Msg1 = case Msg of + <<1, $A, $C, $T, $I, $O, $N, $\s, Rest/binary>> -> + <<"/me ", Rest/binary>>; + _ -> <<"/me NOTICE: ", Msg/binary>> + end, + Msg2 = filter_message(Msg1), + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [FromUser, + <<"!">>, + StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"chat">>}], + children = + [#xmlel{name = <<"body">>, attrs = [], + children = [{xmlcdata, Msg2}]}]}). + +process_version(StateData, _Nick, From) -> + [FromUser | _] = str:tokens(From, <<"!">>), + send_text(StateData, + io_lib:format("NOTICE ~s :\001VERSION ejabberd IRC " + "transport ~s (c) Alexey Shchepin\001\r\n", + [FromUser, ?VERSION]) + ++ + io_lib:format("NOTICE ~s :\001VERSION http://ejabberd.jabber" + "studio.org/\001\r\n", + [FromUser])). + +process_userinfo(StateData, _Nick, From) -> + [FromUser | _] = str:tokens(From, <<"!">>), + send_text(StateData, + io_lib:format("NOTICE ~s :\001USERINFO xmpp:~s\001\r\n", + [FromUser, + jlib:jid_to_string(StateData#state.user)])). + +process_topic(StateData, Chan, From, String) -> + [FromUser | _] = str:tokens(From, <<"!">>), + Msg = ejabberd_regexp:replace(String, + <<".*TOPIC[^:]*:">>, <<"">>), + Msg1 = filter_message(Msg), + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, FromUser), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"groupchat">>}], + children = + [#xmlel{name = <<"subject">>, attrs = [], + children = [{xmlcdata, Msg1}]}, + #xmlel{name = <<"body">>, attrs = [], + children = + [{xmlcdata, + <<"/me has changed the subject to: ", + Msg1/binary>>}]}]}). + +process_part(StateData, Chan, From, String) -> + [FromUser | FromIdent] = str:tokens(From, <<"!">>), + Msg = ejabberd_regexp:replace(String, + <<".*PART[^:]*:">>, <<"">>), + Msg1 = filter_message(Msg), + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, FromUser), + StateData#state.user, + #xmlel{name = <<"presence">>, + attrs = [{<<"type">>, <<"unavailable">>}], + children = + [#xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, ?NS_MUC_USER}], + children = + [#xmlel{name = <<"item">>, + attrs = + [{<<"affiliation">>, + <<"member">>}, + {<<"role">>, + <<"none">>}], + children = []}]}, + #xmlel{name = <<"status">>, attrs = [], + children = + [{xmlcdata, + list_to_binary( + [Msg1, " (", + FromIdent, ")"])}]}]}), + case catch dict:update(Chan, + fun (Ps) -> remove_element(FromUser, Ps) end, + StateData#state.channels) + of + {'EXIT', _} -> StateData; + NS -> StateData#state{channels = NS} + end. + +process_quit(StateData, From, String) -> + [FromUser | FromIdent] = str:tokens(From, <<"!">>), + Msg = ejabberd_regexp:replace(String, + <<".*QUIT[^:]*:">>, <<"">>), + Msg1 = filter_message(Msg), + dict:map(fun (Chan, Ps) -> + case (?SETS):is_member(FromUser, Ps) of + true -> + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, + FromUser), + StateData#state.user, + #xmlel{name = <<"presence">>, + attrs = + [{<<"type">>, + <<"unavailable">>}], + children = + [#xmlel{name = + <<"x">>, + attrs = + [{<<"xmlns">>, + ?NS_MUC_USER}], + children = + [#xmlel{name + = + <<"item">>, + attrs + = + [{<<"affiliation">>, + <<"member">>}, + {<<"role">>, + <<"none">>}], + children + = + []}]}, + #xmlel{name = + <<"status">>, + attrs = [], + children = + [{xmlcdata, + list_to_binary( + [Msg1, " (", + FromIdent, + ")"])}]}]}), + remove_element(FromUser, Ps); + _ -> Ps + end + end, + StateData#state.channels), + StateData. process_join(StateData, Channel, From, _String) -> - [FromUser | FromIdent] = string:tokens(From, "!"), - Chan = lists:subtract(Channel, ":#"), - ejabberd_router:route( - jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - {xmlelement, "presence", [], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", - [{"affiliation", "member"}, - {"role", "participant"}], - []}]}, - {xmlelement, "status", [], - [{xmlcdata, FromIdent}]}]}), - + [FromUser | FromIdent] = str:tokens(From, <<"!">>), + [Chan | _] = binary:split(Channel, <<":#">>), + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, FromUser), + StateData#state.user, + #xmlel{name = <<"presence">>, attrs = [], + children = + [#xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, ?NS_MUC_USER}], + children = + [#xmlel{name = <<"item">>, + attrs = + [{<<"affiliation">>, + <<"member">>}, + {<<"role">>, + <<"participant">>}], + children = []}]}, + #xmlel{name = <<"status">>, attrs = [], + children = + [{xmlcdata, + list_to_binary(FromIdent)}]}]}), case catch dict:update(Chan, - fun(Ps) -> - ?SETS:add_element(FromUser, Ps) - end, StateData#state.channels) of - {'EXIT', _} -> - StateData; - NS -> - StateData#state{channels = NS} + fun (Ps) -> (?SETS):add_element(FromUser, Ps) end, + StateData#state.channels) + of + {'EXIT', _} -> StateData; + NS -> StateData#state{channels = NS} end. - - -process_mode_o(StateData, Chan, _From, Nick, Affiliation, Role) -> - %Msg = lists:last(string:tokens(String, ":")), - ejabberd_router:route( - jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, Nick), - StateData#state.user, - {xmlelement, "presence", [], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", - [{"affiliation", Affiliation}, - {"role", Role}], - []}]}]}). +process_mode_o(StateData, Chan, _From, Nick, + Affiliation, Role) -> + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, Nick), + StateData#state.user, + #xmlel{name = <<"presence">>, attrs = [], + children = + [#xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, ?NS_MUC_USER}], + children = + [#xmlel{name = <<"item">>, + attrs = + [{<<"affiliation">>, + Affiliation}, + {<<"role">>, + Role}], + children = []}]}]}). process_kick(StateData, Chan, From, Nick, String) -> - Msg = lists:last(string:tokens(String, ":")), - Msg2 = Nick ++ " kicked by " ++ From ++ " (" ++ filter_message(Msg) ++ ")", - ejabberd_router:route( - jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, ""), - StateData#state.user, - {xmlelement, "message", [{"type", "groupchat"}], - [{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}), - ejabberd_router:route( - jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, Nick), - StateData#state.user, - {xmlelement, "presence", [{"type", "unavailable"}], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", - [{"affiliation", "none"}, - {"role", "none"}], - []}, - {xmlelement, "status", [{"code", "307"}], []} - ]}]}). + Msg = lists:last(str:tokens(String, <<":">>)), + Msg2 = <>, + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"groupchat">>}], + children = + [#xmlel{name = <<"body">>, attrs = [], + children = [{xmlcdata, Msg2}]}]}), + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, Nick), + StateData#state.user, + #xmlel{name = <<"presence">>, + attrs = [{<<"type">>, <<"unavailable">>}], + children = + [#xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, ?NS_MUC_USER}], + children = + [#xmlel{name = <<"item">>, + attrs = + [{<<"affiliation">>, + <<"none">>}, + {<<"role">>, + <<"none">>}], + children = []}, + #xmlel{name = <<"status">>, + attrs = + [{<<"code">>, + <<"307">>}], + children = []}]}]}). process_nick(StateData, From, NewNick) -> - [FromUser | _] = string:tokens(From, "!"), - Nick = lists:subtract(NewNick, ":"), - NewChans = - dict:map( - fun(Chan, Ps) -> - case ?SETS:is_member(FromUser, Ps) of - true -> - ejabberd_router:route( - jlib:make_jid( - lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - {xmlelement, "presence", [{"type", "unavailable"}], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", - [{"affiliation", "member"}, - {"role", "participant"}, - {"nick", Nick}], - []}, - {xmlelement, "status", [{"code", "303"}], []} - ]}]}), - ejabberd_router:route( - jlib:make_jid( - lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, Nick), - StateData#state.user, - {xmlelement, "presence", [], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", - [{"affiliation", "member"}, - {"role", "participant"}], - []} - ]}]}), - ?SETS:add_element(Nick, - remove_element(FromUser, Ps)); - _ -> - Ps - end - end, StateData#state.channels), - if - FromUser == StateData#state.nick -> - StateData#state{nick = Nick, - nickchannel = undefined, - channels = NewChans}; - true -> - StateData#state{channels = NewChans} + [FromUser | _] = str:tokens(From, <<"!">>), + [Nick | _] = binary:split(NewNick, <<":">>), + NewChans = dict:map(fun (Chan, Ps) -> + case (?SETS):is_member(FromUser, Ps) of + true -> + ejabberd_router:route(jlib:make_jid( + iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, + FromUser), + StateData#state.user, + #xmlel{name = + <<"presence">>, + attrs = + [{<<"type">>, + <<"unavailable">>}], + children = + [#xmlel{name + = + <<"x">>, + attrs + = + [{<<"xmlns">>, + ?NS_MUC_USER}], + children + = + [#xmlel{name + = + <<"item">>, + attrs + = + [{<<"affiliation">>, + <<"member">>}, + {<<"role">>, + <<"participant">>}, + {<<"nick">>, + Nick}], + children + = + []}, + #xmlel{name + = + <<"status">>, + attrs + = + [{<<"code">>, + <<"303">>}], + children + = + []}]}]}), + ejabberd_router:route(jlib:make_jid( + iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, + Nick), + StateData#state.user, + #xmlel{name = + <<"presence">>, + attrs = [], + children = + [#xmlel{name + = + <<"x">>, + attrs + = + [{<<"xmlns">>, + ?NS_MUC_USER}], + children + = + [#xmlel{name + = + <<"item">>, + attrs + = + [{<<"affiliation">>, + <<"member">>}, + {<<"role">>, + <<"participant">>}], + children + = + []}]}]}), + (?SETS):add_element(Nick, + remove_element(FromUser, + Ps)); + _ -> Ps + end + end, + StateData#state.channels), + if FromUser == StateData#state.nick -> + StateData#state{nick = Nick, nickchannel = undefined, + channels = NewChans}; + true -> StateData#state{channels = NewChans} end. - process_error(StateData, String) -> - lists:foreach( - fun(Chan) -> - ejabberd_router:route( - jlib:make_jid( - lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, StateData#state.nick), - StateData#state.user, - {xmlelement, "presence", [{"type", "error"}], - [{xmlelement, "error", [{"code", "502"}], - [{xmlcdata, String}]}]}) - end, dict:fetch_keys(StateData#state.channels)). + lists:foreach(fun (Chan) -> + ejabberd_router:route(jlib:make_jid( + iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, + StateData#state.nick), + StateData#state.user, + #xmlel{name = <<"presence">>, + attrs = + [{<<"type">>, + <<"error">>}], + children = + [#xmlel{name = + <<"error">>, + attrs = + [{<<"code">>, + <<"502">>}], + children = + [{xmlcdata, + String}]}]}) + end, + dict:fetch_keys(StateData#state.channels)). error_unknown_num(_StateData, String, Type) -> - Msg = ejabberd_regexp:replace(String, ".*[45][0-9][0-9] +[^ ]* +", ""), + Msg = ejabberd_regexp:replace(String, + <<".*[45][0-9][0-9] +[^ ]* +">>, <<"">>), Msg1 = filter_message(Msg), - {xmlelement, "error", [{"code", "500"}, {"type", Type}], - [{xmlelement, "undefined-condition", [{"xmlns", ?NS_STANZAS}], []}, - {xmlelement, "text", [{"xmlns", ?NS_STANZAS}], - [{xmlcdata, Msg1}]}]}. - - + #xmlel{name = <<"error">>, + attrs = [{<<"code">>, <<"500">>}, {<<"type">>, Type}], + children = + [#xmlel{name = <<"undefined-condition">>, + attrs = [{<<"xmlns">>, ?NS_STANZAS}], children = []}, + #xmlel{name = <<"text">>, + attrs = [{<<"xmlns">>, ?NS_STANZAS}], + children = [{xmlcdata, Msg1}]}]}. remove_element(E, Set) -> - case ?SETS:is_element(E, Set) of - true -> - ?SETS:del_element(E, Set); - _ -> - Set + case (?SETS):is_element(E, Set) of + true -> (?SETS):del_element(E, Set); + _ -> Set end. - - iq_admin(StateData, Channel, From, To, #iq{type = Type, xmlns = XMLNS, sub_el = SubEl} = IQ) -> - case catch process_iq_admin(StateData, Channel, Type, SubEl) of - {'EXIT', Reason} -> - ?ERROR_MSG("~p", [Reason]); - Res -> - if - Res /= ignore -> - ResIQ = case Res of - {result, ResEls} -> - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - ResEls - }]}; - {error, Error} -> - IQ#iq{type = error, - sub_el = [SubEl, Error]} - end, - ejabberd_router:route(To, From, - jlib:iq_to_xml(ResIQ)); - true -> - ok - end + case catch process_iq_admin(StateData, Channel, Type, + SubEl) + of + {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); + Res -> + if Res /= ignore -> + ResIQ = case Res of + {result, ResEls} -> + IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, XMLNS}], + children = ResEls}]}; + {error, Error} -> + IQ#iq{type = error, sub_el = [SubEl, Error]} + end, + ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)); + true -> ok + end end. - process_iq_admin(StateData, Channel, set, SubEl) -> - case xml:get_subtag(SubEl, "item") of - false -> - {error, ?ERR_BAD_REQUEST}; - ItemEl -> - Nick = xml:get_tag_attr_s("nick", ItemEl), - Affiliation = xml:get_tag_attr_s("affiliation", ItemEl), - Role = xml:get_tag_attr_s("role", ItemEl), - Reason = xml:get_path_s(ItemEl, [{elem, "reason"}, cdata]), - process_admin(StateData, Channel, Nick, Affiliation, Role, Reason) + case xml:get_subtag(SubEl, <<"item">>) of + false -> {error, ?ERR_BAD_REQUEST}; + ItemEl -> + Nick = xml:get_tag_attr_s(<<"nick">>, ItemEl), + Affiliation = xml:get_tag_attr_s(<<"affiliation">>, + ItemEl), + Role = xml:get_tag_attr_s(<<"role">>, ItemEl), + Reason = xml:get_path_s(ItemEl, + [{elem, <<"reason">>}, cdata]), + process_admin(StateData, Channel, Nick, Affiliation, + Role, Reason) end; process_iq_admin(_StateData, _Channel, get, _SubEl) -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED}. - - -process_admin(_StateData, _Channel, "", _Affiliation, _Role, _Reason) -> +process_admin(_StateData, _Channel, <<"">>, + _Affiliation, _Role, _Reason) -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED}; - -process_admin(StateData, Channel, Nick, _Affiliation, "none", Reason) -> +process_admin(StateData, Channel, Nick, _Affiliation, + <<"none">>, Reason) -> case Reason of - "" -> - send_text(StateData, - io_lib:format("KICK #~s ~s\r\n", - [Channel, Nick])); - _ -> - send_text(StateData, - io_lib:format("KICK #~s ~s :~s\r\n", - [Channel, Nick, Reason])) + <<"">> -> + send_text(StateData, + io_lib:format("KICK #~s ~s\r\n", [Channel, Nick])); + _ -> + send_text(StateData, + io_lib:format("KICK #~s ~s :~s\r\n", + [Channel, Nick, Reason])) end, {result, []}; - - - -process_admin(_StateData, _Channel, _Nick, _Affiliation, _Role, _Reason) -> +process_admin(_StateData, _Channel, _Nick, _Affiliation, + _Role, _Reason) -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED}. - - filter_message(Msg) -> - lists:filter( - fun(C) -> - if (C < 32) and - (C /= 9) and - (C /= 10) and - (C /= 13) -> - false; - true -> true - end - end, filter_mirc_colors(Msg)). + list_to_binary( + lists:filter(fun (C) -> + if (C < 32) and (C /= 9) and (C /= 10) and (C /= 13) -> + false; + true -> true + end + end, + binary_to_list(filter_mirc_colors(Msg)))). filter_mirc_colors(Msg) -> - ejabberd_regexp:greplace(Msg, "(\\003[0-9]+)(,[0-9]+)?", ""). + ejabberd_regexp:greplace(Msg, + <<"(\\003[0-9]+)(,[0-9]+)?">>, <<"">>). unixtime2string(Unixtime) -> - Secs = Unixtime + calendar:datetime_to_gregorian_seconds( - {{1970, 1, 1}, {0,0,0}}), + Secs = Unixtime + + calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, + {0, 0, 0}}), {{Year, Month, Day}, {Hour, Minute, Second}} = - calendar:universal_time_to_local_time( - calendar:gregorian_seconds_to_datetime(Secs)), - lists:flatten( - io_lib:format("~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", - [Year, Month, Day, Hour, Minute, Second])). - -toupper([C | Cs]) -> - if - C >= $a, C =< $z -> - [C - 32 | toupper(Cs)]; - true -> - [C | toupper(Cs)] - end; -toupper([]) -> - []. + calendar:universal_time_to_local_time(calendar:gregorian_seconds_to_datetime(Secs)), + iolist_to_binary(io_lib:format("~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", + [Year, Month, Day, Hour, Minute, Second])). diff --git a/src/mod_last.erl b/src/mod_last.erl index 8403b76d0..e8df5a270 100644 --- a/src/mod_last.erl +++ b/src/mod_last.erl @@ -25,182 +25,198 @@ %%%---------------------------------------------------------------------- -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, - [{disc_copies, [node()]}, - {attributes, - record_info(fields, last_activity)}]), - update_table(); - _ -> - ok + mnesia -> + mnesia:create_table(last_activity, + [{disc_copies, [node()]}, + {attributes, + record_info(fields, last_activity)}]), + update_table(); + _ -> 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)}], - []}]} + set -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; + get -> + Sec = get_node_uptime(), + IQ#iq{type = result, + 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]}; - get -> - User = To#jid.luser, - Server = To#jid.lserver, - {Subscription, _Groups} = - 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, - [User, Server, UserListRecord, - {To, From, - {xmlelement, "presence", [], []}}, - out]) of - allow -> - get_last_iq(IQ, SubEl, User, Server); - deny -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_FORBIDDEN]} - end; - true -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_FORBIDDEN]} - end + set -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; + get -> + User = To#jid.luser, + Server = To#jid.lserver, + {Subscription, _Groups} = + 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, + [User, Server, UserListRecord, + {To, From, + #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]} + end; + true -> + 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}] -> - {ok, TimeStamp, 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"], []} -> - not_found; - {selected, ["seconds","state"], [{STimeStamp, Status}]} -> - case catch list_to_integer(STimeStamp) of - TimeStamp when is_integer(TimeStamp) -> - {ok, TimeStamp, Status}; - Reason -> - {error, {invalid_timestamp, Reason}} - end; - Reason -> - {error, {invalid_result, Reason}} + {selected, [<<"seconds">>, <<"state">>], []} -> + not_found; + {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}} + end; + Reason -> {error, {invalid_result, Reason}} end. get_last_iq(IQ, SubEl, LUser, LServer) -> case ejabberd_sm:get_user_resources(LUser, LServer) of - [] -> - case get_last(LUser, LServer) of - {error, _Reason} -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}; - not_found -> - 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}]}]} - end; - _ -> - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", ?NS_LAST}, - {"seconds", "0"}], - []}]} + [] -> + case get_last(LUser, LServer) of + {error, _Reason} -> + IQ#iq{type = error, + sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}; + not_found -> + 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 = + [#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 = + [#xmlel{name = <<"query">>, + attrs = + [{<<"xmlns">>, ?NS_LAST}, + {<<"seconds">>, <<"0">>}], + children = []}]} end. on_presence_update(User, Server, _Resource, Status) -> @@ -211,30 +227,33 @@ store_last_info(User, Server, TimeStamp, Status) -> LUser = jlib:nodeprep(User), LServer = jlib:nameprep(Server), DBType = gen_mod:db_type(LServer, ?MODULE), - store_last_info(LUser, LServer, TimeStamp, Status, DBType). + store_last_info(LUser, LServer, TimeStamp, Status, + DBType). -store_last_info(LUser, LServer, TimeStamp, Status, mnesia) -> +store_last_info(LUser, LServer, TimeStamp, Status, + mnesia) -> US = {LUser, LServer}, - F = fun() -> + F = fun () -> mnesia:write(#last_activity{us = US, timestamp = TimeStamp, status = Status}) end, mnesia:transaction(F); -store_last_info(LUser, LServer, TimeStamp, Status, odbc) -> +store_last_info(LUser, LServer, TimeStamp, Status, + odbc) -> Username = ejabberd_odbc:escape(LUser), - Seconds = ejabberd_odbc:escape(integer_to_list(TimeStamp)), + Seconds = + ejabberd_odbc:escape(iolist_to_binary(integer_to_list(TimeStamp))), State = ejabberd_odbc:escape(Status), - odbc_queries:set_last_t(LServer, Username, Seconds, State). + odbc_queries:set_last_t(LServer, Username, Seconds, + State). %% @spec (LUser::string(), LServer::string()) -> %% {ok, TimeStamp::integer(), Status::string()} | not_found get_last_info(LUser, LServer) -> case get_last(LUser, LServer) of - {error, _Reason} -> - not_found; - Res -> - Res + {error, _Reason} -> not_found; + Res -> Res end. remove_user(User, Server) -> @@ -245,9 +264,7 @@ remove_user(User, Server) -> remove_user(LUser, LServer, mnesia) -> US = {LUser, LServer}, - F = fun() -> - mnesia:delete({last_activity, US}) - end, + F = fun () -> mnesia:delete({last_activity, US}) end, mnesia:transaction(F); remove_user(LUser, LServer, odbc) -> Username = ejabberd_odbc:escape(LUser), @@ -256,48 +273,34 @@ remove_user(LUser, LServer, odbc) -> 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); - _ -> - ?INFO_MSG("Recreating last_activity table", []), - mnesia:transform_table(last_activity, ignore, Fields) + Fields -> + 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}]. diff --git a/src/mod_muc/Makefile.in b/src/mod_muc/Makefile.in index 41315ad29..5ede5e521 100644 --- a/src/mod_muc/Makefile.in +++ b/src/mod_muc/Makefile.in @@ -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) diff --git a/src/mod_muc/mod_muc.erl b/src/mod_muc/mod_muc.erl index d6414a59e..6587cc5d0 100644 --- a/src/mod_muc/mod_muc.erl +++ b/src/mod_muc/mod_muc.erl @@ -25,9 +25,11 @@ %%%---------------------------------------------------------------------- -module(mod_muc). + -author('alexey@process-one.net'). -behaviour(gen_server). + -behaviour(gen_mod). %% API @@ -44,23 +46,32 @@ can_use_nick/4]). %% 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(muc_room, {name_host = {<<"">>, <<"">>} :: {binary(), binary()} | + {'_', binary()}, + opts = [] :: list() | '_'}). --record(muc_room, {name_host, opts}). --record(muc_online_room, {name_host, pid}). --record(muc_registered, {us_host, nick}). +-record(muc_online_room, + {name_host = {<<"">>, <<"">>} :: {binary(), binary()} | '$1', + pid = self() :: pid() | '$2' | '_'}). --record(state, {host, - server_host, - access, - history_size, - default_room_opts, - room_shaper}). +-record(muc_registered, + {us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()}, binary()} | '$1', + nick = <<"">> :: binary()}). + +-record(state, + {host = <<"">> :: binary(), + server_host = <<"">> :: binary(), + access = {none, none, none, none} :: {atom(), atom(), atom(), atom()}, + history_size = 20 :: non_neg_integer(), + default_room_opts = [] :: list(), + room_shaper = none :: shaper:shaper()}). -define(PROCNAME, ejabberd_mod_muc). @@ -73,18 +84,14 @@ %%-------------------------------------------------------------------- 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) -> start_supervisor(Host), 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) -> @@ -101,7 +108,7 @@ stop(Host) -> %% So the message sending must be catched room_destroyed(Host, Room, Pid, ServerHost) -> catch gen_mod:get_module_proc(ServerHost, ?PROCNAME) ! - {room_destroyed, {Room, Host}, Pid}, + {room_destroyed, {Room, Host}, Pid}, ok. %% @doc Create a room. @@ -113,10 +120,11 @@ create_room(Host, Name, From, Nick, Opts) -> store_room(ServerHost, Host, Name, Opts) -> LServer = jlib:nameprep(ServerHost), - store_room(LServer, Host, Name, Opts, gen_mod:db_type(LServer, ?MODULE)). + store_room(LServer, Host, Name, Opts, + gen_mod:db_type(LServer, ?MODULE)). store_room(_LServer, Host, Name, Opts, mnesia) -> - F = fun() -> + F = fun () -> mnesia:write(#muc_room{name_host = {Name, Host}, opts = Opts}) end, @@ -125,104 +133,98 @@ store_room(LServer, Host, Name, Opts, odbc) -> SName = ejabberd_odbc:escape(Name), SHost = ejabberd_odbc:escape(Host), SOpts = ejabberd_odbc:encode_term(Opts), - F = fun() -> - odbc_queries:update_t( - "muc_room", - ["name", "host", "opts"], - [SName, SHost, SOpts], - ["name='", SName, "' and host='", SHost, "'"]) + F = fun () -> + odbc_queries:update_t(<<"muc_room">>, + [<<"name">>, <<"host">>, <<"opts">>], + [SName, SHost, SOpts], + [<<"name='">>, SName, <<"' and host='">>, + SHost, <<"'">>]) end, ejabberd_odbc:sql_transaction(LServer, F). restore_room(ServerHost, Host, Name) -> LServer = jlib:nameprep(ServerHost), - restore_room(LServer, Host, Name, gen_mod:db_type(LServer, ?MODULE)). + restore_room(LServer, Host, Name, + gen_mod:db_type(LServer, ?MODULE)). restore_room(_LServer, Host, Name, mnesia) -> case catch mnesia:dirty_read(muc_room, {Name, Host}) of - [#muc_room{opts = Opts}] -> - Opts; - _ -> - error + [#muc_room{opts = Opts}] -> Opts; + _ -> error end; restore_room(LServer, Host, Name, odbc) -> SName = ejabberd_odbc:escape(Name), SHost = ejabberd_odbc:escape(Host), - case catch ejabberd_odbc:sql_query( - LServer, ["select opts from muc_room where name='", - SName, "' and host='", SHost, "';"]) of - {selected, ["opts"], [{Opts}]} -> - ejabberd_odbc:decode_term(Opts); - _ -> - error + case catch ejabberd_odbc:sql_query(LServer, + [<<"select opts from muc_room where name='">>, + SName, <<"' and host='">>, SHost, + <<"';">>]) + of + {selected, [<<"opts">>], [[Opts]]} -> + opts_to_binary(ejabberd_odbc:decode_term(Opts)); + _ -> error end. forget_room(ServerHost, Host, Name) -> LServer = jlib:nameprep(ServerHost), - forget_room(LServer, Host, Name, gen_mod:db_type(LServer, ?MODULE)). + forget_room(LServer, Host, Name, + gen_mod:db_type(LServer, ?MODULE)). forget_room(_LServer, Host, Name, mnesia) -> - F = fun() -> - mnesia:delete({muc_room, {Name, Host}}) + F = fun () -> mnesia:delete({muc_room, {Name, Host}}) end, mnesia:transaction(F); forget_room(LServer, Host, Name, odbc) -> SName = ejabberd_odbc:escape(Name), SHost = ejabberd_odbc:escape(Host), - F = fun() -> - ejabberd_odbc:sql_query_t( - ["delete from muc_room where name='", - SName, "' and host='", SHost, "';"]) + F = fun () -> + ejabberd_odbc:sql_query_t([<<"delete from muc_room where name='">>, + SName, <<"' and host='">>, SHost, + <<"';">>]) end, ejabberd_odbc:sql_transaction(LServer, F). -process_iq_disco_items(Host, From, To, #iq{lang = Lang} = IQ) -> +process_iq_disco_items(Host, From, To, + #iq{lang = Lang} = IQ) -> Rsm = jlib:rsm_decode(IQ), Res = IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", ?NS_DISCO_ITEMS}], - iq_disco_items(Host, From, Lang, Rsm)}]}, - ejabberd_router:route(To, - From, - jlib:iq_to_xml(Res)). + sub_el = + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}], + children = iq_disco_items(Host, From, Lang, Rsm)}]}, + ejabberd_router:route(To, From, jlib:iq_to_xml(Res)). -can_use_nick(_ServerHost, _Host, _JID, "") -> - false; +can_use_nick(_ServerHost, _Host, _JID, <<"">>) -> false; can_use_nick(ServerHost, Host, JID, Nick) -> LServer = jlib:nameprep(ServerHost), - can_use_nick(LServer, Host, JID, Nick, gen_mod:db_type(LServer, ?MODULE)). + can_use_nick(LServer, Host, JID, Nick, + gen_mod:db_type(LServer, ?MODULE)). can_use_nick(_LServer, Host, JID, Nick, mnesia) -> {LUser, LServer, _} = jlib:jid_tolower(JID), LUS = {LUser, LServer}, - case catch mnesia:dirty_select( - muc_registered, - [{#muc_registered{us_host = '$1', - nick = Nick, - _ = '_'}, - [{'==', {element, 2, '$1'}, Host}], - ['$_']}]) of - {'EXIT', _Reason} -> - true; - [] -> - true; - [#muc_registered{us_host = {U, _Host}}] -> - U == LUS + case catch mnesia:dirty_select(muc_registered, + [{#muc_registered{us_host = '$1', + nick = Nick, _ = '_'}, + [{'==', {element, 2, '$1'}, Host}], + ['$_']}]) + of + {'EXIT', _Reason} -> true; + [] -> true; + [#muc_registered{us_host = {U, _Host}}] -> U == LUS end; can_use_nick(LServer, Host, JID, Nick, odbc) -> - SJID = jlib:jid_to_string( - jlib:jid_tolower( - jlib:jid_remove_resource(JID))), + SJID = + jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(JID))), SNick = ejabberd_odbc:escape(Nick), SHost = ejabberd_odbc:escape(Host), - case catch ejabberd_odbc:sql_query( - LServer, ["select jid from muc_registered ", - "where nick='", SNick, "' and host='", - SHost, "';"]) of - {selected, ["jid"], [{SJID1}]} -> - SJID == SJID1; - _ -> - true + case catch ejabberd_odbc:sql_query(LServer, + [<<"select jid from muc_registered ">>, + <<"where nick='">>, SNick, + <<"' and host='">>, SHost, <<"';">>]) + of + {selected, [<<"jid">>], [[SJID1]]} -> SJID == SJID1; + _ -> true end. %%==================================================================== @@ -237,7 +239,8 @@ can_use_nick(LServer, Host, JID, Nick, odbc) -> %% Description: Initiates the server %%-------------------------------------------------------------------- init([Host, Opts]) -> - MyHost = gen_mod:get_opt_host(Host, Opts, "conference.@HOST@"), + MyHost = gen_mod:get_opt_host(Host, Opts, + <<"conference.@HOST@">>), case gen_mod:db_type(Opts) of mnesia -> mnesia:create_table(muc_room, @@ -260,13 +263,13 @@ init([Host, Opts]) -> catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]), clean_table_from_bad_node(node(), MyHost), mnesia:subscribe(system), - Access = gen_mod:get_opt(access, Opts, all), - AccessCreate = gen_mod:get_opt(access_create, Opts, all), - AccessAdmin = gen_mod:get_opt(access_admin, Opts, none), - AccessPersistent = gen_mod:get_opt(access_persistent, Opts, all), - HistorySize = gen_mod:get_opt(history_size, Opts, 20), - DefRoomOpts = gen_mod:get_opt(default_room_options, Opts, []), - RoomShaper = gen_mod:get_opt(room_shaper, Opts, none), + Access = gen_mod:get_opt(access, Opts, fun(A) -> A end, all), + AccessCreate = gen_mod:get_opt(access_create, Opts, fun(A) -> A end, all), + AccessAdmin = gen_mod:get_opt(access_admin, Opts, fun(A) -> A end, none), + AccessPersistent = gen_mod:get_opt(access_persistent, Opts, fun(A) -> A end, all), + HistorySize = gen_mod:get_opt(history_size, Opts, fun(A) -> A end, 20), + DefRoomOpts = gen_mod:get_opt(default_room_options, Opts, fun(A) -> A end, []), + RoomShaper = gen_mod:get_opt(room_shaper, Opts, fun(A) -> A end, none), ejabberd_router:register_route(MyHost), load_permanent_rooms(MyHost, Host, {Access, AccessCreate, AccessAdmin, AccessPersistent}, @@ -290,19 +293,15 @@ init([Host, Opts]) -> %%-------------------------------------------------------------------- handle_call(stop, _From, State) -> {stop, normal, ok, State}; - -handle_call({create, Room, From, Nick, Opts}, - _From, - #state{host = Host, - server_host = ServerHost, - access = Access, - default_room_opts = DefOpts, +handle_call({create, Room, From, Nick, Opts}, _From, + #state{host = Host, server_host = ServerHost, + access = Access, default_room_opts = DefOpts, history_size = HistorySize, room_shaper = RoomShaper} = State) -> ?DEBUG("MUC: create new room '~s'~n", [Room]), NewOpts = case Opts of - default -> DefOpts; - _ -> Opts + default -> DefOpts; + _ -> Opts end, {ok, Pid} = mod_muc_room:start( Host, ServerHost, Access, @@ -318,8 +317,7 @@ handle_call({create, Room, From, Nick, Opts}, %% {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} | @@ -328,10 +326,8 @@ handle_cast(_Msg, State) -> %% Description: Handling all non call/cast messages %%-------------------------------------------------------------------- handle_info({route, From, To, Packet}, - #state{host = Host, - server_host = ServerHost, - access = Access, - default_room_opts = DefRoomOpts, + #state{host = Host, server_host = ServerHost, + access = Access, default_room_opts = DefRoomOpts, history_size = HistorySize, room_shaper = RoomShaper} = State) -> case catch do_route(Host, ServerHost, Access, HistorySize, RoomShaper, @@ -343,8 +339,9 @@ handle_info({route, From, To, Packet}, end, {noreply, State}; handle_info({room_destroyed, RoomHost, Pid}, State) -> - F = fun() -> - mnesia:delete_object(#muc_online_room{name_host = RoomHost, + F = fun () -> + mnesia:delete_object(#muc_online_room{name_host = + RoomHost, pid = Pid}) end, mnesia:transaction(F), @@ -352,8 +349,7 @@ handle_info({room_destroyed, RoomHost, Pid}, State) -> handle_info({mnesia_system_event, {mnesia_down, Node}}, State) -> clean_table_from_bad_node(Node), {noreply, State}; -handle_info(_Info, State) -> - {noreply, State}. +handle_info(_Info, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: terminate(Reason, State) -> void() @@ -370,26 +366,22 @@ 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 %%-------------------------------------------------------------------- start_supervisor(Host) -> - Proc = gen_mod:get_module_proc(Host, ejabberd_mod_muc_sup), - ChildSpec = - {Proc, - {ejabberd_tmp_sup, start_link, - [Proc, mod_muc_room]}, - permanent, - infinity, - supervisor, - [ejabberd_tmp_sup]}, + Proc = gen_mod:get_module_proc(Host, + ejabberd_mod_muc_sup), + ChildSpec = {Proc, + {ejabberd_tmp_sup, start_link, [Proc, mod_muc_room]}, + permanent, infinity, supervisor, [ejabberd_tmp_sup]}, supervisor:start_child(ejabberd_sup, ChildSpec). stop_supervisor(Host) -> - Proc = gen_mod:get_module_proc(Host, ejabberd_mod_muc_sup), + Proc = gen_mod:get_module_proc(Host, + ejabberd_mod_muc_sup), supervisor:terminate_child(ejabberd_sup, Proc), supervisor:delete_child(ejabberd_sup, Proc). @@ -401,9 +393,9 @@ do_route(Host, ServerHost, Access, HistorySize, RoomShaper, do_route1(Host, ServerHost, Access, HistorySize, RoomShaper, From, To, Packet, DefRoomOpts); _ -> - {xmlelement, _Name, Attrs, _Els} = Packet, - Lang = xml:get_attr_s("xml:lang", Attrs), - ErrText = "Access denied by service policy", + #xmlel{attrs = Attrs} = Packet, + Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), + ErrText = <<"Access denied by service policy">>, Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, ErrText)), ejabberd_router:route_error(To, From, Err, Packet) @@ -414,128 +406,127 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper, From, To, Packet, DefRoomOpts) -> {_AccessRoute, AccessCreate, AccessAdmin, _AccessPersistent} = Access, {Room, _, Nick} = jlib:jid_tolower(To), - {xmlelement, Name, Attrs, _Els} = Packet, + #xmlel{name = Name, attrs = Attrs} = Packet, case Room of - "" -> - case Nick of - "" -> - case Name of - "iq" -> - case jlib:iq_query_info(Packet) of - #iq{type = get, xmlns = ?NS_DISCO_INFO = XMLNS, - sub_el = _SubEl, lang = Lang} = IQ -> - Info = ejabberd_hooks:run_fold( - disco_info, ServerHost, [], - [ServerHost, ?MODULE, "", ""]), - Res = IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - iq_disco_info(Lang) - ++Info}]}, - ejabberd_router:route(To, - From, - jlib:iq_to_xml(Res)); - #iq{type = get, - xmlns = ?NS_DISCO_ITEMS} = IQ -> - spawn(?MODULE, - process_iq_disco_items, - [Host, From, To, IQ]); - #iq{type = get, - xmlns = ?NS_REGISTER = XMLNS, - lang = Lang, - sub_el = _SubEl} = IQ -> - Res = IQ#iq{type = result, - sub_el = - [{xmlelement, "query", - [{"xmlns", XMLNS}], - iq_get_register_info( - ServerHost, Host, From, Lang)}]}, - ejabberd_router:route(To, - From, - jlib:iq_to_xml(Res)); - #iq{type = set, - xmlns = ?NS_REGISTER = XMLNS, - lang = Lang, - sub_el = SubEl} = IQ -> - case process_iq_register_set( - ServerHost, Host, From, SubEl, Lang) of - {result, IQRes} -> - Res = IQ#iq{type = result, - sub_el = - [{xmlelement, "query", - [{"xmlns", XMLNS}], - IQRes}]}, - ejabberd_router:route( - To, From, jlib:iq_to_xml(Res)); - {error, Error} -> - Err = jlib:make_error_reply( - Packet, Error), - ejabberd_router:route( - To, From, Err) - end; - #iq{type = get, - xmlns = ?NS_VCARD = XMLNS, - lang = Lang, - sub_el = _SubEl} = IQ -> - Res = IQ#iq{type = result, - sub_el = - [{xmlelement, "vCard", - [{"xmlns", XMLNS}], - iq_get_vcard(Lang)}]}, - ejabberd_router:route(To, - From, - jlib:iq_to_xml(Res)); - #iq{type = get, - xmlns = ?NS_MUC_UNIQUE - } = IQ -> - Res = IQ#iq{type = result, - sub_el = - [{xmlelement, "unique", - [{"xmlns", ?NS_MUC_UNIQUE}], - [iq_get_unique(From)]}]}, - ejabberd_router:route(To, - From, - jlib:iq_to_xml(Res)); - #iq{} -> - Err = jlib:make_error_reply( - Packet, - ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - _ -> - ok + <<"">> -> + case Nick of + <<"">> -> + case Name of + <<"iq">> -> + case jlib:iq_query_info(Packet) of + #iq{type = get, xmlns = (?NS_DISCO_INFO) = XMLNS, + sub_el = _SubEl, lang = Lang} = + IQ -> + Info = ejabberd_hooks:run_fold(disco_info, + ServerHost, [], + [ServerHost, ?MODULE, + <<"">>, <<"">>]), + Res = IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = + [{<<"xmlns">>, XMLNS}], + children = + iq_disco_info(Lang) ++ + Info}]}, + ejabberd_router:route(To, From, + jlib:iq_to_xml(Res)); + #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ -> + spawn(?MODULE, process_iq_disco_items, + [Host, From, To, IQ]); + #iq{type = get, xmlns = (?NS_REGISTER) = XMLNS, + lang = Lang, sub_el = _SubEl} = + IQ -> + Res = IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = + [{<<"xmlns">>, XMLNS}], + children = + iq_get_register_info(ServerHost, + Host, + From, + Lang)}]}, + ejabberd_router:route(To, From, + jlib:iq_to_xml(Res)); + #iq{type = set, xmlns = (?NS_REGISTER) = XMLNS, + lang = Lang, sub_el = SubEl} = + IQ -> + case process_iq_register_set(ServerHost, Host, From, + SubEl, Lang) + of + {result, IQRes} -> + Res = IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = + [{<<"xmlns">>, + XMLNS}], + children = IQRes}]}, + ejabberd_router:route(To, From, + jlib:iq_to_xml(Res)); + {error, Error} -> + Err = jlib:make_error_reply(Packet, Error), + ejabberd_router:route(To, From, Err) end; - "message" -> - case xml:get_attr_s("type", Attrs) of - "error" -> - ok; - _ -> - case acl:match_rule(ServerHost, AccessAdmin, From) of - allow -> - Msg = xml:get_path_s( - Packet, - [{elem, "body"}, cdata]), - broadcast_service_message(Host, Msg); - _ -> - Lang = xml:get_attr_s("xml:lang", Attrs), - ErrText = "Only service administrators " - "are allowed to send service messages", - Err = jlib:make_error_reply( - Packet, - ?ERRT_FORBIDDEN(Lang, ErrText)), - ejabberd_router:route( - To, From, Err) - end - end; - "presence" -> - ok - end; - _ -> - case xml:get_attr_s("type", Attrs) of - "error" -> - ok; - "result" -> - ok; + #iq{type = get, xmlns = (?NS_VCARD) = XMLNS, + lang = Lang, sub_el = _SubEl} = + IQ -> + Res = IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"vCard">>, + attrs = + [{<<"xmlns">>, XMLNS}], + children = + iq_get_vcard(Lang)}]}, + ejabberd_router:route(To, From, + jlib:iq_to_xml(Res)); + #iq{type = get, xmlns = ?NS_MUC_UNIQUE} = IQ -> + Res = IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"unique">>, + attrs = + [{<<"xmlns">>, + ?NS_MUC_UNIQUE}], + children = + [iq_get_unique(From)]}]}, + ejabberd_router:route(To, From, + jlib:iq_to_xml(Res)); + #iq{} -> + Err = jlib:make_error_reply(Packet, + ?ERR_FEATURE_NOT_IMPLEMENTED), + ejabberd_router:route(To, From, Err); + _ -> ok + end; + <<"message">> -> + case xml:get_attr_s(<<"type">>, Attrs) of + <<"error">> -> ok; _ -> + case acl:match_rule(ServerHost, AccessAdmin, From) + of + allow -> + Msg = xml:get_path_s(Packet, + [{elem, <<"body">>}, + cdata]), + broadcast_service_message(Host, Msg); + _ -> + Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), + ErrText = + <<"Only service administrators are allowed " + "to send service messages">>, + Err = jlib:make_error_reply(Packet, + ?ERRT_FORBIDDEN(Lang, + ErrText)), + ejabberd_router:route(To, From, Err) + end + end; + <<"presence">> -> ok + end; + _ -> + case xml:get_attr_s(<<"type">>, Attrs) of + <<"error">> -> ok; + <<"result">> -> ok; + _ -> Err = jlib:make_error_reply( Packet, ?ERR_ITEM_NOT_FOUND), ejabberd_router:route(To, From, Err) @@ -544,9 +535,9 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper, _ -> case mnesia:dirty_read(muc_online_room, {Room, Host}) of [] -> - Type = xml:get_attr_s("type", Attrs), + Type = xml:get_attr_s(<<"type">>, Attrs), case {Name, Type} of - {"presence", ""} -> + {<<"presence">>, <<"">>} -> case check_user_can_create_room(ServerHost, AccessCreate, From, Room) of @@ -560,65 +551,68 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper, mod_muc_room:route(Pid, From, Nick, Packet), ok; false -> - Lang = xml:get_attr_s("xml:lang", Attrs), - ErrText = "Room creation is denied by service policy", + Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), + ErrText = <<"Room creation is denied by service policy">>, Err = jlib:make_error_reply( Packet, ?ERRT_FORBIDDEN(Lang, ErrText)), ejabberd_router:route(To, From, Err) end; _ -> - Lang = xml:get_attr_s("xml:lang", Attrs), - ErrText = "Conference room does not exist", - Err = jlib:make_error_reply( - Packet, ?ERRT_ITEM_NOT_FOUND(Lang, ErrText)), - ejabberd_router:route(To, From, Err) - end; - [R] -> - Pid = R#muc_online_room.pid, - ?DEBUG("MUC: send to process ~p~n", [Pid]), - mod_muc_room:route(Pid, From, Nick, Packet), - ok - end + Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), + ErrText = <<"Conference room does not exist">>, + Err = jlib:make_error_reply(Packet, + ?ERRT_ITEM_NOT_FOUND(Lang, + ErrText)), + ejabberd_router:route(To, From, Err) + end; + [R] -> + Pid = R#muc_online_room.pid, + ?DEBUG("MUC: send to process ~p~n", [Pid]), + mod_muc_room:route(Pid, From, Nick, Packet), + ok + end end. -check_user_can_create_room(ServerHost, AccessCreate, From, RoomID) -> +check_user_can_create_room(ServerHost, AccessCreate, + From, RoomID) -> case acl:match_rule(ServerHost, AccessCreate, From) of - allow -> - (length(RoomID) =< gen_mod:get_module_opt(ServerHost, ?MODULE, - max_room_id, infinite)); - _ -> - false + allow -> + byte_size(RoomID) =< + gen_mod:get_module_opt(ServerHost, ?MODULE, max_room_id, + fun(infinity) -> infinity; + (I) when is_integer(I), I>0 -> I + end, infinity); + _ -> false end. get_rooms(ServerHost, Host) -> LServer = jlib:nameprep(ServerHost), - get_rooms(LServer, Host, gen_mod:db_type(LServer, ?MODULE)). + get_rooms(LServer, Host, + gen_mod:db_type(LServer, ?MODULE)). get_rooms(_LServer, Host, mnesia) -> - case catch mnesia:dirty_select( - muc_room, [{#muc_room{name_host = {'_', Host}, _ = '_'}, - [], - ['$_']}]) of - {'EXIT', Reason} -> - ?ERROR_MSG("~p", [Reason]), - []; - Rs -> - Rs + case catch mnesia:dirty_select(muc_room, + [{#muc_room{name_host = {'_', Host}, + _ = '_'}, + [], ['$_']}]) + of + {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), []; + Rs -> Rs end; get_rooms(LServer, Host, odbc) -> SHost = ejabberd_odbc:escape(Host), - case catch ejabberd_odbc:sql_query( - LServer, ["select name, opts from muc_room ", - "where host='", SHost, "';"]) of - {'EXIT', Reason} -> - ?ERROR_MSG("~p", [Reason]), - []; - {selected, ["name", "opts"], RoomOpts} -> - lists:map( - fun({Room, Opts}) -> - #muc_room{name_host = {Room, Host}, - opts = ejabberd_odbc:decode_term(Opts)} - end, RoomOpts) + case catch ejabberd_odbc:sql_query(LServer, + [<<"select name, opts from muc_room ">>, + <<"where host='">>, SHost, <<"';">>]) + of + {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), []; + {selected, [<<"name">>, <<"opts">>], RoomOpts} -> + lists:map(fun ([Room, Opts]) -> + #muc_room{name_host = {Room, Host}, + opts = opts_to_binary( + ejabberd_odbc:decode_term(Opts))} + end, + RoomOpts) end. load_permanent_rooms(Host, ServerHost, Access, HistorySize, RoomShaper) -> @@ -667,50 +661,77 @@ register_room(Host, Room, Pid) -> iq_disco_info(Lang) -> - [{xmlelement, "identity", - [{"category", "conference"}, - {"type", "text"}, - {"name", translate:translate(Lang, "Chatrooms")}], []}, - {xmlelement, "feature", [{"var", ?NS_DISCO_INFO}], []}, - {xmlelement, "feature", [{"var", ?NS_DISCO_ITEMS}], []}, - {xmlelement, "feature", [{"var", ?NS_MUC}], []}, - {xmlelement, "feature", [{"var", ?NS_MUC_UNIQUE}], []}, - {xmlelement, "feature", [{"var", ?NS_REGISTER}], []}, - {xmlelement, "feature", [{"var", ?NS_RSM}], []}, - {xmlelement, "feature", [{"var", ?NS_VCARD}], []}]. - + [#xmlel{name = <<"identity">>, + attrs = + [{<<"category">>, <<"conference">>}, + {<<"type">>, <<"text">>}, + {<<"name">>, + translate:translate(Lang, <<"Chatrooms">>)}], + children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_DISCO_INFO}], children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_DISCO_ITEMS}], children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_MUC}], children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_MUC_UNIQUE}], children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_REGISTER}], children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_RSM}], children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_VCARD}], children = []}]. iq_disco_items(Host, From, Lang, none) -> - lists:zf(fun(#muc_online_room{name_host = {Name, _Host}, pid = Pid}) -> - case catch gen_fsm:sync_send_all_state_event( - Pid, {get_disco_item, From, Lang}, 100) of - {item, Desc} -> - flush(), - {true, - {xmlelement, "item", - [{"jid", jlib:jid_to_string({Name, Host, ""})}, - {"name", Desc}], []}}; - _ -> - false + lists:zf(fun (#muc_online_room{name_host = + {Name, _Host}, + pid = Pid}) -> + case catch gen_fsm:sync_send_all_state_event(Pid, + {get_disco_item, + From, Lang}, + 100) + of + {item, Desc} -> + flush(), + {true, + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, + jlib:jid_to_string({Name, Host, + <<"">>})}, + {<<"name">>, Desc}], + children = []}}; + _ -> false end end, get_vh_rooms(Host)); iq_disco_items(Host, From, Lang, Rsm) -> {Rooms, RsmO} = get_vh_rooms(Host, Rsm), RsmOut = jlib:rsm_encode(RsmO), - lists:zf(fun(#muc_online_room{name_host = {Name, _Host}, pid = Pid}) -> - case catch gen_fsm:sync_send_all_state_event( - Pid, {get_disco_item, From, Lang}, 100) of - {item, Desc} -> - flush(), - {true, - {xmlelement, "item", - [{"jid", jlib:jid_to_string({Name, Host, ""})}, - {"name", Desc}], []}}; - _ -> - false + lists:zf(fun (#muc_online_room{name_host = + {Name, _Host}, + pid = Pid}) -> + case catch gen_fsm:sync_send_all_state_event(Pid, + {get_disco_item, + From, Lang}, + 100) + of + {item, Desc} -> + flush(), + {true, + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, + jlib:jid_to_string({Name, Host, + <<"">>})}, + {<<"name">>, Desc}], + children = []}}; + _ -> false end - end, Rooms) ++ RsmOut. + end, + Rooms) + ++ RsmOut. get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})-> AllRooms = lists:sort(get_vh_rooms(Host)), @@ -736,16 +757,16 @@ get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})-> true -> lists:sublist(L, Index+1, M) end, - if - L2 == [] -> - {L2, #rsm_out{count=Count}}; - true -> - H = hd(L2), - NewIndex = get_room_pos(H, AllRooms), - T=lists:last(L2), - {F, _}=H#muc_online_room.name_host, - {Last, _}=T#muc_online_room.name_host, - {L2, #rsm_out{first=F, last=Last, count=Count, index=NewIndex}} + if L2 == [] -> {L2, #rsm_out{count = Count}}; + true -> + H = hd(L2), + NewIndex = get_room_pos(H, AllRooms), + T = lists:last(L2), + {F, _} = H#muc_online_room.name_host, + {Last, _} = T#muc_online_room.name_host, + {L2, + #rsm_out{first = F, last = Last, count = Count, + index = NewIndex}} end. %% @doc Return the position of desired room in the list of rooms. @@ -753,27 +774,17 @@ get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})-> %% @spec (Desired::muc_online_room(), Rooms::[muc_online_room()]) -> integer() get_room_pos(Desired, Rooms) -> get_room_pos(Desired, Rooms, 0). + get_room_pos(Desired, [HeadRoom | _], HeadPosition) - when (Desired#muc_online_room.name_host == - HeadRoom#muc_online_room.name_host) -> + when Desired#muc_online_room.name_host == + HeadRoom#muc_online_room.name_host -> HeadPosition; get_room_pos(Desired, [_ | Rooms], HeadPosition) -> get_room_pos(Desired, Rooms, HeadPosition + 1). -flush() -> - receive - _ -> - flush() - after 0 -> - ok - end. +flush() -> receive _ -> flush() after 0 -> ok end. -define(XFIELD(Type, Label, Var, Val), - {xmlelement, "field", [{"type", Type}, - {"label", translate:translate(Lang, Label)}, - {"var", Var}], - [{xmlelement, "value", [], [{xmlcdata, Val}]}]}). - %% @doc Get a pseudo unique Room Name. The Room Name is generated as a hash of %% the requester JID, the local time and a random salt. %% @@ -781,195 +792,216 @@ flush() -> %% with the returned Name already created, nor mark the generated Name %% as "already used". But in practice, it is unique enough. See %% http://xmpp.org/extensions/xep-0045.html#createroom-unique + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, Type}, + {<<"label">>, translate:translate(Lang, Label)}, + {<<"var">>, Var}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Val}]}]}). + iq_get_unique(From) -> - {xmlcdata, sha:sha(term_to_binary([From, now(), randoms:get_string()]))}. + {xmlcdata, + sha:sha(term_to_binary([From, now(), + randoms:get_string()]))}. get_nick(ServerHost, Host, From) -> LServer = jlib:nameprep(ServerHost), - get_nick(LServer, Host, From, gen_mod:db_type(LServer, ?MODULE)). + get_nick(LServer, Host, From, + gen_mod:db_type(LServer, ?MODULE)). get_nick(_LServer, Host, From, mnesia) -> {LUser, LServer, _} = jlib:jid_tolower(From), LUS = {LUser, LServer}, - case catch mnesia:dirty_read(muc_registered, {LUS, Host}) of - {'EXIT', _Reason} -> - error; - [] -> - error; - [#muc_registered{nick = Nick}] -> - Nick + case catch mnesia:dirty_read(muc_registered, + {LUS, Host}) + of + {'EXIT', _Reason} -> error; + [] -> error; + [#muc_registered{nick = Nick}] -> Nick end; get_nick(LServer, Host, From, odbc) -> - SJID = ejabberd_odbc:escape( - jlib:jid_to_string( - jlib:jid_tolower( - jlib:jid_remove_resource(From)))), + SJID = + ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From)))), SHost = ejabberd_odbc:escape(Host), - case catch ejabberd_odbc:sql_query( - LServer, ["select nick from muc_registered where " - "jid='", SJID, "' and host='", SHost, "';"]) of - {selected, ["nick"], [{Nick}]} -> - Nick; - _ -> - error + case catch ejabberd_odbc:sql_query(LServer, + [<<"select nick from muc_registered where " + "jid='">>, + SJID, <<"' and host='">>, SHost, + <<"';">>]) + of + {selected, [<<"nick">>], [[Nick]]} -> Nick; + _ -> error end. iq_get_register_info(ServerHost, Host, From, Lang) -> - {Nick, Registered} = - case get_nick(ServerHost, Host, From) of - error -> - {"", []}; - N -> - {N, [{xmlelement, "registered", [], []}]} - end, + {Nick, Registered} = case get_nick(ServerHost, Host, + From) + of + error -> {<<"">>, []}; + N -> + {N, + [#xmlel{name = <<"registered">>, attrs = [], + children = []}]} + end, Registered ++ - [{xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, "You need a client that supports x:data to register the nickname")}]}, - {xmlelement, "x", - [{"xmlns", ?NS_XDATA}], - [{xmlelement, "title", [], - [{xmlcdata, - translate:translate( - Lang, "Nickname Registration at ") ++ Host}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, "Enter nickname you want to register")}]}, - ?XFIELD("text-single", "Nickname", "nick", Nick)]}]. + [#xmlel{name = <<"instructions">>, attrs = [], + children = + [{xmlcdata, + translate:translate(Lang, + <<"You need a client that supports x:data " + "to register the nickname">>)}]}, + #xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [#xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, + <<(translate:translate(Lang, + <<"Nickname Registration at ">>))/binary, + Host/binary>>}]}, + #xmlel{name = <<"instructions">>, attrs = [], + children = + [{xmlcdata, + translate:translate(Lang, + <<"Enter nickname you want to register">>)}]}, + ?XFIELD(<<"text-single">>, <<"Nickname">>, <<"nick">>, + Nick)]}]. set_nick(ServerHost, Host, From, Nick) -> LServer = jlib:nameprep(ServerHost), - set_nick(LServer, Host, From, Nick, gen_mod:db_type(LServer, ?MODULE)). + set_nick(LServer, Host, From, Nick, + gen_mod:db_type(LServer, ?MODULE)). set_nick(_LServer, Host, From, Nick, mnesia) -> {LUser, LServer, _} = jlib:jid_tolower(From), LUS = {LUser, LServer}, - F = fun() -> + F = fun () -> case Nick of - "" -> - mnesia:delete({muc_registered, {LUS, Host}}), - ok; - _ -> - Allow = - case mnesia:select( - muc_registered, - [{#muc_registered{us_host = '$1', - nick = Nick, - _ = '_'}, - [{'==', {element, 2, '$1'}, Host}], - ['$_']}]) of - [] -> - true; + <<"">> -> + mnesia:delete({muc_registered, {LUS, Host}}), ok; + _ -> + Allow = case mnesia:select(muc_registered, + [{#muc_registered{us_host = + '$1', + nick = Nick, + _ = '_'}, + [{'==', {element, 2, '$1'}, + Host}], + ['$_']}]) + of + [] -> true; [#muc_registered{us_host = {U, _Host}}] -> U == LUS - end, - if - Allow -> - mnesia:write( - #muc_registered{us_host = {LUS, Host}, - nick = Nick}), - ok; - true -> - false - end + end, + if Allow -> + mnesia:write(#muc_registered{us_host = {LUS, Host}, + nick = Nick}), + ok; + true -> false + end end end, mnesia:transaction(F); set_nick(LServer, Host, From, Nick, odbc) -> - JID = jlib:jid_to_string( - jlib:jid_tolower( - jlib:jid_remove_resource(From))), + JID = + jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From))), SJID = ejabberd_odbc:escape(JID), SNick = ejabberd_odbc:escape(Nick), SHost = ejabberd_odbc:escape(Host), - F = fun() -> - case Nick of - "" -> - ejabberd_odbc:sql_query_t( - ["delete from muc_registered where ", - "jid='", SJID, "' and host='", Host, "';"]), - ok; - _ -> - Allow = - case ejabberd_odbc:sql_query_t( - ["select jid from muc_registered ", - "where nick='", SNick, "' and host='", - SHost, "';"]) of - {selected, ["jid"], [{J}]} -> - J == JID; - _ -> - true - end, - if Allow -> - odbc_queries:update_t( - "muc_registered", - ["jid", "host", "nick"], - [SJID, SHost, SNick], - ["jid='", SJID, "' and host='", SHost, "'"]), - ok; - true -> - false - end - end - end, + F = fun () -> + case Nick of + <<"">> -> + ejabberd_odbc:sql_query_t([<<"delete from muc_registered where ">>, + <<"jid='">>, SJID, + <<"' and host='">>, Host, + <<"';">>]), + ok; + _ -> + Allow = case + ejabberd_odbc:sql_query_t([<<"select jid from muc_registered ">>, + <<"where nick='">>, + SNick, + <<"' and host='">>, + SHost, <<"';">>]) + of + {selected, [<<"jid">>], [[J]]} -> J == JID; + _ -> true + end, + if Allow -> + odbc_queries:update_t(<<"muc_registered">>, + [<<"jid">>, <<"host">>, + <<"nick">>], + [SJID, SHost, SNick], + [<<"jid='">>, SJID, + <<"' and host='">>, SHost, + <<"'">>]), + ok; + true -> false + end + end + end, ejabberd_odbc:sql_transaction(LServer, F). -iq_set_register_info(ServerHost, Host, From, Nick, Lang) -> +iq_set_register_info(ServerHost, Host, From, Nick, + Lang) -> case set_nick(ServerHost, Host, From, Nick) of - {atomic, ok} -> - {result, []}; - {atomic, false} -> - ErrText = "That nickname is registered by another person", - {error, ?ERRT_CONFLICT(Lang, ErrText)}; - _ -> - {error, ?ERR_INTERNAL_SERVER_ERROR} + {atomic, ok} -> {result, []}; + {atomic, false} -> + ErrText = <<"That nickname is registered by another " + "person">>, + {error, ?ERRT_CONFLICT(Lang, ErrText)}; + _ -> {error, ?ERR_INTERNAL_SERVER_ERROR} end. -process_iq_register_set(ServerHost, Host, From, SubEl, Lang) -> - {xmlelement, _Name, _Attrs, Els} = SubEl, - case xml:get_subtag(SubEl, "remove") of - false -> - case xml:remove_cdata(Els) of - [{xmlelement, "x", _Attrs1, _Els1} = XEl] -> - case {xml:get_tag_attr_s("xmlns", XEl), - xml:get_tag_attr_s("type", XEl)} of - {?NS_XDATA, "cancel"} -> - {result, []}; - {?NS_XDATA, "submit"} -> - XData = jlib:parse_xdata_submit(XEl), - case XData of - invalid -> - {error, ?ERR_BAD_REQUEST}; - _ -> - case lists:keysearch("nick", 1, XData) of - {value, {_, [Nick]}} when Nick /= "" -> - iq_set_register_info(ServerHost, Host, - From, Nick, Lang); - _ -> - ErrText = "You must fill in field \"Nickname\" in the form", - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)} - end - end; +process_iq_register_set(ServerHost, Host, From, SubEl, + Lang) -> + #xmlel{children = Els} = SubEl, + case xml:get_subtag(SubEl, <<"remove">>) of + false -> + case xml:remove_cdata(Els) of + [#xmlel{name = <<"x">>} = XEl] -> + case {xml:get_tag_attr_s(<<"xmlns">>, XEl), + xml:get_tag_attr_s(<<"type">>, XEl)} + of + {?NS_XDATA, <<"cancel">>} -> {result, []}; + {?NS_XDATA, <<"submit">>} -> + XData = jlib:parse_xdata_submit(XEl), + case XData of + invalid -> {error, ?ERR_BAD_REQUEST}; _ -> - {error, ?ERR_BAD_REQUEST} - end; - _ -> - {error, ?ERR_BAD_REQUEST} - end; - _ -> - iq_set_register_info(ServerHost, Host, From, "", Lang) + case lists:keysearch(<<"nick">>, 1, XData) of + {value, {_, [Nick]}} when Nick /= <<"">> -> + iq_set_register_info(ServerHost, Host, From, + Nick, Lang); + _ -> + ErrText = + <<"You must fill in field \"Nickname\" " + "in the form">>, + {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)} + end + end; + _ -> {error, ?ERR_BAD_REQUEST} + end; + _ -> {error, ?ERR_BAD_REQUEST} + end; + _ -> + iq_set_register_info(ServerHost, Host, From, <<"">>, + Lang) end. iq_get_vcard(Lang) -> - [{xmlelement, "FN", [], - [{xmlcdata, "ejabberd/mod_muc"}]}, - {xmlelement, "URL", [], - [{xmlcdata, ?EJABBERD_URI}]}, - {xmlelement, "DESC", [], - [{xmlcdata, translate:translate(Lang, "ejabberd MUC module") ++ - "\nCopyright (c) 2003-2013 ProcessOne"}]}]. + [#xmlel{name = <<"FN">>, attrs = [], + children = [{xmlcdata, <<"ejabberd/mod_muc">>}]}, + #xmlel{name = <<"URL">>, attrs = [], + children = [{xmlcdata, ?EJABBERD_URI}]}, + #xmlel{name = <<"DESC">>, attrs = [], + children = + [{xmlcdata, + <<(translate:translate(Lang, + <<"ejabberd MUC module">>))/binary, + "\nCopyright (c) 2003-2013 ProcessOne">>}]}]. broadcast_service_message(Host, Msg) -> @@ -979,6 +1011,7 @@ broadcast_service_message(Host, Msg) -> Pid, {service_message, Msg}) end, get_vh_rooms(Host)). + get_vh_rooms(Host) -> mnesia:dirty_select(muc_online_room, [{#muc_online_room{name_host = '$1', _ = '_'}, @@ -1014,6 +1047,44 @@ clean_table_from_bad_node(Node, Host) -> end, mnesia:async_dirty(F). +opts_to_binary(Opts) -> + lists:map( + fun({title, Title}) -> + {title, iolist_to_binary(Title)}; + ({description, Desc}) -> + {description, iolist_to_binary(Desc)}; + ({password, Pass}) -> + {password, iolist_to_binary(Pass)}; + ({subject, Subj}) -> + {subject, iolist_to_binary(Subj)}; + ({subject_author, Author}) -> + {subject_author, iolist_to_binary(Author)}; + ({affiliations, Affs}) -> + {affiliations, lists:map( + fun({{U, S, R}, Aff}) -> + NewAff = + case Aff of + {A, Reason} -> + {A, iolist_to_binary(Reason)}; + _ -> + Aff + end, + {{iolist_to_binary(U), + iolist_to_binary(S), + iolist_to_binary(R)}, + NewAff} + end, Affs)}; + ({captcha_whitelist, CWList}) -> + {captcha_whitelist, lists:map( + fun({U, S, R}) -> + {iolist_to_binary(U), + iolist_to_binary(S), + iolist_to_binary(R)} + end, CWList)}; + (Opt) -> + Opt + end, Opts). + update_tables(Host) -> update_muc_room_table(Host), update_muc_registered_table(Host). @@ -1021,83 +1092,36 @@ update_tables(Host) -> update_muc_room_table(Host) -> Fields = record_info(fields, muc_room), case mnesia:table_info(muc_room, attributes) of - Fields -> - ok; - [name, opts] -> - ?INFO_MSG("Converting muc_room table from " - "{name, opts} format", []), - {atomic, ok} = mnesia:create_table( - mod_muc_tmp_table, - [{disc_only_copies, [node()]}, - {type, bag}, - {local_content, true}, - {record_name, muc_room}, - {attributes, record_info(fields, muc_room)}]), - mnesia:transform_table(muc_room, ignore, Fields), - F1 = fun() -> - mnesia:write_lock_table(mod_muc_tmp_table), - mnesia:foldl( - fun(#muc_room{name_host = Name} = R, _) -> - mnesia:dirty_write( - mod_muc_tmp_table, - R#muc_room{name_host = {Name, Host}}) - end, ok, muc_room) - end, - mnesia:transaction(F1), - mnesia:clear_table(muc_room), - F2 = fun() -> - mnesia:write_lock_table(muc_room), - mnesia:foldl( - fun(R, _) -> - mnesia:dirty_write(R) - end, ok, mod_muc_tmp_table) - end, - mnesia:transaction(F2), - mnesia:delete_table(mod_muc_tmp_table); - _ -> - ?INFO_MSG("Recreating muc_room table", []), - mnesia:transform_table(muc_room, ignore, Fields) + Fields -> + ejabberd_config:convert_table_to_binary( + muc_room, Fields, set, + fun(#muc_room{name_host = {N, _}}) -> N end, + fun(#muc_room{name_host = {N, H}, + opts = Opts} = R) -> + R#muc_room{name_host = {iolist_to_binary(N), + iolist_to_binary(H)}, + opts = opts_to_binary(Opts)} + end); + _ -> + ?INFO_MSG("Recreating muc_room table", []), + mnesia:transform_table(muc_room, ignore, Fields) end. - update_muc_registered_table(Host) -> Fields = record_info(fields, muc_registered), case mnesia:table_info(muc_registered, attributes) of - Fields -> - ok; - [user, nick] -> - ?INFO_MSG("Converting muc_registered table from " - "{user, nick} format", []), - {atomic, ok} = mnesia:create_table( - mod_muc_tmp_table, - [{disc_only_copies, [node()]}, - {type, bag}, - {local_content, true}, - {record_name, muc_registered}, - {attributes, record_info(fields, muc_registered)}]), - mnesia:del_table_index(muc_registered, nick), - mnesia:transform_table(muc_registered, ignore, Fields), - F1 = fun() -> - mnesia:write_lock_table(mod_muc_tmp_table), - mnesia:foldl( - fun(#muc_registered{us_host = US} = R, _) -> - mnesia:dirty_write( - mod_muc_tmp_table, - R#muc_registered{us_host = {US, Host}}) - end, ok, muc_registered) - end, - mnesia:transaction(F1), - mnesia:clear_table(muc_registered), - F2 = fun() -> - mnesia:write_lock_table(muc_registered), - mnesia:foldl( - fun(R, _) -> - mnesia:dirty_write(R) - end, ok, mod_muc_tmp_table) - end, - mnesia:transaction(F2), - mnesia:delete_table(mod_muc_tmp_table); - _ -> - ?INFO_MSG("Recreating muc_registered table", []), - mnesia:transform_table(muc_registered, ignore, Fields) + Fields -> + ejabberd_config:convert_table_to_binary( + muc_registered, Fields, set, + fun(#muc_registered{us_host = {_, H}}) -> H end, + fun(#muc_registered{us_host = {{U, S}, H}, + nick = Nick} = R) -> + R#muc_registered{us_host = {{iolist_to_binary(U), + iolist_to_binary(S)}, + iolist_to_binary(H)}, + nick = iolist_to_binary(Nick)} + end); + _ -> + ?INFO_MSG("Recreating muc_registered table", []), + mnesia:transform_table(muc_registered, ignore, Fields) end. diff --git a/src/mod_muc/mod_muc_log.erl b/src/mod_muc/mod_muc_log.erl index 71dc8a0b2..ee9eb7a9d 100644 --- a/src/mod_muc/mod_muc_log.erl +++ b/src/mod_muc/mod_muc_log.erl @@ -25,35 +25,37 @@ %%%---------------------------------------------------------------------- -module(mod_muc_log). + -author('badlop@process-one.net'). -behaviour(gen_server). + -behaviour(gen_mod). %% API --export([start_link/2, - start/2, - stop/1, - check_access_log/2, - add_to_log/5]). +-export([start_link/2, start/2, stop/1, + check_access_log/2, add_to_log/5]). %% 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("mod_muc_room.hrl"). %% Copied from mod_muc/mod_muc.erl --record(muc_online_room, {name_host, pid}). +-record(muc_online_room, {name_host = {<<>>, <<>>} :: {binary(), binary()}, + pid = self() :: pid()}). -define(T(Text), translate:translate(Lang, Text)). -define(PROCNAME, ejabberd_mod_muc_log). -record(room, {jid, title, subject, subject_author, config}). --define(PLAINTEXT_IN, "ZZIZZ"). --define(PLAINTEXT_OUT, "ZZOZZ"). +-define(PLAINTEXT_IN, <<"ZZIZZ">>). +-define(PLAINTEXT_OUT, <<"ZZOZZ">>). -record(logstate, {host, out_dir, @@ -101,11 +103,10 @@ add_to_log(Host, Type, Data, Room, Opts) -> check_access_log(Host, From) -> case catch gen_server:call(get_proc_name(Host), - {check_access_log, Host, From}) of - {'EXIT', _Error} -> - deny; - Res -> - Res + {check_access_log, Host, From}) + of + {'EXIT', _Error} -> deny; + Res -> Res end. %%==================================================================== @@ -120,36 +121,52 @@ check_access_log(Host, From) -> %% Description: Initiates the server %%-------------------------------------------------------------------- init([Host, Opts]) -> - OutDir = gen_mod:get_opt(outdir, Opts, "www/muc"), - DirType = gen_mod:get_opt(dirtype, Opts, subdirs), - DirName = gen_mod:get_opt(dirname, Opts, room_jid), - FileFormat = gen_mod:get_opt(file_format, Opts, html), % Allowed values: html|plaintext - FilePermissions = gen_mod:get_opt(file_permissions, Opts, {644, 33}), - CSSFile = gen_mod:get_opt(cssfile, Opts, false), - AccessLog = gen_mod:get_opt(access_log, Opts, muc_admin), - Timezone = gen_mod:get_opt(timezone, Opts, local), - Top_link = gen_mod:get_opt(top_link, Opts, {"/", "Home"}), - NoFollow = gen_mod:get_opt(spam_prevention, Opts, true), - Lang = case ejabberd_config:get_local_option({language, Host}) of - undefined -> - case ejabberd_config:get_global_option(language) of - undefined -> "en"; - L -> L - end; - L -> L - end, - {ok, #logstate{host = Host, - out_dir = OutDir, - dir_type = DirType, - dir_name = DirName, - file_format = FileFormat, - file_permissions = FilePermissions, - css_file = CSSFile, - access = AccessLog, - lang = Lang, - timezone = Timezone, - spam_prevention = NoFollow, - top_link = Top_link}}. + OutDir = gen_mod:get_opt(outdir, Opts, + fun iolist_to_binary/1, + <<"www/muc">>), + DirType = gen_mod:get_opt(dirtype, Opts, + fun(subdirs) -> subdirs; + (plain) -> plain + end, subdirs), + DirName = gen_mod:get_opt(dirname, Opts, + fun(room_jid) -> room_jid; + (room_name) -> room_name + end, room_jid), + FileFormat = gen_mod:get_opt(file_format, Opts, + fun(html) -> html; + (plaintext) -> plaintext + end, html), + FilePermissions = gen_mod:get_opt(file_permissions, Opts, + fun({A, B}) -> {A, B} + end, {644, 33}), + CSSFile = gen_mod:get_opt(cssfile, Opts, + fun iolist_to_binary/1, + false), + AccessLog = gen_mod:get_opt(access_log, Opts, + fun(A) when is_atom(A) -> A end, + muc_admin), + Timezone = gen_mod:get_opt(timezone, Opts, + fun(local) -> local; + (universal) -> universal + end, local), + Top_link = gen_mod:get_opt(top_link, Opts, + fun({S1, S2}) -> + {iolist_to_binary(S1), + iolist_to_binary(S2)} + end, {<<"/">>, <<"Home">>}), + NoFollow = gen_mod:get_opt(spam_prevention, Opts, + fun(B) when is_boolean(B) -> B end, + true), + Lang = ejabberd_config:get_local_option( + {language, Host}, + fun iolist_to_binary/1, + ?MYLANG), + {ok, + #logstate{host = Host, out_dir = OutDir, + dir_type = DirType, dir_name = DirName, + file_format = FileFormat, file_permissions = FilePermissions, css_file = CSSFile, + access = AccessLog, lang = Lang, timezone = Timezone, + spam_prevention = NoFollow, top_link = Top_link}}. %%-------------------------------------------------------------------- %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | @@ -174,14 +191,11 @@ handle_call(stop, _From, State) -> %%-------------------------------------------------------------------- handle_cast({add_to_log, Type, Data, Room, Opts}, State) -> case catch add_to_log2(Type, Data, Room, Opts, State) of - {'EXIT', Reason} -> - ?ERROR_MSG("~p", [Reason]); - _ -> - ok + {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); + _ -> ok end, {noreply, State}; -handle_cast(_Msg, State) -> - {noreply, State}. +handle_cast(_Msg, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: handle_info(Info, State) -> {noreply, State} | @@ -189,8 +203,7 @@ handle_cast(_Msg, State) -> %% {stop, Reason, State} %% Description: Handling all non call/cast messages %%-------------------------------------------------------------------- -handle_info(_Info, State) -> - {noreply, State}. +handle_info(_Info, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: terminate(Reason, State) -> void() @@ -199,763 +212,982 @@ 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 %%-------------------------------------------------------------------- add_to_log2(text, {Nick, Packet}, Room, Opts, State) -> - case {xml:get_subtag(Packet, "subject"), xml:get_subtag(Packet, "body")} of - {false, false} -> - ok; - {false, SubEl} -> - Message = {body, xml:get_tag_cdata(SubEl)}, - add_message_to_log(Nick, Message, Room, Opts, State); - {SubEl, _} -> - Message = {subject, xml:get_tag_cdata(SubEl)}, - add_message_to_log(Nick, Message, Room, Opts, State) + case {xml:get_subtag(Packet, <<"subject">>), + xml:get_subtag(Packet, <<"body">>)} + of + {false, false} -> ok; + {false, SubEl} -> + Message = {body, xml:get_tag_cdata(SubEl)}, + add_message_to_log(Nick, Message, Room, Opts, State); + {SubEl, _} -> + Message = {subject, xml:get_tag_cdata(SubEl)}, + add_message_to_log(Nick, Message, Room, Opts, State) end; - -add_to_log2(roomconfig_change, _Occupants, Room, Opts, State) -> - add_message_to_log("", roomconfig_change, Room, Opts, State); - -add_to_log2(roomconfig_change_enabledlogging, Occupants, Room, Opts, State) -> - add_message_to_log("", {roomconfig_change, Occupants}, Room, Opts, State); - -add_to_log2(room_existence, NewStatus, Room, Opts, State) -> - add_message_to_log("", {room_existence, NewStatus}, Room, Opts, State); - -add_to_log2(nickchange, {OldNick, NewNick}, Room, Opts, State) -> - add_message_to_log(NewNick, {nickchange, OldNick}, Room, Opts, State); - +add_to_log2(roomconfig_change, _Occupants, Room, Opts, + State) -> + add_message_to_log(<<"">>, roomconfig_change, Room, + Opts, State); +add_to_log2(roomconfig_change_enabledlogging, Occupants, + Room, Opts, State) -> + add_message_to_log(<<"">>, + {roomconfig_change, Occupants}, Room, Opts, State); +add_to_log2(room_existence, NewStatus, Room, Opts, + State) -> + add_message_to_log(<<"">>, {room_existence, NewStatus}, + Room, Opts, State); +add_to_log2(nickchange, {OldNick, NewNick}, Room, Opts, + State) -> + add_message_to_log(NewNick, {nickchange, OldNick}, Room, + Opts, State); add_to_log2(join, Nick, Room, Opts, State) -> add_message_to_log(Nick, join, Room, Opts, State); - add_to_log2(leave, {Nick, Reason}, Room, Opts, State) -> case Reason of - "" -> add_message_to_log(Nick, leave, Room, Opts, State); - _ -> add_message_to_log(Nick, {leave, Reason}, Room, Opts, State) + <<"">> -> + add_message_to_log(Nick, leave, Room, Opts, State); + _ -> + add_message_to_log(Nick, {leave, Reason}, Room, Opts, + State) end; - -add_to_log2(kickban, {Nick, Reason, Code}, Room, Opts, State) -> - add_message_to_log(Nick, {kickban, Code, Reason}, Room, Opts, State). - +add_to_log2(kickban, {Nick, Reason, Code}, Room, Opts, + State) -> + add_message_to_log(Nick, {kickban, Code, Reason}, Room, + Opts, State). %%---------------------------------------------------------------------- %% Core -build_filename_string(TimeStamp, OutDir, RoomJID, DirType, DirName, FileFormat) -> +build_filename_string(TimeStamp, OutDir, RoomJID, + DirType, DirName, FileFormat) -> {{Year, Month, Day}, _Time} = TimeStamp, - - %% Directory and file names - {Dir, Filename, Rel} = - case DirType of - subdirs -> - SYear = lists:flatten(io_lib:format("~4..0w", [Year])), - SMonth = lists:flatten(io_lib:format("~2..0w", [Month])), - SDay = lists:flatten(io_lib:format("~2..0w", [Day])), - {filename:join(SYear, SMonth), SDay, "../.."}; - plain -> - Date = lists:flatten( - io_lib:format("~4..0w-~2..0w-~2..0w", - [Year, Month, Day])), - {"", Date, "."} - end, - + {Dir, Filename, Rel} = case DirType of + subdirs -> + SYear = + iolist_to_binary(io_lib:format("~4..0w", + [Year])), + SMonth = + iolist_to_binary(io_lib:format("~2..0w", + [Month])), + SDay = iolist_to_binary(io_lib:format("~2..0w", + [Day])), + {fjoin([SYear, SMonth]), SDay, + <<"../..">>}; + plain -> + Date = + iolist_to_binary(io_lib:format("~4..0w-~2..0w-~2..0w", + [Year, + Month, + Day])), + {<<"">>, Date, <<".">>} + end, RoomString = case DirName of - room_jid -> RoomJID; - room_name -> get_room_name(RoomJID) + room_jid -> RoomJID; + room_name -> get_room_name(RoomJID) end, Extension = case FileFormat of - html -> ".html"; - plaintext -> ".txt" + html -> <<".html">>; + plaintext -> <<".txt">> end, - Fd = filename:join([OutDir, RoomString, Dir]), - Fn = filename:join([Fd, Filename ++ Extension]), - Fnrel = filename:join([Rel, Dir, Filename ++ Extension]), + Fd = fjoin([OutDir, RoomString, Dir]), + Fn = fjoin([Fd, <>]), + Fnrel = fjoin([Rel, Dir, <>]), {Fd, Fn, Fnrel}. get_room_name(RoomJID) -> - JID = jlib:string_to_jid(RoomJID), - JID#jid.user. + JID = jlib:string_to_jid(RoomJID), JID#jid.user. %% calculate day before get_timestamp_daydiff(TimeStamp, Daydiff) -> {Date1, HMS} = TimeStamp, - Date2 = calendar:gregorian_days_to_date( - calendar:date_to_gregorian_days(Date1) + Daydiff), + Date2 = + calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(Date1) + + Daydiff), {Date2, HMS}. %% Try to close the previous day log, if it exists close_previous_log(Fn, Images_dir, FileFormat) -> case file:read_file_info(Fn) of - {ok, _} -> - {ok, F} = file:open(Fn, [append]), - write_last_lines(F, Images_dir, FileFormat), - file:close(F); - _ -> ok + {ok, _} -> + {ok, F} = file:open(Fn, [append]), + write_last_lines(F, Images_dir, FileFormat), + file:close(F); + _ -> ok end. -write_last_lines(_, _, plaintext) -> - ok; +write_last_lines(_, _, plaintext) -> ok; write_last_lines(F, Images_dir, _FileFormat) -> - %%fw(F, "ejabberd/mod_muc log"), - fw(F, ""), - fw(F, " ", [Images_dir]), - fw(F, " ", [Images_dir]), - fw(F, ""), - fw(F, " ", [Images_dir]), - fw(F, " ", [Images_dir]), - fw(F, "