diff --git a/sql/mysql.sql b/sql/mysql.sql index 7f96905c0..64f54ba70 100644 --- a/sql/mysql.sql +++ b/sql/mysql.sql @@ -278,3 +278,17 @@ CREATE TABLE caps_features ( ) ENGINE=InnoDB CHARACTER SET utf8; CREATE INDEX i_caps_features_node_subnode ON caps_features(node(75), subnode(75)); + +CREATE TABLE sm ( + usec bigint NOT NULL, + pid text NOT NULL, + node text NOT NULL, + username varchar(250) NOT NULL, + resource varchar(250) NOT NULL, + priority text NOT NULL, + info text NOT NULL +) ENGINE=InnoDB CHARACTER SET utf8; + +CREATE UNIQUE INDEX i_sid ON sm(usec, pid(75)); +CREATE INDEX i_node ON sm(node(75)); +CREATE INDEX i_username ON sm(username); diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl index 957aa5d46..15170daee 100644 --- a/src/ejabberd_app.erl +++ b/src/ejabberd_app.erl @@ -58,6 +58,7 @@ start(normal, _Args) -> Sup = ejabberd_sup:start_link(), ejabberd_rdbms:start(), ejabberd_riak_sup:start(), + ejabberd_sm:start(), ejabberd_auth:start(), cyrsasl:start(), % Profiling diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index 9925d26dd..67a82d024 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -30,7 +30,8 @@ -behaviour(gen_server). %% API --export([start_link/0, +-export([start/0, + start_link/0, route/3, open_session/5, open_session/6, @@ -77,9 +78,9 @@ -include("ejabberd_sm.hrl"). -callback init() -> ok | {error, any()}. --callback get_session(sid()) -> {ok, #session{}} | {error, notfound}. +-callback get_session(binary(), sid()) -> {ok, #session{}} | {error, notfound}. -callback set_session(#session{}) -> ok. --callback delete_session(sid()) -> ok. +-callback delete_session(binary(), sid()) -> ok. -callback get_sessions() -> [#session{}]. -callback get_sessions(binary()) -> [#session{}]. -callback get_sessions(binary(), binary()) -> [#session{}]. @@ -99,6 +100,11 @@ %%-------------------------------------------------------------------- -export_type([sid/0]). +start() -> + ChildSpec = {?MODULE, {?MODULE, start_link, []}, + transient, 1000, worker, [?MODULE]}, + supervisor:start_child(ejabberd_sup, ChildSpec). + start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). @@ -131,11 +137,12 @@ open_session(SID, User, Server, Resource, Info) -> close_session(SID, User, Server, Resource) -> Mod = get_sm_backend(), - Info = case Mod:get_session(SID) of + LServer = jlib:nameprep(Server), + Info = case Mod:get_session(LServer, SID) of {ok, #session{info = I}} -> I; {error, notfound} -> [] end, - Mod:delete_session(SID), + Mod:delete_session(LServer, SID), JID = jlib:make_jid(User, Server, Resource), ejabberd_hooks:run(sm_remove_connection_hook, JID#jid.lserver, [SID, JID, Info]). @@ -723,7 +730,8 @@ force_update_presence({LUser, LServer}) -> get_sm_backend() -> DBType = ejabberd_config:get_option(sm_db_type, fun(mnesia) -> mnesia; - (internal) -> mnesia + (internal) -> mnesia; + (odbc) -> odbc end, mnesia), list_to_atom("ejabberd_sm_" ++ atom_to_list(DBType)). diff --git a/src/ejabberd_sm_mnesia.erl b/src/ejabberd_sm_mnesia.erl index 504fa9842..59a6c64f6 100644 --- a/src/ejabberd_sm_mnesia.erl +++ b/src/ejabberd_sm_mnesia.erl @@ -13,9 +13,9 @@ %% API -export([init/0, - get_session/1, + get_session/2, set_session/1, - delete_session/1, + delete_session/2, get_sessions/0, get_sessions/1, get_sessions/2, @@ -44,8 +44,8 @@ init() -> Err end. --spec get_session(sid()) -> {ok, #session{}} | {error, notfound}. -get_session(SID) -> +-spec get_session(binary(), sid()) -> {ok, #session{}} | {error, notfound}. +get_session(_LServer, SID) -> case mnesia:dirty_read(session, SID) of [] -> {error, notfound}; @@ -57,8 +57,8 @@ get_session(SID) -> set_session(Session) -> mnesia:dirty_write(Session). --spec delete_session(sid()) -> ok. -delete_session(SID) -> +-spec delete_session(binary(), sid()) -> ok. +delete_session(_LServer, SID) -> mnesia:dirty_delete(session, SID). -spec get_sessions() -> [#session{}]. diff --git a/src/ejabberd_sm_odbc.erl b/src/ejabberd_sm_odbc.erl new file mode 100644 index 000000000..ef24fa557 --- /dev/null +++ b/src/ejabberd_sm_odbc.erl @@ -0,0 +1,179 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2015, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 9 Mar 2015 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(ejabberd_sm_odbc). + +-behaviour(ejabberd_sm). + +%% API +-export([init/0, + get_session/2, + set_session/1, + delete_session/2, + get_sessions/0, + get_sessions/1, + get_sessions/2, + get_sessions/3]). + +-include("ejabberd.hrl"). +-include("ejabberd_sm.hrl"). +-include("logger.hrl"). +-include("jlib.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +-spec init() -> ok | {error, any()}. +init() -> + Node = ejabberd_odbc:escape(erlang:atom_to_binary(node(), utf8)), + lists:foldl( + fun(Host, ok) -> + case ejabberd_odbc:sql_query( + Host, [<<"delete from sm where node='">>, Node, <<"'">>]) of + {updated, _} -> + ok; + Err -> + ?ERROR_MSG("failed to clean 'sm' table: ~p", [Err]), + Err + end; + (_, Err) -> + Err + end, ok, ?MYHOSTS). + +-spec get_session(binary(), sid()) -> {ok, #session{}} | {error, notfound}. +get_session(LServer, {Now, Pid} = SID) -> + Host = ejabberd_odbc:escape(LServer), + PidS = list_to_binary(erlang:pid_to_list(Pid)), + TS = now_to_timestamp(Now), + case ejabberd_odbc:sql_query( + Host, [<<"select username, resource, priority, info from sm ">>, + <<"where usec='">>, TS, <<"' and pid='">>, PidS, <<"'">>]) of + {selected, _, [[User, Resource, Priority, Info]|_]} -> + {ok, #session{sid = SID, us = {User, Resource}, + usr = {User, Resource, LServer}, + priority = dec_priority(Priority), + info = ejabberd_odbc:decode_term(Info)}}; + {selected, _, []} -> + {error, notfound} + end. + +set_session(#session{sid = {Now, Pid}, usr = {U, LServer, R}, + priority = Priority, info = Info}) -> + Username = ejabberd_odbc:escape(U), + Resource = ejabberd_odbc:escape(R), + InfoS = ejabberd_odbc:encode_term(Info), + PrioS = enc_priority(Priority), + TS = now_to_timestamp(Now), + PidS = list_to_binary(erlang:pid_to_list(Pid)), + Node = ejabberd_odbc:escape(erlang:atom_to_binary(node(Pid), utf8)), + case odbc_queries:update( + LServer, + <<"sm">>, + [<<"usec">>, <<"pid">>, <<"node">>, <<"username">>, + <<"resource">>, <<"priority">>, <<"info">>], + [TS, PidS, Node, Username, Resource, PrioS, InfoS], + [<<"usec='">>, TS, <<"' and pid='">>, PidS, <<"'">>]) of + ok -> + ok; + Err -> + ?ERROR_MSG("failed to update 'sm' table: ~p", [Err]) + end. + +delete_session(LServer, {Now, Pid}) -> + TS = now_to_timestamp(Now), + PidS = list_to_binary(erlang:pid_to_list(Pid)), + case ejabberd_odbc:sql_query( + LServer, [<<"delete from sm where usec='">>, + TS, <<"' and pid='">>, PidS, <<"'">>]) of + {updated, _} -> + ok; + Err -> + ?ERROR_MSG("failed to delete from 'sm' table: ~p", [Err]) + end. + +get_sessions() -> + lists:flatmap( + fun(LServer) -> + get_sessions(LServer) + end, ?MYHOSTS). + +get_sessions(LServer) -> + case ejabberd_odbc:sql_query( + LServer, [<<"select usec, pid, username, ">>, + <<"resource, priority, info from sm">>]) of + {selected, _, Rows} -> + [row_to_session(LServer, Row) || Row <- Rows]; + Err -> + ?ERROR_MSG("failed to select from 'sm' table: ~p", [Err]), + [] + end. + +get_sessions(LUser, LServer) -> + Username = ejabberd_odbc:escape(LUser), + case ejabberd_odbc:sql_query( + LServer, [<<"select usec, pid, username, ">>, + <<"resource, priority, info from sm where ">>, + <<"username='">>, Username, <<"'">>]) of + {selected, _, Rows} -> + [row_to_session(LServer, Row) || Row <- Rows]; + Err -> + ?ERROR_MSG("failed to select from 'sm' table: ~p", [Err]), + [] + end. + +get_sessions(LUser, LServer, LResource) -> + Username = ejabberd_odbc:escape(LUser), + Resource = ejabberd_odbc:escape(LResource), + case ejabberd_odbc:sql_query( + LServer, [<<"select usec, pid, username, ">>, + <<"resource, priority, info from sm where ">>, + <<"username='">>, Username, <<"' and resource='">>, + Resource, <<"'">>]) of + {selected, _, Rows} -> + [row_to_session(LServer, Row) || Row <- Rows]; + Err -> + ?ERROR_MSG("failed to select from 'sm' table: ~p", [Err]), + [] + end. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +now_to_timestamp({MSec, Sec, USec}) -> + erlang:integer_to_binary((MSec * 1000000 + Sec) * 1000000 + USec). + +timestamp_to_now(TS) -> + I = erlang:binary_to_integer(TS), + Head = I div 1000000, + USec = I rem 1000000, + MSec = Head div 1000000, + Sec = Head div 1000000, + {MSec, Sec, USec}. + +dec_priority(Prio) -> + case catch erlang:binary_to_integer(Prio) of + {'EXIT', _} -> + undefined; + Int -> + Int + end. + +enc_priority(undefined) -> + <<"">>; +enc_priority(Int) when is_integer(Int) -> + erlang:integer_to_binary(Int). + +row_to_session(LServer, [USec, PidS, User, Resource, PrioS, InfoS]) -> + Now = timestamp_to_now(USec), + Pid = erlang:list_to_pid(binary_to_list(PidS)), + Priority = dec_priority(PrioS), + Info = ejabberd_odbc:decode_term(InfoS), + #session{sid = {Now, Pid}, us = {User, LServer}, + usr = {User, LServer, Resource}, + priority = Priority, + info = Info}. diff --git a/src/ejabberd_sup.erl b/src/ejabberd_sup.erl index 35c79f429..ecedfa502 100644 --- a/src/ejabberd_sup.erl +++ b/src/ejabberd_sup.erl @@ -62,13 +62,6 @@ init([]) -> brutal_kill, worker, [ejabberd_router]}, - SM = - {ejabberd_sm, - {ejabberd_sm, start_link, []}, - permanent, - brutal_kill, - worker, - [ejabberd_sm]}, S2S = {ejabberd_s2s, {ejabberd_s2s, start_link, []}, @@ -173,7 +166,6 @@ init([]) -> NodeGroups, SystemMonitor, Router, - SM, S2S, Local, Captcha, diff --git a/src/odbc_queries.erl b/src/odbc_queries.erl index 1fa16b896..f2771e52f 100644 --- a/src/odbc_queries.erl +++ b/src/odbc_queries.erl @@ -27,7 +27,7 @@ -author("mremond@process-one.net"). --export([get_db_type/0, update_t/4, sql_transaction/2, +-export([get_db_type/0, update/5, update_t/4, sql_transaction/2, get_last/2, set_last_t/4, del_last/2, get_password/2, set_password_t/3, add_user/3, del_user/2, del_user_return_password/3, list_users/1, list_users/2,