From cdb379a22cf8cad90e90b7746e5e05e00e0958f3 Mon Sep 17 00:00:00 2001 From: Badlop Date: Thu, 22 Jul 2010 18:41:53 +0200 Subject: [PATCH] Patch core for MH (thanks to Alexey Shchepin and Geoff Cant) --- src/acl.erl | 20 ++- src/ejabberd.erl | 37 ++++- src/ejabberd.hrl | 6 +- src/ejabberd_admin.erl | 4 +- src/ejabberd_app.erl | 9 ++ src/ejabberd_auth.erl | 45 ++++-- src/ejabberd_auth_ldap.erl | 116 ++++++++++----- src/ejabberd_auth_odbc.erl | 11 +- src/ejabberd_c2s.erl | 2 +- src/ejabberd_check.erl | 2 + src/ejabberd_config.erl | 115 ++++++++++++++- src/ejabberd_config.hrl | 5 - src/ejabberd_hooks.erl | 54 +++++-- src/ejabberd_local.erl | 27 ++-- src/ejabberd_rdbms.erl | 59 +++++++- src/ejabberd_router.erl | 190 ++++++++++++++----------- src/ejabberd_sm.erl | 30 ++-- src/ejabberd_sup.erl | 18 ++- src/ejabberdctl.template | 22 +-- src/ejd2odbc.erl | 2 +- src/gen_mod.erl | 17 +++ src/odbc/ejabberd_odbc.erl | 16 ++- src/odbc/ejabberd_odbc_sup.erl | 11 +- src/odbc/mysql.sql | 69 +++++---- src/odbc/odbc_queries.erl | 249 ++++++++++++++++++--------------- src/odbc/pg.sql | 81 +++++++---- src/randoms.erl | 7 +- src/web/ejabberd_web_admin.erl | 2 +- 28 files changed, 858 insertions(+), 368 deletions(-) diff --git a/src/acl.erl b/src/acl.erl index fae1ef5ef..cae7f9bac 100644 --- a/src/acl.erl +++ b/src/acl.erl @@ -32,10 +32,12 @@ add/3, add_list/3, match_rule/3, + for_host/1, % for debugging only match_acl/3]). -include("ejabberd.hrl"). +-include_lib("stdlib/include/ms_transform.hrl"). %% @type aclspec() = all | JID_Exact | JID_Regexp | JID_Glob | Shared_Group %% JID_Exact = {user, U} | {user, U, S} | {server, S} | {resource, R} @@ -234,7 +236,7 @@ match_acl(ACLName, JID, Host) -> User = exmpp_jid:prep_node_as_list(JID), Server = exmpp_jid:prep_domain_as_list(JID), Resource = exmpp_jid:prep_resource_as_list(JID), - lists:any(fun(#acl{aclspec = Spec}) -> + lists:any(fun(#acl{aclname=Name, aclspec = Spec}) -> case Spec of all -> true; @@ -243,17 +245,21 @@ match_acl(ACLName, JID, Host) -> andalso ((Host == Server) orelse ((Host == global) andalso - lists:member(Server, ?MYHOSTS))); + ?IS_MY_HOST(Server))); {user, U, S} -> (U == User) andalso (S == Server); {server, S} -> S == Server; {resource, R} -> R == Resource; + {user_regexp, UR} when is_tuple(Name), + element(2, Name) =:= global -> + ?IS_MY_HOST(Server) + andalso is_regexp_match(User, UR); {user_regexp, UR} -> ((Host == Server) orelse ((Host == global) andalso - lists:member(Server, ?MYHOSTS))) + ?IS_MY_HOST(Server))) andalso is_regexp_match(User, UR); {shared_group, G} -> mod_shared_roster:is_user_in_group({User, Server}, G, Host); @@ -272,7 +278,7 @@ match_acl(ACLName, JID, Host) -> {user_glob, UR} -> ((Host == Server) orelse ((Host == global) andalso - lists:member(Server, ?MYHOSTS))) + ?IS_MY_HOST(Server))) andalso is_glob_match(User, UR); {user_glob, UR, S} -> @@ -325,3 +331,9 @@ is_glob_match(String, Glob) -> is_regexp_match(String, xmerl_regexp:sh_to_awk(Glob)). +for_host(Host) -> + mnesia:select(acl, + ets:fun2ms(fun (#acl{aclname = {_ACLName, H}}) + when H =:= Host -> + object() + end)). diff --git a/src/ejabberd.erl b/src/ejabberd.erl index c92509e7a..5dc5c990d 100644 --- a/src/ejabberd.erl +++ b/src/ejabberd.erl @@ -28,8 +28,14 @@ -author('alexey@process-one.net'). -export([start/0, stop/0, - get_pid_file/0, - get_so_path/0, get_bin_path/0]). + get_so_path/0, + get_bin_path/0, + get_pid_file/0, + is_my_host/1, + normalize_host/1 + ]). + +-include("ejabberd.hrl"). start() -> %%ejabberd_cover:start(), @@ -65,6 +71,33 @@ get_bin_path() -> Path end. +is_my_host([$* | _]) -> + false; +is_my_host(Host) -> + case ejabberd_config:get_local_option({Host, host}) of + undefined -> + WCHost = re:replace(Host, "^[^.]*\.", "*.", [{return, list}]), + case ejabberd_config:get_local_option({WCHost, host}) of + undefined -> + false; + _ -> + true + end; + _ -> + true + end. + +normalize_host(global) -> + global; +normalize_host(Host) -> + WCHost = re:replace(Host, "^[^.]*\.", "*.", [{return, list}]), + case ejabberd_config:get_local_option({WCHost, host}) of + undefined -> + Host; + _ -> + WCHost + end. + %% @spec () -> false | string() get_pid_file() -> case os:getenv("EJABBERD_PID_PATH") of diff --git a/src/ejabberd.hrl b/src/ejabberd.hrl index e1f0cfd37..4317c33a1 100644 --- a/src/ejabberd.hrl +++ b/src/ejabberd.hrl @@ -23,7 +23,8 @@ %% If the ejabberd application description isn't loaded, returns atom: undefined -define(VERSION, element(2, application:get_key(ejabberd,vsn))). --define(MYHOSTS, ejabberd_config:get_global_option(hosts)). +-define(IS_MY_HOST(Host), ejabberd:is_my_host(Host)). +-define(MYHOSTS, ejabberd_config:get_global_option(hosts)). %% Deprecated -define(MYNAME, hd(ejabberd_config:get_global_option(hosts))). -define(MYLANG, ejabberd_config:get_global_option(language)). @@ -35,6 +36,8 @@ -define(S2STIMEOUT, 600000). +-define(PRIVACY_SUPPORT, true). + %%-define(DBGFSM, true). %% --------------------------------- @@ -58,4 +61,3 @@ -define(CRITICAL_MSG(Format, Args), ejabberd_logger:critical_msg(?MODULE,?LINE,Format, Args)). - diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index a38cee740..96176d8da 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -278,8 +278,8 @@ send_service_message_all_mucs(Subject, AnnouncementText) -> Message = io_lib:format("~s~n~s", [Subject, AnnouncementText]), lists:foreach( fun(ServerHost) -> - MUCHost = gen_mod:get_module_opt_host( - ServerHost, mod_muc, "conference.@HOST@"), + MUCHost = gen_mod:expand_host_name( + ServerHost, mod_muc, "conference"), MUCHostB = list_to_binary(MUCHost), mod_muc:broadcast_service_message(MUCHostB, Message) end, diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl index 1b0289752..f6379e75f 100644 --- a/src/ejabberd_app.erl +++ b/src/ejabberd_app.erl @@ -119,6 +119,15 @@ db_init() -> %% Start all the modules in all the hosts start_modules() -> + case ejabberd_config:get_local_option({static_modules, global}) of + undefined -> + ok; + StaticModules -> + lists:foreach( + fun({Module, Args}) -> + gen_mod:start_module(global, Module, Args) + end, StaticModules) + end, lists:foreach( fun(Host) -> case ejabberd_config:get_local_option({modules, Host}) of diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index 00fb0338d..c926aaeee 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -52,6 +52,15 @@ plain_password_required/1 ]). +-export([start/1 + ,start_module/2 + ,stop_module/2 + ,start_modules/2 + ,start_method/2 + ,stop_method/2 + ,start_methods/2 + ]). + -export([auth_modules/1]). -include("ejabberd.hrl"). @@ -67,13 +76,26 @@ %% @spec () -> term() start() -> - lists:foreach( - fun(Host) -> - lists:foreach( - fun(M) -> - M:start(Host) - end, auth_modules(Host)) - end, ?MYHOSTS). + ?DEBUG("About to start auth modules. Hosts: ~p", ?MYHOSTS), + lists:foreach(fun start/1, ?MYHOSTS). + +start(Host) -> + start_modules(Host, auth_modules(Host)). + +start_modules(Host, Modules) when is_list(Modules) -> + lists:foreach(fun (M) -> start_module(Host, M) end, Modules). +start_module(Host, Module) when is_atom(Module) -> + Module:start(Host). +stop_module(Host, Module) when is_atom(Module) -> + Module:stop(Host). + +start_methods(Host, Methods) when is_list(Methods) -> + lists:foreach(fun (M) -> start_method(Host, M) end, Methods). +start_method(Host, Method) when is_atom(Method) -> + start_module(Host, module_name(Method)). +stop_method(Host, Method) when is_atom(Method) -> + stop_module(Host, module_name(Method)). + %% @spec (Server) -> bool() %% Server = string() @@ -186,7 +208,7 @@ try_register(User, Server, Password) true -> {atomic, exists}; false -> - case lists:member(exmpp_stringprep:nameprep(Server), ?MYHOSTS) of + case ?IS_MY_HOST(exmpp_stringprep:nameprep(Server)) of true -> Res = lists:foldl( fun (_M, {atomic, ok} = Res) -> @@ -443,10 +465,13 @@ auth_modules() -> auth_modules(Server) when is_list(Server) -> LServer = exmpp_stringprep:nameprep(Server), - Method = ejabberd_config:get_local_option({auth_method, LServer}), + Method = ejabberd_config:get_local_option({auth_method, ejabberd:normalize_host(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]. + [module_name(M) || M <- Methods]. + +module_name(Method) when is_atom(Method) -> + list_to_atom("ejabberd_auth_" ++ atom_to_list(Method)). diff --git a/src/ejabberd_auth_ldap.erl b/src/ejabberd_auth_ldap.erl index 6fb6d2faf..54419374e 100644 --- a/src/ejabberd_auth_ldap.erl +++ b/src/ejabberd_auth_ldap.erl @@ -72,7 +72,6 @@ base, uids, ufilter, - sfilter, lfilter, %% Local filter (performed by ejabberd, not LDAP) dn_filter, dn_filter_attrs @@ -99,21 +98,41 @@ handle_info(_Info, State) -> %% Host = string() start(Host) -> - Proc = gen_mod:get_module_proc(Host, ?MODULE), - ChildSpec = { - Proc, {?MODULE, start_link, [Host]}, - transient, 1000, worker, [?MODULE] - }, - supervisor:start_child(ejabberd_sup, ChildSpec). + ?DEBUG("Starting ~p for ~p.", [?MODULE, Host]), + case ejabberd_config:get_host_option(Host, ldap_servers) of + undefined -> check_bad_config(Host); + {host, _Host} -> ok; + _ -> + Proc = gen_mod:get_module_proc(Host, ?MODULE), + ChildSpec = { + Proc, {?MODULE, start_link, [Host]}, + transient, 1000, worker, [?MODULE] + }, + supervisor:start_child(ejabberd_sup, ChildSpec) + end. + +check_bad_config(Host) -> + case ejabberd_config:get_local_option({ldap_servers, Host}) of + undefined -> + ?ERROR_MSG("Can't start ~p for host ~p: missing ldap_servers configuration", + [?MODULE, Host]), + {error, bad_config}; + _ -> ok + end. %% @spec (Host) -> term() %% Host = string() stop(Host) -> - Proc = gen_mod:get_module_proc(Host, ?MODULE), - gen_server:call(Proc, stop), - supervisor:terminate_child(ejabberd_sup, Proc), - supervisor:delete_child(ejabberd_sup, Proc). + case ejabberd_config:get_host_option(Host, ldap_servers) of + undefined -> ok; + {host, _Host} -> ok; + _ -> + Proc = gen_mod:get_module_proc(Host, ?MODULE), + gen_server:call(Proc, stop), + supervisor:terminate_child(ejabberd_sup, Proc), + supervisor:delete_child(ejabberd_sup, Proc) + end. %% @spec (Host) -> term() %% Host = string() @@ -190,7 +209,7 @@ check_password(User, Server, Password, _Digest, _DigestGen) -> set_password(User, Server, Password) -> {ok, State} = eldap_utils:get_state(Server, ?MODULE), - case find_user_dn(User, State) of + case find_user_dn(User, Server, State) of false -> {error, user_not_found}; DN -> @@ -285,8 +304,8 @@ remove_user(_User, _Server, _Password) -> %% Password = string() check_password_ldap(User, Server, Password) -> - {ok, State} = eldap_utils:get_state(Server, ?MODULE), - case find_user_dn(User, State) of + {ok, State} = get_state(Server), + case find_user_dn(User, Server, State) of false -> false; DN -> @@ -294,7 +313,21 @@ check_password_ldap(User, Server, Password) -> ok -> true; _ -> false end - end. + end. + +%% We need an ?MODULE server state to use for queries. This will +%% either be Server if this is a statically configured host or the +%% Server for a different host if this is a dynamically configured +%% vhost. +%% The {ldap_vhost, Server} -> Host. ejabberd config option specifies +%% which actual ?MODULE server to use for a particular Host. The value +%% of the option if it is defined or Server by default. +get_state(Server) -> + Host = case ejabberd_config:get_local_option({ldap_servers, Server}) of + {host, H} -> H; + _ -> Server + end, + eldap_utils:get_state(Host, ?MODULE). %% @spec (Server) -> [{LUser, LServer}] %% Server = string() @@ -303,11 +336,11 @@ check_password_ldap(User, Server, Password) -> get_vh_registered_users_ldap(Server) -> {ok, State} = eldap_utils:get_state(Server, ?MODULE), - UIDs = State#state.uids, + UIDs = eldap_utils:uids_domain_subst(Server, State#state.uids), Eldap_ID = State#state.eldap_id, - Server = State#state.host, + SearchFilter = build_sfilter(State, UIDs), ResAttrs = result_attrs(State), - case eldap_filter:parse(State#state.sfilter) of + case eldap_filter:parse(SearchFilter) of {ok, EldapFilter} -> case eldap_pool:search(Eldap_ID, [{base, State#state.base}, {filter, EldapFilter}, @@ -317,7 +350,7 @@ get_vh_registered_users_ldap(Server) -> lists:flatmap( fun(#eldap_entry{attributes = Attrs, object_name = DN}) -> - case is_valid_dn(DN, Attrs, State) of + case is_valid_dn(DN, Server, Attrs, State) of false -> []; _ -> case eldap_utils:find_ldap_attrs(UIDs, Attrs) of @@ -349,7 +382,7 @@ get_vh_registered_users_ldap(Server) -> is_user_exists_ldap(User, Server) -> {ok, State} = eldap_utils:get_state(Server, ?MODULE), - case find_user_dn(User, State) of + case find_user_dn(User, Server, State) of false -> false; _DN -> true end. @@ -363,16 +396,17 @@ handle_call(stop, _From, State) -> handle_call(_Request, _From, State) -> {reply, bad_request, State}. -find_user_dn(User, State) -> +find_user_dn(User, Server, State) -> ResAttrs = result_attrs(State), - case eldap_filter:parse(State#state.ufilter, [{"%u", User}]) of + UserFilter = build_ufilter(State, Server), + case eldap_filter:parse(UserFilter, [{"%u", User}]) of {ok, Filter} -> case eldap_pool:search(State#state.eldap_id, [{base, State#state.base}, {filter, Filter}, {attributes, ResAttrs}]) of #eldap_search_result{entries = [#eldap_entry{attributes = Attrs, object_name = DN} | _]} -> - dn_filter(DN, Attrs, State); + dn_filter(DN, Server, Attrs, State); _ -> false end; @@ -381,20 +415,20 @@ find_user_dn(User, State) -> end. %% apply the dn filter and the local filter: -dn_filter(DN, Attrs, State) -> +dn_filter(DN, Server, 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) + true -> is_valid_dn(DN, Server, Attrs, State) end. %% Check that the DN is valid, based on the dn filter -is_valid_dn(DN, _, #state{dn_filter = undefined}) -> +is_valid_dn(DN, _, _, #state{dn_filter = undefined}) -> DN; -is_valid_dn(DN, Attrs, State) -> +is_valid_dn(DN, Server, Attrs, State) -> DNAttrs = State#state.dn_filter_attrs, - UIDs = State#state.uids, + UIDs = eldap_utils:uids_domain_subst(Server, 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; @@ -449,6 +483,22 @@ result_attrs(#state{uids = UIDs, dn_filter_attrs = DNFilterAttrs}) -> [UID | Acc] end, DNFilterAttrs, UIDs). +build_ufilter(State, VHost) -> + UIDs = eldap_utils:uids_domain_subst(VHost, State#state.uids), + SubFilter = lists:flatten(eldap_utils:generate_subfilter(UIDs)), + case State#state.ufilter of + "" -> SubFilter; + F -> "(&" ++ SubFilter ++ F ++ ")" + end. + +build_sfilter(State, FormattedUIDs) -> + SubFilter = lists:flatten(eldap_utils:generate_subfilter(FormattedUIDs)), + UserFilter = case State#state.ufilter of + "" -> SubFilter; + F -> "(&" ++ SubFilter ++ F ++ ")" + end, + eldap_filter:do_sub(UserFilter, [{"%u", "*"}]). + %%%---------------------------------------------------------------------- %%% Auxiliary functions %%%---------------------------------------------------------------------- @@ -480,15 +530,12 @@ parse_options(Host) -> end, UIDs = case ejabberd_config:get_local_option({ldap_uids, Host}) of undefined -> [{"uid", "%u"}]; - UI -> eldap_utils:uids_domain_subst(Host, UI) + UI -> 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 -> "(&" ++ SubFilter ++ F ++ ")" + undefined -> ""; + F -> F end, - SearchFilter = eldap_filter:do_sub(UserFilter, [{"%u", "*"}]), LDAPBase = ejabberd_config:get_local_option({ldap_base, Host}), {DNFilter, DNFilterAttrs} = case ejabberd_config:get_local_option({ldap_dn_filter, Host}) of @@ -513,7 +560,6 @@ parse_options(Host) -> base = LDAPBase, uids = UIDs, ufilter = UserFilter, - sfilter = SearchFilter, lfilter = LocalFilter, dn_filter = DNFilter, dn_filter_attrs = DNFilterAttrs diff --git a/src/ejabberd_auth_odbc.erl b/src/ejabberd_auth_odbc.erl index 1389e1951..8227b241d 100644 --- a/src/ejabberd_auth_odbc.erl +++ b/src/ejabberd_auth_odbc.erl @@ -29,6 +29,7 @@ %% External exports -export([start/1, + stop/1, set_password/3, check_password/3, check_password/5, @@ -55,8 +56,14 @@ %% @spec (Host) -> ok %% Host = string() -start(_Host) -> - ok. +start(Host) -> + case ejabberd_odbc:running(Host) of + true -> ok; + false -> ejabberd_rdbms:start_odbc(Host) + end. + +stop(Host) -> + ejabberd_rdbms:stop_odbc(Host). %% @spec () -> bool() diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 3f7dde71d..010fb177f 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -315,7 +315,7 @@ wait_for_stream({xmlstreamstart, #xmlel{ns = NS} = Opening}, StateData) -> ServerB = exmpp_stringprep:nameprep( exmpp_stream:get_receiving_entity(Opening)), Server = binary_to_list(ServerB), - case lists:member(Server, ?MYHOSTS) of + case ?IS_MY_HOST(Server) of true -> Lang = exmpp_stream:get_lang(Opening), change_shaper(StateData, diff --git a/src/ejabberd_check.erl b/src/ejabberd_check.erl index 403cdd2a9..8b315861a 100644 --- a/src/ejabberd_check.erl +++ b/src/ejabberd_check.erl @@ -46,6 +46,8 @@ config() -> check_database_modules() -> [check_database_module(M)||M<-get_db_used()]. +check_database_module(host) -> + ok; check_database_module(odbc) -> check_modules(odbc, [odbc, odbc_app, odbc_sup, ejabberd_odbc, ejabberd_odbc_sup, odbc_queries]); check_database_module(mysql) -> diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index 2233f7123..e9c167a87 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -27,17 +27,32 @@ -module(ejabberd_config). -author('alexey@process-one.net'). --export([start/0, load_file/1, +-export([start/0, load_file/1, get_host_option/2, add_global_option/2, add_local_option/2, + mne_add_local_option/2, mne_del_local_option/1, del_global_option/1, del_local_option/1, get_global_option/1, get_local_option/1]). + +-export([for_host/1 + ,configure_host/2 + ,delete_host/1 + ]). + +-export([search/1]). + -export([get_vh_by_auth_method/1]). -export([is_file_readable/1]). -include("ejabberd.hrl"). -include("ejabberd_config.hrl"). -include_lib("kernel/include/file.hrl"). +-include_lib("stdlib/include/ms_transform.hrl"). +-record(state, {opts = [], + hosts = [], + override_local = false, + override_global = false, + override_acls = false}). %% @type macro() = {macro_key(), macro_value()} @@ -153,6 +168,14 @@ search_hosts(Term, State) -> add_hosts_to_option(Hosts, State) -> PrepHosts = normalize_hosts(Hosts), + mnesia:transaction( + fun() -> + lists:foreach( + fun(H) -> + mnesia:write(#local_config{key = {H, host}, + value = []}) + end, PrepHosts) + end), add_option(hosts, PrepHosts, State#state{hosts = PrepHosts}). normalize_hosts(Hosts) -> @@ -379,6 +402,8 @@ process_term(Term, State) -> {host_config, Host, Terms} -> lists:foldl(fun(T, S) -> process_host_term(T, Host, S) end, State, Terms); + {clusterid, ClusterID} -> + add_option(clusterid, ClusterID, State); {listen, Listeners} -> Listeners2 = lists:map( @@ -443,8 +468,7 @@ process_term(Term, State) -> {max_fsm_queue, N} -> add_option(max_fsm_queue, N, State); {_Opt, _Val} -> - lists:foldl(fun(Host, S) -> process_host_term(Term, Host, S) end, - State, State#state.hosts) + process_host_term(Term, global, State) end. process_host_term(Term, Host, State) -> @@ -569,10 +593,11 @@ add_global_option(Opt, Val) -> end). add_local_option(Opt, Val) -> - mnesia:transaction(fun() -> - mnesia:write(#local_config{key = Opt, - value = Val}) - end). + mnesia:transaction(fun mne_add_local_option/2, [Opt, Val]). + +mne_add_local_option(Opt, Val) -> + mnesia:write(#local_config{key = Opt, + value = Val}). del_global_option(Opt) -> mnesia:transaction(fun() -> @@ -585,6 +610,13 @@ del_local_option(Opt) -> end). +get_global_option({Opt1, Host} = Opt) when is_list(Host) -> + case ets:lookup(config, Opt) of + [#config{value = Val}] -> + Val; + _ -> + get_global_option({Opt1, global}) + end; get_global_option(Opt) -> case ets:lookup(config, Opt) of [#config{value = Val}] -> @@ -593,6 +625,13 @@ get_global_option(Opt) -> undefined end. +get_local_option({Opt1, Host} = Opt) when is_list(Host) -> + case ets:lookup(local_config, Opt) of + [#local_config{value = Val}] -> + Val; + _ -> + get_local_option({Opt1, global}) + end; get_local_option(Opt) -> case ets:lookup(local_config, Opt) of [#local_config{value = Val}] -> @@ -601,6 +640,17 @@ get_local_option(Opt) -> undefined end. +get_host_option(Host, Option) -> + case ets:lookup(local_config, {Option, Host}) of + [#local_config{value=V}] -> V; + _ -> undefined + end. + +mne_del_local_option({_OptName, Host} = Opt) when is_list(Host) -> + mnesia:delete({local_config, Opt}); +mne_del_local_option({Host, host} = Opt) when is_list(Host) -> + mnesia:delete({local_config, Opt}). + %% Return the list of hosts handled by a given module get_vh_by_auth_method(AuthMethod) -> mnesia:dirty_select(local_config, @@ -619,3 +669,54 @@ is_file_readable(Path) -> {error, _Reason} -> false end. + +search(Pattern) -> + {atomic, Res} = mnesia:transaction(fun mnesia:select/2, [local_config, Pattern]), + Res. + +for_host(Host) -> + mnesia:read({local_config, {Host, host}}) + ++ mnesia:select(local_config, + ets:fun2ms(fun (#local_config{key={_, H}}) + when H =:= Host -> + object() + end)) + ++ acl:for_host(Host). + +delete_host(Host) -> + mnesia_delete_objects(for_host(Host)), + ok. + +configure_host(Host, Config) -> + HostExistenceTerm = {{Host, host}, []}, + Records = host_terms_to_records(Host, [HostExistenceTerm | Config]), + mnesia_write_objects(Records), + ok. + +host_terms_to_records(Host, Terms) -> + lists:foldl(fun (Term, Acc) -> + host_term_to_record(Term, Host, Acc) + end, [], Terms). + +host_term_to_record({acl, ACLName, ACLData}, Host, Acc) -> + [acl:to_record(Host, ACLName, ACLData) | Acc]; +host_term_to_record({access, RuleName, Rules}, Host, Acc) -> + [#config{key={access, RuleName, Host}, value=Rules} | Acc]; +host_term_to_record({shaper, Name, Data}, Host, Acc) -> + [#config{key={shaper, Name, Host}, value=Data} | Acc]; +host_term_to_record({host, _}, _Host, Acc) -> Acc; +host_term_to_record({hosts, _}, _Host, Acc) -> Acc; +host_term_to_record({{Host, host}, []}, Host, Acc) -> + [#local_config{key={Host, host}, value=[]} | Acc]; +host_term_to_record({Opt, Val}, Host, Acc) when is_atom(Opt) -> + [#local_config{key={Opt, Host}, value=Val} | Acc]. + + +mnesia_delete_objects(List) when is_list(List) -> + true = lists:all(fun (I) -> + ok =:= mnesia:delete_object(I) + end, List). +mnesia_write_objects(List) when is_list(List) -> + true = lists:all(fun (I) -> + ok =:= mnesia:write(I) + end, List). diff --git a/src/ejabberd_config.hrl b/src/ejabberd_config.hrl index f847f35b7..c9c5335f7 100644 --- a/src/ejabberd_config.hrl +++ b/src/ejabberd_config.hrl @@ -21,8 +21,3 @@ -record(config, {key, value}). -record(local_config, {key, value}). --record(state, {opts = [], - hosts = [], - override_local = false, - override_global = false, - override_acls = false}). diff --git a/src/ejabberd_hooks.erl b/src/ejabberd_hooks.erl index e88df4548..58933e964 100644 --- a/src/ejabberd_hooks.erl +++ b/src/ejabberd_hooks.erl @@ -119,14 +119,27 @@ delete_dist(Hook, Host, Node, Module, Function, Seq) -> %% @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). + runx(Hook, global, Args). -run(Hook, Host, Args) when is_binary(Host) orelse is_atom(Host) -> - case ets:lookup(hooks, {Hook, Host}) of +run(Hook, Host, Args) when is_binary(Host) -> + case runx(Hook, Host, Args) of + stop -> stop; + _ -> runx(Hook, global, Args) + end; +run(Hook, Host, Args) when Host == global -> + runx(Hook, Host, Args). + +runx(Hook, Host, Args) when is_binary(Host) orelse is_atom(Host) -> + case ets:lookup(hooks, {Hook, ejabberd:normalize_host(Host)}) of [{_, Ls}] -> run1(Ls, Hook, Args); [] -> - ok + case ets:lookup(hooks, {Hook, global}) of + [{_, Ls}] -> + run1(Ls, Hook, Args); + [] -> + ok + end end. %% @spec (Hook::atom(), Val, Args) -> Val | stopped | NewVal @@ -136,16 +149,29 @@ run(Hook, Host, Args) when is_binary(Host) orelse is_atom(Host) -> %% If a call returns 'stop', no more calls are performed and 'stopped' is returned. %% If a call returns {stopped, NewVal}, no more calls are performed and NewVal is returned. run_fold(Hook, Val, Args) -> - run_fold(Hook, global, Val, Args). + run_foldx(Hook, global, Val, Args). %% @spec (Hook::atom(), Host, Val, Args) -> Val | stopped | NewVal %% Host = global | binary() -run_fold(Hook, Host, Val, Args) when is_binary(Host) orelse is_atom(Host) -> - case ets:lookup(hooks, {Hook, Host}) of +run_fold(Hook, Host, Val, Args) when is_binary(Host) -> + case run_foldx(Hook, Host, Val, Args) of + stopped -> stopped; + Val2 -> run_foldx(Hook, global, Val2, Args) + end; +run_fold(Hook, Host, Val, Args) when Host == global -> + run_foldx(Hook, Host, Val, Args). + +run_foldx(Hook, Host, Val, Args) -> + case ets:lookup(hooks, {Hook, ejabberd:normalize_host(Host)}) of [{_, Ls}] -> run_fold1(Ls, Hook, Val, Args); [] -> - Val + case ets:lookup(hooks, {Hook, global}) of + [{_, Ls}] -> + run_fold1(Ls, Hook, Val, Args); + [] -> + Val + end end. %%%---------------------------------------------------------------------- @@ -173,7 +199,8 @@ init([]) -> %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- handle_call({add, Hook, Host, Module, Function, Seq}, _From, State) -> - Reply = case ets:lookup(hooks, {Hook, Host}) of + NHost = ejabberd:normalize_host(Host), + Reply = case ets:lookup(hooks, {Hook, NHost}) of [{_, Ls}] -> El = {Seq, Module, Function}, case lists:member(El, Ls) of @@ -181,12 +208,12 @@ handle_call({add, Hook, Host, Module, Function, Seq}, _From, State) -> ok; false -> NewLs = lists:merge(Ls, [El]), - ets:insert(hooks, {{Hook, Host}, NewLs}), + ets:insert(hooks, {{Hook, NHost}, NewLs}), ok end; [] -> NewLs = [{Seq, Module, Function}], - ets:insert(hooks, {{Hook, Host}, NewLs}), + ets:insert(hooks, {{Hook, NHost}, NewLs}), ok end, {reply, Reply, State}; @@ -209,10 +236,11 @@ handle_call({add, Hook, Host, Node, Module, Function, Seq}, _From, State) -> end, {reply, Reply, State}; handle_call({delete, Hook, Host, Module, Function, Seq}, _From, State) -> - Reply = case ets:lookup(hooks, {Hook, Host}) of + NHost = ejabberd:normalize_host(Host), + Reply = case ets:lookup(hooks, {Hook, NHost}) of [{_, Ls}] -> NewLs = lists:delete({Seq, Module, Function}, Ls), - ets:insert(hooks, {{Hook, Host}, NewLs}), + ets:insert(hooks, {{Hook, NHost}, NewLs}), ok; [] -> ok diff --git a/src/ejabberd_local.erl b/src/ejabberd_local.erl index 65086d972..ebcad372d 100644 --- a/src/ejabberd_local.erl +++ b/src/ejabberd_local.erl @@ -82,7 +82,7 @@ process_iq(From, To, Packet) -> case exmpp_iq:xmlel_to_iq(Packet) of #iq{kind = request, ns = XMLNS} = IQ_Rec -> Host = exmpp_jid:prep_domain(To), - case ets:lookup(?IQTABLE, {XMLNS, Host}) of + case ets:lookup(?IQTABLE, {XMLNS, ejabberd:normalize_host(Host)}) of [{_, Module, Function}] -> ResIQ = Module:Function(From, To, IQ_Rec), if @@ -96,8 +96,15 @@ process_iq(From, To, Packet) -> gen_iq_handler:handle(Host, Module, Function, Opts, From, To, IQ_Rec); [] -> - Err = exmpp_iq:error(Packet, 'feature-not-implemented'), - ejabberd_router:route(To, From, Err) + case ets:lookup(?IQTABLE, {XMLNS, global}) of + [{_, Module, Function, Opts}] -> + gen_iq_handler:handle( + global, Module, Function, Opts, + From, To, IQ_Rec); + [] -> + Err = exmpp_iq:error(Packet, 'feature-not-implemented'), + ejabberd_router:route(To, From, Err) + end end; #iq{kind = response} = IQReply -> %%IQReply = jlib:iq_query_or_response_info(IQ_Rec), @@ -193,10 +200,10 @@ bounce_resource_packet(From, To, Packet) -> init([]) -> lists:foreach( fun(Host) -> - ejabberd_router:register_route(Host, {apply, ?MODULE, route}), - ejabberd_hooks:add(local_send_to_resource_hook, list_to_binary(Host), - ?MODULE, bounce_resource_packet, 100) + ejabberd_router:register_route(Host, {apply, ?MODULE, route}) end, ?MYHOSTS), + ejabberd_hooks:add(local_send_to_resource_hook, global, + ?MODULE, bounce_resource_packet, 100), catch ets:new(?IQTABLE, [named_table, public]), mnesia:delete_table(iq_response), catch ets:new(iq_response, [named_table, public, @@ -253,21 +260,21 @@ handle_info({route, From, To, Packet}, State) -> end, {noreply, State}; handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) -> - ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function}), + ets:insert(?IQTABLE, {{XMLNS, ejabberd:normalize_host(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}), + ets:insert(?IQTABLE, {{XMLNS, ejabberd:normalize_host(Host)}, Module, Function, Opts}), catch mod_disco:register_feature(Host, XMLNS), {noreply, State}; handle_info({unregister_iq_handler, Host, XMLNS}, State) -> - case ets:lookup(?IQTABLE, {XMLNS, Host}) of + case ets:lookup(?IQTABLE, {XMLNS, ejabberd:normalize_host(Host)}) of [{_, Module, Function, Opts}] -> gen_iq_handler:stop_iq_handler(Module, Function, Opts); _ -> ok end, - ets:delete(?IQTABLE, {XMLNS, Host}), + ets:delete(?IQTABLE, {XMLNS, ejabberd:normalize_host(Host)}), catch mod_disco:unregister_feature(Host, XMLNS), {noreply, State}; handle_info(refresh_iq_handlers, State) -> diff --git a/src/ejabberd_rdbms.erl b/src/ejabberd_rdbms.erl index 3b5d748b4..2cb67b3a2 100644 --- a/src/ejabberd_rdbms.erl +++ b/src/ejabberd_rdbms.erl @@ -27,8 +27,15 @@ -module(ejabberd_rdbms). -author('alexey@process-one.net'). --export([start/0]). +-export([start/0 + ,start_odbc/1 + ,stop_odbc/1 + ,running/1 + ]). -include("ejabberd.hrl"). +-include("ejabberd_config.hrl"). +-include_lib("stdlib/include/ms_transform.hrl"). +-define(SUPERVISOR, ejabberd_sup). start() -> %% Check if ejabberd has been compiled with ODBC @@ -52,22 +59,39 @@ start_hosts() -> %% Start the ODBC module on the given host start_odbc(Host) -> - Supervisor_name = gen_mod:get_module_proc(Host, ejabberd_odbc_sup), + SupervisorName = sup_name(Host), ChildSpec = - {Supervisor_name, + {SupervisorName, {ejabberd_odbc_sup, start_link, [Host]}, transient, infinity, supervisor, [ejabberd_odbc_sup]}, - case supervisor:start_child(ejabberd_sup, ChildSpec) of + case supervisor:start_child(?SUPERVISOR, ChildSpec) of {ok, _PID} -> ok; _Error -> - ?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying...~n", [Supervisor_name, _Error]), + ?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying...~n", [SupervisorName, _Error]), start_odbc(Host) end. +stop_odbc(Host) -> + SupervisorName = sup_name(Host), + case running(Host) of + false -> ok; + true -> + case [H || H <- dependent_hosts(Host), ejabberd_hosts:running(H)] of + [] -> + ?INFO_MSG("About to terminate ~p", [SupervisorName]), + ok = supervisor:terminate_child(?SUPERVISOR, SupervisorName), + ok = supervisor:delete_child(?SUPERVISOR, SupervisorName); + RunningHosts -> + ?WARNING_MSG("Not stopping ODBC for ~p because the virtual hosts ~p are still using it.", + [Host, RunningHosts]), + {error, still_in_use} + end + end. + %% Returns true if we have configured odbc_server for the given host needs_odbc(Host) -> try @@ -75,9 +99,32 @@ needs_odbc(Host) -> case ejabberd_config:get_local_option({odbc_server, LHost}) of undefined -> false; - _ -> true + {host, _} -> + false; + _ -> + true end catch _ -> false end. + +running(Host) -> + Supervisors = supervisor:which_children(?SUPERVISOR), + SupervisorName = gen_mod:get_module_proc(Host, ejabberd_odbc_sup), + case lists:keysearch(SupervisorName, 1, Supervisors) of + false -> false; + {value, Cspec} when is_tuple(Cspec) -> true + end. + + +dependent_hosts(Host) -> + MS = ets:fun2ms(fun (#local_config{key={odbc_server, DHost}, + value={host, H}}) + when H =:= Host -> + DHost + end), + ejabberd_config:search(MS). + +sup_name(Host) -> + gen_mod:get_module_proc(Host, ejabberd_odbc_sup). diff --git a/src/ejabberd_router.erl b/src/ejabberd_router.erl index d6927f398..3bf0439ba 100644 --- a/src/ejabberd_router.erl +++ b/src/ejabberd_router.erl @@ -36,9 +36,11 @@ register_route/2, register_routes/1, unregister_route/1, + force_unregister_route/1, unregister_routes/1, dirty_get_all_routes/0, dirty_get_all_domains/0, + read_route/1, make_id/0 ]). @@ -98,56 +100,24 @@ route_error(From, To, ErrPacket, OrigPacket) -> ok end. -register_route(Domain) -> +register_route({global, Prefix}) -> + ejabberd_global_router:register_route(Prefix); +register_route(Domain) when is_list(Domain) -> register_route(Domain, undefined). register_route(Domain, LocalHint) -> try - LDomain = exmpp_stringprep:nameprep(Domain), - LDomainB = list_to_binary(LDomain), - Pid = self(), - case get_component_number(LDomain) of - undefined -> - F = fun() -> - mnesia:write(#route{domain = LDomainB, - pid = Pid, - local_hint = LocalHint}) - end, - mnesia:transaction(F); - N -> - F = fun() -> - case mnesia:wread({route, LDomainB}) of - [] -> - mnesia:write( - #route{domain = LDomainB, - pid = Pid, - local_hint = 1}), - lists:foreach( - fun(I) -> - mnesia:write( - #route{domain = LDomainB, - 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 = LDomainB, - pid = Pid, - local_hint = I}), - mnesia:delete_object(R), - true; - (_) -> - false - end, Rs) - end - end, - mnesia:transaction(F) - end + LDomain = exmpp_stringprep:nameprep(Domain), + LDomainB = list_to_binary(LDomain), + Pid = self(), + case get_component_number(LDomain) of + undefined -> + mnesia:transaction(fun register_simple_route/3, [LDomainB, Pid, LocalHint]); + N -> + mnesia:transaction(fun register_balanced_route/3, [LDomainB, Pid, N]) + end catch - _ -> + _ -> erlang:error({invalid_domain, Domain}) end. @@ -156,53 +126,112 @@ register_routes(Domains) -> register_route(Domain) end, Domains). -unregister_route(Domain) -> +register_simple_route(LDomain, Pid, LocalHint) -> + mnesia:write(#route{domain = LDomain, + pid = Pid, + local_hint = LocalHint}). + +register_balanced_route(LDomain, Pid, N) -> + case mnesia:read({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. + +unregister_route({global, Prefix}) -> + ejabberd_global_router:unregister_route(Prefix); +unregister_route(Domain) when is_list(Domain) -> try LDomain = exmpp_stringprep:nameprep(Domain), LDomainB = list_to_binary(LDomain), Pid = self(), case get_component_number(LDomain) of undefined -> - F = fun() -> - case mnesia:match_object( - #route{domain = LDomainB, - pid = Pid, - _ = '_'}) of - [R] -> - mnesia:delete_object(R); - _ -> - ok - end - end, - mnesia:transaction(F); + mnesia:transaction(fun delete_simple_route/2, [LDomainB, Pid]); _ -> - F = fun() -> - case mnesia:match_object(#route{domain=LDomainB, - pid = Pid, - _ = '_'}) of - [R] -> - I = R#route.local_hint, - mnesia:write( - #route{domain = LDomainB, - pid = undefined, - local_hint = I}), - mnesia:delete_object(R); - _ -> - ok - end - end, - mnesia:transaction(F) + mnesia:transaction(fun delete_balanced_route/2, [LDomainB, Pid]) end catch _ -> erlang:error({invalid_domain, Domain}) end. +delete_simple_route(LDomain, Pid) -> + case mnesia:match_object(#route{domain = LDomain, + pid = Pid, + _ = '_'}) of + [R] -> + mnesia:delete_object(R); + _ -> + ok + end. + +delete_balanced_route(LDomain, Pid) -> + case mnesia:match_object(#route{domain=LDomain, + pid = Pid, + _ = '_'}) of + [R] -> + I = R#route.local_hint, + ok = mnesia:write( + #route{domain = LDomain, + pid = undefined, + local_hint = I}), + mnesia:delete_object(R); + _ -> + ok + end. + + +force_unregister_route(Domain) -> + case jlib:nameprep(Domain) of + error -> + erlang:error({invalid_domain, Domain}); + LDomain -> + F = fun() -> + case mnesia:match_object( + #route{domain = LDomain, + _ = '_'}) of + Rs when is_list(Rs) -> + lists:foreach(fun(R) -> + mnesia:delete_object(R) + end, Rs); + _ -> + ok + end + end, + mnesia:transaction(F) + end. + unregister_routes(Domains) -> lists:foreach(fun(Domain) -> unregister_route(Domain) end, Domains). +read_route(Domain) -> + [{D,P,H} + || #route{domain=D, pid=P, local_hint=H} <- mnesia:dirty_read({route, Domain})]. dirty_get_all_routes() -> lists:usort( @@ -376,10 +405,16 @@ do_route(OrigFrom, OrigTo, OrigPacket) -> case ejabberd_hooks:run_fold(filter_packet, {OrigFrom, OrigTo, OrigPacket}, []) of {From, To, Packet} -> - LDomain = exmpp_jid:prep_domain(To), - case mnesia:dirty_read(route, LDomain) of + LDstDomain = exmpp_jid:prep_domain_as_list(To), + Destination = ejabberd:normalize_host(LDstDomain), + case mnesia:dirty_read(route, list_to_binary(Destination)) of [] -> - ejabberd_s2s:route(From, To, Packet); + case ejabberd_global_router:find_route(Destination) of + no_route -> + ejabberd_s2s:route(From, To, Packet); + Route -> + ejabberd_global_router:route(Route, From, To, Packet) + end; [R] -> Pid = R#route.pid, if @@ -396,7 +431,6 @@ do_route(OrigFrom, OrigTo, OrigPacket) -> drop end; Rs -> - LDstDomain = exmpp_jid:prep_domain_as_list(To), Value = case ejabberd_config:get_local_option( {domain_balancing, LDstDomain}) of undefined -> now(); diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index ea722ae17..f6d09276f 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -353,6 +353,12 @@ init([]) -> mnesia:add_table_index(session, us), mnesia:add_table_copy(session, node(), ram_copies), ets:new(sm_iqtable, [named_table]), + ejabberd_hooks:add(roster_in_subscription, global, + ejabberd_sm, check_in_subscription, 20), + ejabberd_hooks:add(offline_message_hook, global, + ejabberd_sm, bounce_offline_message, 100), + ejabberd_hooks:add(remove_user, global, + ejabberd_sm, disconnect_removed_user, 100), ejabberd_hooks:add(node_hash_update, ?MODULE, migrate, 100), lists:foreach( fun(Host) -> @@ -417,19 +423,19 @@ handle_info({route, From, To, Packet}, State) -> end, {noreply, State}; handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) -> - ets:insert(sm_iqtable, {{XMLNS, Host}, Module, Function}), + ets:insert(sm_iqtable, {{XMLNS, ejabberd:normalize_host(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}), + ets:insert(sm_iqtable, {{XMLNS, ejabberd:normalize_host(Host)}, Module, Function, Opts}), {noreply, State}; handle_info({unregister_iq_handler, Host, XMLNS}, State) -> - case ets:lookup(sm_iqtable, {XMLNS, Host}) of + case ets:lookup(sm_iqtable, {XMLNS, ejabberd:normalize_host(Host)}) of [{_, Module, Function, Opts}] -> gen_iq_handler:stop_iq_handler(Module, Function, Opts); _ -> ok end, - ets:delete(sm_iqtable, {XMLNS, Host}), + ets:delete(sm_iqtable, {XMLNS, ejabberd:normalize_host(Host)}), {noreply, State}; handle_info(_Info, State) -> {noreply, State}. @@ -763,7 +769,7 @@ process_iq(From, To, Packet) -> case exmpp_iq:xmlel_to_iq(Packet) of #iq{kind = request, ns = XMLNS} = IQ_Rec -> LServer = exmpp_jid:prep_domain(To), - case ets:lookup(sm_iqtable, {XMLNS, LServer}) of + case ets:lookup(sm_iqtable, {XMLNS, ejabberd:normalize_host(LServer)}) of [{_, Module, Function}] -> ResIQ = Module:Function(From, To, IQ_Rec), if @@ -774,11 +780,17 @@ process_iq(From, To, Packet) -> ok end; [{_, Module, Function, Opts}] -> - gen_iq_handler:handle(LServer, - Module, Function, Opts, From, To, IQ_Rec); + gen_iq_handler:handle(LServer, + Module, Function, Opts, From, To, IQ_Rec); [] -> - Err = exmpp_iq:error(Packet, 'service-unavailable'), - ejabberd_router:route(To, From, Err) + case ets:lookup(sm_iqtable, {XMLNS, global}) of + [{_, Module, Function, Opts}] -> + gen_iq_handler:handle(global, Module, Function, Opts, + From, To, IQ_Rec); + [] -> + Err = exmpp_iq:error(Packet, 'service-unavailable'), + ejabberd_router:route(To, From, Err) + end end; #iq{kind = response} -> ok; diff --git a/src/ejabberd_sup.erl b/src/ejabberd_sup.erl index 1fc193e5d..e088c032b 100644 --- a/src/ejabberd_sup.erl +++ b/src/ejabberd_sup.erl @@ -42,13 +42,13 @@ init([]) -> brutal_kill, worker, [ejabberd_hooks]}, - SystemMonitor = - {ejabberd_system_monitor, - {ejabberd_system_monitor, start_link, []}, + GlobalRouter = + {ejabberd_global_router, + {ejabberd_global_router, start_link, []}, permanent, brutal_kill, worker, - [ejabberd_system_monitor]}, + [ejabberd_global_router]}, Router = {ejabberd_router, {ejabberd_router, start_link, []}, @@ -137,6 +137,13 @@ init([]) -> infinity, supervisor, [ejabberd_tmp_sup]}, + Hosts = + {ejabberd_hosts, + {ejabberd_hosts, start_link, []}, + permanent, + brutal_kill, + worker, + [ejabberd_hosts]}, HTTPSupervisor = {ejabberd_http_sup, {ejabberd_tmp_sup, start_link, @@ -186,8 +193,8 @@ init([]) -> [ejabberd_cluster]}, {ok, {{one_for_one, 10, 1}, [Hooks, + GlobalRouter, Cluster, - SystemMonitor, Router, Router_multicast, SM, @@ -199,6 +206,7 @@ init([]) -> S2SInSupervisor, S2SOutSupervisor, ServiceSupervisor, + Hosts, HTTPSupervisor, HTTPPollSupervisor, IQSupervisor, diff --git a/src/ejabberdctl.template b/src/ejabberdctl.template index 06681f16a..5c73bc8ce 100644 --- a/src/ejabberdctl.template +++ b/src/ejabberdctl.template @@ -57,8 +57,9 @@ if [ "$ERLANG_NODE_ARG" != "" ] ; then fi # check the proper system user is used -ID=`id -g` -GIDS=`id -G` +if [ `uname -s` = "SunOS" ]; then IDCMD=/usr/xpg4/bin/id; else IDCMD=id; fi +ID=`$IDCMD -g` +EJID=`$IDCMD -g $INSTALLUSER` EJID=`id -g $INSTALLUSER` EXEC_CMD="false" for GID in $GIDS; do @@ -77,10 +78,13 @@ fi NAME=-name [ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] && NAME=-sname -if [ "$FIREWALL_WINDOW" = "" ] ; then - KERNEL_OPTS="" -else - KERNEL_OPTS="-kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}" +KERNEL_OPTS="" +if [ "$FIREWALL_WINDOW" != "" ] ; then + KERNEL_OPTS="${KERNEL_OPTS} -kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}" +fi + +if [ "$DIST_INTERFACE" != "" ] ; then + KERNEL_OPTS="${KERNEL_OPTS} -kernel inet_dist_use_interface \"${DIST_INTERFACE}\"" fi ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS" @@ -146,7 +150,7 @@ start () $KERNEL_OPTS \ -s ejabberd \ -sasl sasl_error_logger \\{file,\\\"$SASL_LOG_PATH\\\"\\} \ - $ERLANG_OPTS $ARGS \"$@\" $ERL_ARGS" + $ERLANG_OPTS -- $ARGS \"$@\" $ERL_ARGS" } # attach to server @@ -177,7 +181,7 @@ debug () $NAME debug-${TTY}-${ERLANG_NODE} \ -remsh $ERLANG_NODE \ $KERNEL_OPTS \ - $ERLANG_OPTS $ARGS \"$@\" $ERL_ARGS" + $ERLANG_OPTS -- $ARGS \"$@\" $ERL_ARGS" } # start interactive server @@ -209,7 +213,7 @@ live () -mnesia dir \"\\\"$SPOOLDIR\\\"\" \ $KERNEL_OPTS \ -s ejabberd \ - $ERLANG_OPTS $ARGS \"$@\" $ERL_ARGS" + $ERLANG_OPTS -- $ARGS \"$@\" $ERL_ARGS" } etop() diff --git a/src/ejd2odbc.erl b/src/ejd2odbc.erl index ea68e509d..a1af8f3e6 100644 --- a/src/ejd2odbc.erl +++ b/src/ejd2odbc.erl @@ -261,7 +261,7 @@ export_private_storage(ServerS, Output) -> LXMLNS = ejabberd_odbc:escape(atom_to_list(XMLNS)), SData = ejabberd_odbc:escape( exmpp_xml:document_to_list(Data)), - odbc_queries:set_private_data_sql(Username, LXMLNS, SData); + odbc_queries:set_private_data_sql(LServer, Username, LXMLNS, SData); (_Host, _R) -> [] end). diff --git a/src/gen_mod.erl b/src/gen_mod.erl index a1b3d140b..6e7a8fed0 100644 --- a/src/gen_mod.erl +++ b/src/gen_mod.erl @@ -40,6 +40,7 @@ loaded_modules_with_opts/1, get_hosts/2, get_module_proc/2, + expand_host_name/3, is_loaded/2]). -export([behaviour_info/1]). @@ -192,6 +193,9 @@ loaded_modules_with_opts(Host) -> [], [{{'$1', '$2'}}]}]). +set_module_opts_mnesia(global, _Module, _Opts) -> + %% Modules on the global host are usually static, so we shouldn't manipulate them. + ok; set_module_opts_mnesia(Host, Module, Opts) -> Modules = case ejabberd_config:get_local_option({modules, Host}) of undefined -> @@ -203,6 +207,9 @@ set_module_opts_mnesia(Host, Module, Opts) -> Modules2 = [{Module, Opts} | Modules1], ejabberd_config:add_local_option({modules, Host}, Modules2). +del_module_mnesia(global, _Module) -> + %% Modules on the global host are usually static, so we shouldn't manipulate them. + ok; del_module_mnesia(Host, Module) -> Modules = case ejabberd_config:get_local_option({modules, Host}) of undefined -> @@ -226,6 +233,8 @@ get_hosts(Opts, Prefix) -> Hosts end. +get_module_proc(global, Base) -> + list_to_atom(atom_to_list(Base) ++ "__global"); get_module_proc(Host, {frontend, Base}) -> get_module_proc("frontend_" ++ Host, Base); get_module_proc(Host, Base) -> @@ -234,3 +243,11 @@ get_module_proc(Host, Base) -> is_loaded(Host, Module) -> ets:member(ejabberd_modules, {Module, Host}). +expand_host_name(Host, Opts, DefaultPrefix) -> + case Host of + global -> + {global, gen_mod:get_opt(prefix, Opts, DefaultPrefix)}; + _ -> + gen_mod:get_opt_host(Host, Opts, DefaultPrefix ++ ".@HOST@") + end. + diff --git a/src/odbc/ejabberd_odbc.erl b/src/odbc/ejabberd_odbc.erl index 79ab48d14..53895cc70 100644 --- a/src/odbc/ejabberd_odbc.erl +++ b/src/odbc/ejabberd_odbc.erl @@ -33,6 +33,7 @@ %% External exports -export([start/1, start_link/2, + running/1, sql_query/2, sql_query_t/1, sql_transaction/2, @@ -106,6 +107,14 @@ sql_query_on_all_connections(Host, Query) -> erlang:now()}, ?TRANSACTION_TIMEOUT) end, lists:map(F, ejabberd_odbc_sup:get_pids(Host)). +%% Predicate returning true if there is an odbc process running for +%% host Host, false otherwise. +running(Host) -> + case catch ejabberd_odbc_sup:get_random_pid(Host) of + P when is_pid(P) -> true; + {'EXIT', {noproc, _}} -> false + end. + %% SQL transaction based on a list of queries %% This function automatically sql_transaction(Host, Queries) when is_list(Queries) -> @@ -497,7 +506,11 @@ pgsql_to_odbc({ok, PGSQLResult}) -> pgsql_item_to_odbc({"SELECT", Rows, Recs}) -> {selected, - [element(1, Row) || Row <- Rows], + [case Row of + {desc, _, Col, _, _, _, _, _} -> Col; % Recent pgsql driver API change. + _ -> element(1, Row) + end + || Row <- Rows], [list_to_tuple(Rec) || Rec <- Recs]}; pgsql_item_to_odbc("INSERT " ++ OIDN) -> [_OID, N] = string:tokens(OIDN, " "), @@ -555,6 +568,7 @@ log(Level, Format, Args) -> ?ERROR_MSG(Format, Args) end. +%% TODO: update this function to handle the case clase {host, VhostName} db_opts(Host) -> case ejabberd_config:get_local_option({odbc_server, Host}) of %% Default pgsql port diff --git a/src/odbc/ejabberd_odbc_sup.erl b/src/odbc/ejabberd_odbc_sup.erl index 45ede1835..819028d44 100644 --- a/src/odbc/ejabberd_odbc_sup.erl +++ b/src/odbc/ejabberd_odbc_sup.erl @@ -100,11 +100,16 @@ init([Host]) -> end, lists:seq(1, PoolSize))}}. get_pids(Host) -> - Rs = mnesia:dirty_read(sql_pool, Host), - [R#sql_pool.pid || R <- Rs]. + case ejabberd_config:get_local_option({odbc_server, Host}) of + {host, Host1} -> + get_pids(Host1); + _ -> + Rs = mnesia:dirty_read(sql_pool, Host), + [R#sql_pool.pid || R <- Rs] + end. get_random_pid(Host) -> - Pids = get_pids(Host), + Pids = get_pids(ejabberd:normalize_host(Host)), lists:nth(erlang:phash(now(), length(Pids)), Pids). add_pid(Host, Pid) -> diff --git a/src/odbc/mysql.sql b/src/odbc/mysql.sql index 9d8fa9bb9..29a71ce24 100644 --- a/src/odbc/mysql.sql +++ b/src/odbc/mysql.sql @@ -20,21 +20,35 @@ -- Needs MySQL (at least 4.0.x) with innodb back-end SET table_type=InnoDB; +CREATE TABLE hosts ( + clusterid integer NOT NULL, + host varchar(250) NOT NULL PRIMARY KEY, + config text NOT NULL +) CHARACTER SET utf8; + +INSERT INTO hosts (clusterid, host, config) +VALUES (1, 'localhost', ''); + CREATE TABLE users ( - username varchar(250) PRIMARY KEY, + host varchar(250) NOT NULL, + username varchar(250) NOT NULL, password text NOT NULL, - created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (host, username) ) CHARACTER SET utf8; CREATE TABLE last ( - username varchar(250) PRIMARY KEY, + host varchar(250) NOT NULL, + username varchar(250) NOT NULL, seconds text NOT NULL, - state text NOT NULl + state text NOT NULL, + PRIMARY KEY (host, username) ) CHARACTER SET utf8; CREATE TABLE rosterusers ( + host varchar(250) NOT NULL, username varchar(250) NOT NULL, jid varchar(250) NOT NULL, nick text NOT NULL, @@ -44,42 +58,44 @@ CREATE TABLE rosterusers ( server character(1) NOT NULL, subscribe text NOT NULL, type text, - created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (host(75), username(75), jid(75)) ) CHARACTER SET utf8; -CREATE UNIQUE INDEX i_rosteru_user_jid ON rosterusers(username(75), jid(75)); CREATE INDEX i_rosteru_username ON rosterusers(username); CREATE INDEX i_rosteru_jid ON rosterusers(jid); CREATE TABLE rostergroups ( + host varchar(250) NOT NULL, username varchar(250) NOT NULL, jid varchar(250) NOT NULL, - grp text NOT NULL + grp text NOT NULL, + PRIMARY KEY (host(75), username(75), jid(75)) ) CHARACTER SET utf8; -CREATE INDEX pk_rosterg_user_jid ON rostergroups(username(75), jid(75)); - - CREATE TABLE spool ( + host varchar(250) NOT NULL, username varchar(250) NOT NULL, xml text NOT NULL, seq BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, - created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (host, username, seq) ) CHARACTER SET utf8; -CREATE INDEX i_despool USING BTREE ON spool(username); - CREATE TABLE vcard ( - username varchar(250) PRIMARY KEY, + host varchar(250) NOT NULL, + username varchar(250) NOT NULL, vcard text NOT NULL, - created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (host, username) ) CHARACTER SET utf8; CREATE TABLE vcard_search ( + host varchar(250) NOT NULL, username varchar(250) NOT NULL, - lusername varchar(250) PRIMARY KEY, + lusername varchar(250) NOT NULL, fn text NOT NULL, lfn varchar(250) NOT NULL, family text NOT NULL, @@ -101,7 +117,8 @@ CREATE TABLE vcard_search ( orgname text NOT NULL, lorgname varchar(250) NOT NULL, orgunit text NOT NULL, - lorgunit varchar(250) NOT NULL + lorgunit varchar(250) NOT NULL, + PRIMARY KEY (host, lusername) ) CHARACTER SET utf8; CREATE INDEX i_vcard_search_lfn ON vcard_search(lfn); @@ -117,20 +134,21 @@ CREATE INDEX i_vcard_search_lorgname ON vcard_search(lorgname); CREATE INDEX i_vcard_search_lorgunit ON vcard_search(lorgunit); CREATE TABLE privacy_default_list ( - username varchar(250) PRIMARY KEY, - name varchar(250) NOT NULL + host varchar(250) NOT NULL, + username varchar(250), + name varchar(250) NOT NULL, + PRIMARY KEY (host, username) ) CHARACTER SET utf8; CREATE TABLE privacy_list ( + host varchar(250) NOT NULL, username varchar(250) NOT NULL, name varchar(250) NOT NULL, id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, - created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (host, username, name) ) CHARACTER SET utf8; -CREATE INDEX i_privacy_list_username USING BTREE ON privacy_list(username); -CREATE UNIQUE INDEX i_privacy_list_username_name USING BTREE ON privacy_list (username(75), name(75)); - CREATE TABLE privacy_list_data ( id bigint, t character(1) NOT NULL, @@ -145,14 +163,15 @@ CREATE TABLE privacy_list_data ( ) CHARACTER SET utf8; CREATE TABLE private_storage ( + host varchar(250) NOT NULL, username varchar(250) NOT NULL, namespace varchar(250) NOT NULL, data text NOT NULL, - created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (host(75), username(75), namespace(75)) ) CHARACTER SET utf8; CREATE INDEX i_private_storage_username USING BTREE ON private_storage(username); -CREATE UNIQUE INDEX i_private_storage_username_namespace USING BTREE ON private_storage(username(75), namespace(75)); -- Not tested in mysql CREATE TABLE roster_version ( diff --git a/src/odbc/odbc_queries.erl b/src/odbc/odbc_queries.erl index 14a89dae1..77409d048 100644 --- a/src/odbc/odbc_queries.erl +++ b/src/odbc/odbc_queries.erl @@ -41,7 +41,7 @@ list_users/2, users_number/1, users_number/2, - add_spool_sql/2, + add_spool_sql/3, add_spool/2, get_and_del_spool_msg_t/2, del_spool_msg/2, @@ -52,27 +52,27 @@ get_roster_by_jid/3, get_rostergroup_by_jid/3, del_roster/3, - del_roster_sql/2, + del_roster_sql/3, update_roster/5, - update_roster_sql/4, + update_roster_sql/5, roster_subscribe/4, get_subscription/3, set_private_data/4, - set_private_data_sql/3, + set_private_data_sql/4, get_private_data/3, del_user_private_storage/2, get_default_privacy_list/2, - get_default_privacy_list_t/1, + get_default_privacy_list_t/2, get_privacy_list_names/2, - get_privacy_list_names_t/1, + get_privacy_list_names_t/2, get_privacy_list_id/3, - get_privacy_list_id_t/2, + get_privacy_list_id_t/3, get_privacy_list_data/3, get_privacy_list_data_by_id/2, - set_default_privacy_list/2, + set_default_privacy_list/3, unset_default_privacy_list/2, - remove_privacy_list/2, - add_privacy_list/2, + remove_privacy_list/3, + add_privacy_list/3, set_privacy_list/2, del_privacy_lists/3, set_vcard/26, @@ -122,65 +122,75 @@ sql_transaction(LServer, F) -> ejabberd_odbc:sql_transaction(LServer, F). get_last(LServer, Username) -> + Host = escape(LServer), ejabberd_odbc:sql_query( LServer, ["select seconds, state from last " - "where username='", Username, "'"]). + "where username='", Username, "' and host='", Host, "'"]). set_last_t(LServer, Username, Seconds, State) -> + Host = escape(LServer), %% MREMOND: I think this should be turn into a non transactional behaviour ejabberd_odbc:sql_transaction( LServer, fun() -> - update_t("last", ["username", "seconds", "state"], - [Username, Seconds, State], - ["username='", Username, "'"]) + update_t("last", ["username", "host", "seconds", "state"], + [Username, Host, Seconds, State], + ["username='", Username, "' and host='", Host, "'"]) end). del_last(LServer, Username) -> + Host = escape(LServer), ejabberd_odbc:sql_query( LServer, - ["delete from last where username='", Username, "'"]). + ["delete from last where username='", Username, "' and host='", Host, "'"]). get_password(LServer, Username) -> + Host = escape(LServer), ejabberd_odbc:sql_query( LServer, ["select password from users " - "where username='", Username, "';"]). + "where username='", Username, "' and host='", Host, "';"]). set_password_t(LServer, Username, Pass) -> + Host = escape(LServer), ejabberd_odbc:sql_transaction( LServer, fun() -> - update_t("users", ["username", "password"], - [Username, Pass], - ["username='", Username ,"'"]) + update_t("users", ["username", "host", "password"], + [Username, Host, Pass], + ["username='", Username ,"' and host='", Host, "'"]) end). add_user(LServer, Username, Pass) -> + Host = escape(LServer), ejabberd_odbc:sql_query( LServer, - ["insert into users(username, password) " - "values ('", Username, "', '", Pass, "');"]). + ["insert into users(username, host, password) " + "values ('", Username, "', '", Host, "', '", Pass, "');"]). del_user(LServer, Username) -> + Host = escape(LServer), ejabberd_odbc:sql_query( LServer, - ["delete from users where username='", Username ,"';"]). + ["delete from users where username='", Username ,"' and host='", Host, "';"]). -del_user_return_password(_LServer, Username, Pass) -> - P = ejabberd_odbc:sql_query_t( - ["select password from users where username='", - Username, "';"]), +del_user_return_password(LServer, Username, Pass) -> + Host = escape(LServer), + ejabberd_odbc:sql_query_t( + ["select password from users where username='", + Username, "' and host='", Host, "';"]), ejabberd_odbc:sql_query_t(["delete from users " "where username='", Username, + "' and host='", Host, "' and password='", Pass, "';"]), - P. + Pass. % REVIEWME list_users(LServer) -> + Host = escape(LServer), ejabberd_odbc:sql_query( LServer, - "select username from users"). + ["select username from users where host='", Host, "'"]). list_users(LServer, [{from, Start}, {to, End}]) when is_integer(Start) and is_integer(End) -> @@ -211,6 +221,7 @@ list_users(LServer, [{prefix, Prefix}, "limit ~w offset ~w ", [Prefix, Limit, Offset])). users_number(LServer) -> + Host = escape(LServer), case element(1, ejabberd_config:get_local_option({odbc_server, LServer})) of mysql -> ejabberd_odbc:sql_query( @@ -224,13 +235,13 @@ users_number(LServer) -> "select reltuples from pg_class where oid = 'users'::regclass::oid"); _ -> ejabberd_odbc:sql_query( - LServer, - "select count(*) from users") + LServer, + ["select count(*) from users where host='", Host, "'"]) end; _ -> ejabberd_odbc:sql_query( - LServer, - "select count(*) from users") + LServer, + ["select count(*) from users where host='", Host, "'"]) end. users_number(LServer, [{prefix, Prefix}]) when is_list(Prefix) -> @@ -243,10 +254,10 @@ users_number(LServer, [{prefix, Prefix}]) when is_list(Prefix) -> users_number(LServer, []) -> users_number(LServer). - -add_spool_sql(Username, XML) -> - ["insert into spool(username, xml) " - "values ('", Username, "', '", +add_spool_sql(LServer, Username, XML) -> + Host = escape(LServer), + ["insert into spool(username, host, xml) " + "values ('", Username, "', '", Host, "', '", XML, "');"]. @@ -255,158 +266,177 @@ add_spool(LServer, Queries) -> LServer, Queries). get_and_del_spool_msg_t(LServer, Username) -> + Host = escape(LServer), F = fun() -> Result = ejabberd_odbc:sql_query_t( ["select username, xml from spool where username='", Username, "'" + " and host='", Host, "'" " order by seq;"]), ejabberd_odbc:sql_query_t( - ["delete from spool where username='", Username, "';"]), + ["delete from spool where username='", Username, "' and host='", Host, "';"]), Result end, ejabberd_odbc:sql_transaction(LServer,F). del_spool_msg(LServer, Username) -> + Host = escape(LServer), ejabberd_odbc:sql_query( LServer, - ["delete from spool where username='", Username, "';"]). + ["delete from spool where username='", Username, "' and host='", Host, "';"]). get_roster(LServer, Username) -> + Host = escape(LServer), ejabberd_odbc:sql_query( LServer, ["select username, jid, nick, subscription, ask, " "askmessage, server, subscribe, type from rosterusers " - "where username='", Username, "'"]). + "where username='", Username, "' and host='", Host, "'"]). get_roster_jid_groups(LServer, Username) -> + Host = escape(LServer), ejabberd_odbc:sql_query( LServer, ["select jid, grp from rostergroups " - "where username='", Username, "'"]). + "where username='", Username, "' and host='", Host, "'"]). -get_roster_groups(_LServer, Username, SJID) -> +get_roster_groups(LServer, Username, SJID) -> + Host = escape(LServer), ejabberd_odbc:sql_query_t( ["select grp from rostergroups " - "where username='", Username, "' " + "where username='", Username, "' and host='", Host, "' " "and jid='", SJID, "';"]). del_user_roster_t(LServer, Username) -> + Host = escape(LServer), ejabberd_odbc:sql_transaction( LServer, fun() -> ejabberd_odbc:sql_query_t( ["delete from rosterusers " - " where username='", Username, "';"]), + " where username='", Username, "' and host='", Host, "';"]), ejabberd_odbc:sql_query_t( ["delete from rostergroups " - " where username='", Username, "';"]) + " where username='", Username, "' and host='", Host, "';"]) end). -get_roster_by_jid(_LServer, Username, SJID) -> +get_roster_by_jid(LServer, Username, SJID) -> + Host = escape(LServer), ejabberd_odbc:sql_query_t( ["select username, jid, nick, subscription, " "ask, askmessage, server, subscribe, type from rosterusers " - "where username='", Username, "' " + "where username='", Username, "' and host='", Host, "' " "and jid='", SJID, "';"]). get_rostergroup_by_jid(LServer, Username, SJID) -> + Host = escape(LServer), ejabberd_odbc:sql_query( LServer, ["select grp from rostergroups " - "where username='", Username, "' " + "where username='", Username, "' and host='", Host, "' " "and jid='", SJID, "'"]). -del_roster(_LServer, Username, SJID) -> +del_roster(LServer, Username, SJID) -> + Host = escape(LServer), ejabberd_odbc:sql_query_t( ["delete from rosterusers " - " where username='", Username, "' " + " where username='", Username, "' and host='", Host, "' " " and jid='", SJID, "';"]), ejabberd_odbc:sql_query_t( ["delete from rostergroups " - " where username='", Username, "' " + " where username='", Username, "' and host='", Host, "' " " and jid='", SJID, "';"]). -del_roster_sql(Username, SJID) -> +del_roster_sql(LServer, Username, SJID) -> + Host = escape(LServer), [["delete from rosterusers " - " where username='", Username, "' " + " where username='", Username, "' and host='", Host, "' " " and jid='", SJID, "';"], ["delete from rostergroups " - " where username='", Username, "' " + " where username='", Username, "' and host='", Host, "' " " and jid='", SJID, "';"]]. -update_roster(_LServer, Username, SJID, ItemVals, ItemGroups) -> +update_roster(LServer, Username, SJID, ItemVals, ItemGroups) -> + Host = escape(LServer), update_t("rosterusers", - ["username", "jid", "nick", "subscription", "ask", + ["host", "username", "jid", "nick", "subscription", "ask", "askmessage", "server", "subscribe", "type"], - ItemVals, - ["username='", Username, "' and jid='", SJID, "'"]), + [Host | ItemVals], + ["username='", Username, "' and host='", Host, "' and jid='", SJID, "'"]), ejabberd_odbc:sql_query_t( ["delete from rostergroups " - " where username='", Username, "' " + " where username='", Username, "' and host='", Host, "' " " and jid='", SJID, "';"]), lists:foreach(fun(ItemGroup) -> ejabberd_odbc:sql_query_t( ["insert into rostergroups(" - " username, jid, grp) " - " values ('", string:join(ItemGroup, "', '"), "');"]) + " host, username, jid, grp) " + " values ('", Host, "', '", string:join(ItemGroup, "', '"), "');"]) end, ItemGroups). -update_roster_sql(Username, SJID, ItemVals, ItemGroups) -> +update_roster_sql(LServer, Username, SJID, ItemVals, ItemGroups) -> + Host = escape(LServer), [["delete from rosterusers " - " where username='", Username, "' " + " where username='", Username, "' and host='", Host, "' " " and jid='", SJID, "';"], ["insert into rosterusers(" - " username, jid, nick, " + " host, username, jid, nick, " " subscription, ask, askmessage, " " server, subscribe, type) " - " values ('", string:join(ItemVals, "', '"), "');"], + " values ('", Host, "', '", string:join(ItemVals, "', '"), "');"], ["delete from rostergroups " - " where username='", Username, "' " + " where username='", Username, "' and host='", Host, "' " " and jid='", SJID, "';"]] ++ [["insert into rostergroups(" - " username, jid, grp) " - " values ('", string:join(ItemGroup, "', '"), "');"] || + " host, username, jid, grp) " + " values ('", Host, "', '", string:join(ItemGroup, "', '"), "');"] || ItemGroup <- ItemGroups]. -roster_subscribe(_LServer, Username, SJID, ItemVals) -> +roster_subscribe(LServer, Username, SJID, ItemVals) -> + Host = escape(LServer), update_t("rosterusers", - ["username", "jid", "nick", "subscription", "ask", + ["host", "username", "jid", "nick", "subscription", "ask", "askmessage", "server", "subscribe", "type"], - ItemVals, - ["username='", Username, "' and jid='", SJID, "'"]). + [Host | ItemVals], + ["username='", Username, "' and host='", Host, "' and jid='", SJID, "'"]). get_subscription(LServer, Username, SJID) -> + Host = escape(LServer), ejabberd_odbc:sql_query( LServer, ["select subscription from rosterusers " - "where username='", Username, "' " + "where username='", Username, "' and host='", Host, "' " "and jid='", SJID, "'"]). -set_private_data(_LServer, Username, LXMLNS, SData) -> +set_private_data(LServer, Username, LXMLNS, SData) -> + Host = escape(LServer), update_t("private_storage", - ["username", "namespace", "data"], - [Username, LXMLNS, SData], - ["username='", Username, "' and namespace='", LXMLNS, "'"]). + ["host", "username", "namespace", "data"], + [Host, Username, LXMLNS, SData], + ["username='", Username, "' and host='", Host, "' and namespace='", LXMLNS, "'"]). -set_private_data_sql(Username, LXMLNS, SData) -> +set_private_data_sql(LServer, Username, LXMLNS, SData) -> + Host = escape(LServer), [["delete from private_storage " - "where username='", Username, "' and " + "where username='", Username, "' and host='", Host, "' and " "namespace='", LXMLNS, "';"], - ["insert into private_storage(username, namespace, data) " - "values ('", Username, "', '", LXMLNS, "', " + ["insert into private_storage(host, username, namespace, data) " + "values ('", Host, "', '", Username, "', '", LXMLNS, "', " "'", SData, "');"]]. get_private_data(LServer, Username, LXMLNS) -> + Host = escape(LServer), ejabberd_odbc:sql_query( LServer, ["select data from private_storage " - "where username='", Username, "' and " + "where username='", Username, "' and host='", Host, "' and " "namespace='", LXMLNS, "';"]). del_user_private_storage(LServer, Username) -> + Host = escape(LServer), ejabberd_odbc:sql_query( LServer, - ["delete from private_storage where username='", Username, "';"]). + ["delete from private_storage where username='", Username, "' and host='", Host, "';"]). set_vcard(LServer, LUsername, SBDay, SCTRY, SEMail, SFN, SFamily, SGiven, SLBDay, SLCTRY, SLEMail, SLFN, SLFamily, SLGiven, SLLocality, @@ -415,16 +445,16 @@ set_vcard(LServer, LUsername, SBDay, SCTRY, SEMail, SFN, SFamily, SGiven, ejabberd_odbc:sql_transaction( LServer, fun() -> - update_t("vcard", ["username", "vcard"], - [LUsername, SVCARD], - ["username='", LUsername, "'"]), + update_t("vcard", ["host", "username", "vcard"], + [LServer, LUsername, SVCARD], + ["username='", LUsername, "' and host='", LServer, "'"]), update_t("vcard_search", - ["username", "lusername", "fn", "lfn", "family", + ["host", "username", "lusername", "fn", "lfn", "family", "lfamily", "given", "lgiven", "middle", "lmiddle", "nickname", "lnickname", "bday", "lbday", "ctry", "lctry", "locality", "llocality", "email", "lemail", "orgname", "lorgname", "orgunit", "lorgunit"], - [Username, LUsername, SFN, SLFN, SFamily, SLFamily, + [LServer, Username, LUsername, SFN, SLFN, SFamily, SLFamily, SGiven, SLGiven, SMiddle, SLMiddle, SNickname, SLNickname, SBDay, SLBDay, SCTRY, SLCTRY, SLocality, SLLocality, SEMail, SLEMail, SOrgName, @@ -436,7 +466,7 @@ get_vcard(LServer, Username) -> ejabberd_odbc:sql_query( LServer, ["select vcard from vcard " - "where username='", Username, "';"]). + "where username='", Username, "' and host='", LServer, "';"]). del_vcard(LServer, Username) -> ejabberd_odbc:sql_transaction( @@ -457,34 +487,34 @@ get_default_privacy_list(LServer, Username) -> ejabberd_odbc:sql_query( LServer, ["select name from privacy_default_list " - "where username='", Username, "';"]). + "where username='", Username, "' and host='", LServer, "';"]). -get_default_privacy_list_t(Username) -> +get_default_privacy_list_t(LServer, Username) -> ejabberd_odbc:sql_query_t( ["select name from privacy_default_list " - "where username='", Username, "';"]). + "where username='", Username, "' and host='", LServer, "';"]). get_privacy_list_names(LServer, Username) -> ejabberd_odbc:sql_query( LServer, ["select name from privacy_list " - "where username='", Username, "';"]). + "where username='", Username, "' and host='", LServer, "';"]). -get_privacy_list_names_t(Username) -> +get_privacy_list_names_t(LServer, Username) -> ejabberd_odbc:sql_query_t( ["select name from privacy_list " - "where username='", Username, "';"]). + "where username='", Username, "' and host='", LServer, "';"]). get_privacy_list_id(LServer, Username, SName) -> ejabberd_odbc:sql_query( LServer, ["select id from privacy_list " - "where username='", Username, "' and name='", SName, "';"]). + "where username='", Username, "' and name='", SName, "' and host='", LServer, "';"]). -get_privacy_list_id_t(Username, SName) -> +get_privacy_list_id_t(LServer, Username, SName) -> ejabberd_odbc:sql_query_t( ["select id from privacy_list " - "where username='", Username, "' and name='", SName, "';"]). + "where username='", Username, "' and name='", SName, "' and host='", LServer, "';"]). get_privacy_list_data(LServer, Username, SName) -> ejabberd_odbc:sql_query( @@ -493,7 +523,7 @@ get_privacy_list_data(LServer, Username, SName) -> "match_message, match_presence_in, match_presence_out " "from privacy_list_data " "where id = (select id from privacy_list where " - " username='", Username, "' and name='", SName, "') " + " username='", Username, "' and name='", SName, "' and host='", LServer, "') " "order by ord;"]). get_privacy_list_data_by_id(LServer, ID) -> @@ -504,25 +534,26 @@ get_privacy_list_data_by_id(LServer, ID) -> "from privacy_list_data " "where id='", ID, "' order by ord;"]). -set_default_privacy_list(Username, SName) -> - update_t("privacy_default_list", ["username", "name"], - [Username, SName], ["username='", Username, "'"]). +set_default_privacy_list(LServer, Username, SName) -> + update_t("privacy_default_list", ["host", "username", "name"], + [LServer, Username, SName], + ["host='", LServer, "' and username='", Username, "'"]). unset_default_privacy_list(LServer, Username) -> ejabberd_odbc:sql_query( LServer, ["delete from privacy_default_list " - " where username='", Username, "';"]). + " where username='", Username, "' and host='", LServer, "';"]). -remove_privacy_list(Username, SName) -> +remove_privacy_list(LServer, Username, SName) -> ejabberd_odbc:sql_query_t( ["delete from privacy_list " - "where username='", Username, "' and name='", SName, "';"]). + "where username='", Username, "' and name='", SName, "' and host='", LServer, "';"]). -add_privacy_list(Username, SName) -> +add_privacy_list(LServer, Username, SName) -> ejabberd_odbc:sql_query_t( - ["insert into privacy_list(username, name) " - "values ('", Username, "', '", SName, "');"]). + ["insert into privacy_list(host, username, name) " + "values ('", LServer, "', '", Username, "', '", SName, "');"]). set_privacy_list(ID, RItems) -> ejabberd_odbc:sql_query_t( @@ -542,13 +573,13 @@ set_privacy_list(ID, RItems) -> del_privacy_lists(LServer, Server, Username) -> ejabberd_odbc:sql_query( LServer, - ["delete from privacy_list where username='", Username, "';"]), + ["delete from privacy_list where username='", Username, "' and host='", LServer, "';"]), ejabberd_odbc:sql_query( LServer, ["delete from privacy_list_data where value='", Username++"@"++Server, "';"]), ejabberd_odbc:sql_query( LServer, - ["delete from privacy_default_list where username='", Username, "';"]). + ["delete from privacy_default_list where username='", Username, "' and host='", LServer, "';"]). %% Characters to escape escape($\0) -> "\\0"; diff --git a/src/odbc/pg.sql b/src/odbc/pg.sql index 1d6a458aa..25bd8d9ea 100644 --- a/src/odbc/pg.sql +++ b/src/odbc/pg.sql @@ -18,21 +18,24 @@ -- CREATE TABLE users ( - username text PRIMARY KEY, + username text NOT NULL, + host text NOT NULL, "password" text NOT NULL, + PRIMARY KEY (host, username) created_at TIMESTAMP NOT NULL DEFAULT now() ); - CREATE TABLE last ( - username text PRIMARY KEY, + username text NOT NULL, + host text NOT NULL, seconds text NOT NULL, - state text NOT NULL + state text NOT NULL, + PRIMARY KEY (host, username) ); - CREATE TABLE rosterusers ( username text NOT NULL, + host text NOT NULL, jid text NOT NULL, nick text NOT NULL, subscription character(1) NOT NULL, @@ -41,42 +44,50 @@ CREATE TABLE rosterusers ( server character(1) NOT NULL, subscribe text, "type" text, +<<<<<<< HEAD + PRIMARY KEY (host, username, jid) +======= created_at TIMESTAMP NOT NULL DEFAULT now() +>>>>>>> 30 ); -CREATE UNIQUE INDEX i_rosteru_user_jid ON rosterusers USING btree (username, jid); -CREATE INDEX i_rosteru_username ON rosterusers USING btree (username); -CREATE INDEX i_rosteru_jid ON rosterusers USING btree (jid); - - CREATE TABLE rostergroups ( username text NOT NULL, + host text NOT NULL, jid text NOT NULL, - grp text NOT NULL + grp text NOT NULL, + PRIMARY KEY (host, username, jid) ); -CREATE INDEX pk_rosterg_user_jid ON rostergroups USING btree (username, jid); - - CREATE TABLE spool ( username text NOT NULL, + host text NOT NULL, xml text NOT NULL, seq SERIAL, +<<<<<<< HEAD + PRIMARY KEY (host, username, seq) +======= created_at TIMESTAMP NOT NULL DEFAULT now() +>>>>>>> 30 ); -CREATE INDEX i_despool ON spool USING btree (username); - - CREATE TABLE vcard ( +<<<<<<< HEAD + username text NOT NULL, + host text NOT NULL, + vcard text NOT NULL, + PRIMARY KEY (host, username) +======= username text PRIMARY KEY, vcard text NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT now() +>>>>>>> 30 ); CREATE TABLE vcard_search ( + host text NOT NULL, username text NOT NULL, - lusername text PRIMARY KEY, + lusername text NOT NULL, fn text NOT NULL, lfn text NOT NULL, family text NOT NULL, @@ -98,9 +109,11 @@ CREATE TABLE vcard_search ( orgname text NOT NULL, lorgname text NOT NULL, orgunit text NOT NULL, - lorgunit text NOT NULL + lorgunit text NOT NULL, + PRIMARY KEY (host, lusername) ); +CREATE INDEX i_vcard_search_lusername ON vcard_search(lusername); CREATE INDEX i_vcard_search_lfn ON vcard_search(lfn); CREATE INDEX i_vcard_search_lfamily ON vcard_search(lfamily); CREATE INDEX i_vcard_search_lgiven ON vcard_search(lgiven); @@ -114,20 +127,24 @@ CREATE INDEX i_vcard_search_lorgname ON vcard_search(lorgname); CREATE INDEX i_vcard_search_lorgunit ON vcard_search(lorgunit); CREATE TABLE privacy_default_list ( - username text PRIMARY KEY, - name text NOT NULL + username text NOT NULL, + host text NOT NULL, + name text NOT NULL, + PRIMARY KEY (host, username) ); CREATE TABLE privacy_list ( username text NOT NULL, + host text NOT NULL, name text NOT NULL, id SERIAL UNIQUE, +<<<<<<< HEAD + PRIMARY KEY (host, username, name) +======= created_at TIMESTAMP NOT NULL DEFAULT now() +>>>>>>> 30 ); -CREATE INDEX i_privacy_list_username ON privacy_list USING btree (username); -CREATE UNIQUE INDEX i_privacy_list_username_name ON privacy_list USING btree (username, name); - CREATE TABLE privacy_list_data ( id bigint REFERENCES privacy_list(id) ON DELETE CASCADE, t character(1) NOT NULL, @@ -138,18 +155,28 @@ CREATE TABLE privacy_list_data ( match_iq boolean NOT NULL, match_message boolean NOT NULL, match_presence_in boolean NOT NULL, - match_presence_out boolean NOT NULL + match_presence_out boolean NOT NULL, + PRIMARY KEY (id) ); CREATE TABLE private_storage ( username text NOT NULL, + host text NOT NULL, namespace text NOT NULL, data text NOT NULL, +<<<<<<< HEAD + PRIMARY KEY (host, username, namespace) +======= created_at TIMESTAMP NOT NULL DEFAULT now() +>>>>>>> 30 ); -CREATE INDEX i_private_storage_username ON private_storage USING btree (username); -CREATE UNIQUE INDEX i_private_storage_username_namespace ON private_storage USING btree (username, namespace); +CREATE TABLE hosts ( + clusterid integer NOT NULL, + host text NOT NULL, + config text NOT NULL, + PRIMARY KEY (host) +); CREATE TABLE roster_version ( diff --git a/src/randoms.erl b/src/randoms.erl index 455ea3529..1b85c33e0 100644 --- a/src/randoms.erl +++ b/src/randoms.erl @@ -33,7 +33,12 @@ start() -> - register(random_generator, spawn(randoms, init, [])). + case erlang:whereis(random_generator) of + Pid when is_pid(Pid) -> + true; + undefined -> + register(random_generator, spawn(randoms, init, [])) + end. init() -> {A1, A2, A3} = now(), diff --git a/src/web/ejabberd_web_admin.erl b/src/web/ejabberd_web_admin.erl index ec69ac503..7d1063136 100644 --- a/src/web/ejabberd_web_admin.erl +++ b/src/web/ejabberd_web_admin.erl @@ -165,7 +165,7 @@ process(["doc", LocalFile], _Request) -> process(["server", SHost | RPath] = Path, #request{auth = Auth, lang = Lang, host = HostHTTP, method = Method} = Request) -> Host = exmpp_stringprep:nameprep(SHost), - case lists:member(Host, ?MYHOSTS) of + case ?IS_MY_HOST(Host) of true -> case get_auth_admin(Auth, HostHTTP, Path, Method) of {ok, {User, Server}} ->