25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-24 16:23:40 +01:00

Merged the Process One contributions ( Virtual Hosting )

SVN Revision: 307
This commit is contained in:
tmallard 2005-04-17 18:08:34 +00:00
parent 13de45118d
commit 374446f847
46 changed files with 6472 additions and 1305 deletions

View File

@ -13,17 +13,19 @@
-export([start/0,
register_mechanism/2,
listmech/0,
server_new/4,
server_new/6,
server_start/3,
server_step/2]).
-record(sasl_mechanism, {mechanism, module}).
-record(sasl_state, {service, myname, realm, mech_mod, mech_state}).
-record(sasl_state, {service, myname, realm,
get_password, check_password,
mech_mod, mech_state}).
-export([behaviour_info/1]).
behaviour_info(callbacks) ->
[{mech_new, 0},
[{mech_new, 2},
{mech_step, 2}];
behaviour_info(Other) ->
undefined.
@ -80,15 +82,19 @@ listmech() ->
[{#sasl_mechanism{mechanism = '$1', _ = '_'}, [], ['$1']}]).
server_new(Service, ServerFQDN, UserRealm, SecFlags) ->
server_new(Service, ServerFQDN, UserRealm, SecFlags,
GetPassword, CheckPassword) ->
#sasl_state{service = Service,
myname = ServerFQDN,
realm = UserRealm}.
realm = UserRealm,
get_password = GetPassword,
check_password = CheckPassword}.
server_start(State, Mech, ClientIn) ->
case ets:lookup(sasl_mechanism, Mech) of
[#sasl_mechanism{module = Module}] ->
{ok, MechState} = Module:mech_new(),
{ok, MechState} = Module:mech_new(State#sasl_state.get_password,
State#sasl_state.check_password),
server_step(State#sasl_state{mech_mod = Module,
mech_state = MechState},
ClientIn);

View File

@ -12,15 +12,14 @@
-export([start/1,
stop/0,
mech_new/0,
mech_new/2,
mech_step/2]).
-behaviour(cyrsasl).
%-behaviour(gen_mod).
-record(state, {step, nonce, username, authzid}).
-record(state, {step, nonce, username, authzid, get_password}).
start(Opts) ->
start(_Opts) ->
case ejabberd_auth:plain_password_required() of
true ->
ok;
@ -32,15 +31,15 @@ start(Opts) ->
stop() ->
ok.
mech_new() ->
mech_new(GetPassword, _CheckPassword) ->
{ok, #state{step = 1,
nonce = randoms:get_string()}}.
nonce = randoms:get_string(),
get_password = GetPassword}}.
mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
{continue,
"nonce=\"" ++ Nonce ++
"\",qop=\"auth\",charset=utf-8,algorithm=md5-sess",
%"\",qop=\"auth,auth-int\",charset=utf-8,algorithm=md5-sess",
State#state{step = 3}};
mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
case parse(ClientIn) of
@ -49,7 +48,7 @@ mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
KeyVals ->
UserName = xml:get_attr_s("username", KeyVals),
AuthzId = xml:get_attr_s("authzid", KeyVals),
case ejabberd_auth:get_password(UserName) of
case (State#state.get_password)(UserName) of
false ->
{error, "not-authorized"};
Passwd ->
@ -72,7 +71,7 @@ mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
end;
mech_step(#state{step = 5,
username = UserName,
authzid = AuthzId} = State, "") ->
authzid = AuthzId}, "") ->
{ok, [{username, UserName}, {authzid, AuthzId}]};
mech_step(A, B) ->
io:format("SASL DIGEST: A ~p B ~p", [A,B]),
@ -88,7 +87,7 @@ parse1([C | Cs], S, Ts) ->
parse1(Cs, [C | S], Ts);
parse1([], [], T) ->
lists:reverse(T);
parse1([], S, T) ->
parse1([], _S, _T) ->
bad.
parse2([$" | Cs], Key, Val, Ts) ->
@ -148,11 +147,11 @@ response(KeyVals, User, Passwd, Nonce, AuthzId, A2Prefix) ->
crypto:md5(User ++ ":" ++ Realm ++ ":" ++ Passwd)) ++
":" ++ Nonce ++ ":" ++ CNonce ++ ":" ++ AuthzId
end,
case QOP of
A2 = case QOP of
"auth" ->
A2 = A2Prefix ++ ":" ++ DigestURI;
A2Prefix ++ ":" ++ DigestURI;
_ ->
A2 = A2Prefix ++ ":" ++ DigestURI ++
A2Prefix ++ ":" ++ DigestURI ++
":00000000000000000000000000000000"
end,
T = hex(binary_to_list(crypto:md5(A1))) ++ ":" ++ Nonce ++ ":" ++

View File

@ -10,25 +10,26 @@
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-export([start/1, stop/0, mech_new/0, mech_step/2, parse/1]).
-export([start/1, stop/0, mech_new/2, mech_step/2, parse/1]).
-behaviour(cyrsasl).
%-behaviour(gen_mod).
start(Opts) ->
-record(state, {check_password}).
start(_Opts) ->
cyrsasl:register_mechanism("PLAIN", ?MODULE),
ok.
stop() ->
ok.
mech_new() ->
{ok, []}.
mech_new(_GetPassword, CheckPassword) ->
{ok, #state{check_password = CheckPassword}}.
mech_step(State, ClientIn) ->
case parse(ClientIn) of
[AuthzId, User, Password] ->
case ejabberd_auth:check_password(User, Password) of
case (State#state.check_password)(User, Password) of
true ->
{ok, [{username, User}, {authzid, AuthzId}]};
_ ->

View File

@ -27,10 +27,9 @@
[self(),?MODULE,?LINE]++Args)).
%-define(MYNAME,"e.localhost").
-define(MYNAME, ejabberd_config:get_global_option(host)).
-define(MYHOSTS, ejabberd_config:get_global_option(hosts)).
-define(MYNAME, hd(ejabberd_config:get_global_option(hosts))).
-define(S2STIMEOUT, 600000).
%-define(S2STIMEOUT, 6000).
-define(MYLANG, ejabberd_config:get_global_option(language)).
-define(MSGS_DIR, "msgs").

View File

@ -12,16 +12,17 @@
%% External exports
-export([start/0,
set_password/2,
check_password/2,
check_password/4,
try_register/2,
set_password/3,
check_password/3,
check_password/5,
try_register/3,
dirty_get_registered_users/0,
get_password/1,
get_password_s/1,
is_user_exists/1,
remove_user/1,
get_vh_registered_users/1,
get_password/2,
get_password_s/2,
is_user_exists/2,
remove_user/2,
remove_user/3,
plain_password_required/0
]).
@ -34,35 +35,38 @@ start() ->
plain_password_required() ->
(auth_module()):plain_password_required().
check_password(User, Password) ->
(auth_module()):check_password(User, Password).
check_password(User, Server, Password) ->
(auth_module()):check_password(User, Server, Password).
check_password(User, Password, StreamID, Digest) ->
(auth_module()):check_password(User, Password, StreamID, Digest).
check_password(User, Server, Password, StreamID, Digest) ->
(auth_module()):check_password(User, Server, Password, StreamID, Digest).
set_password(User, Password) ->
(auth_module()):set_password(User, Password).
set_password(User, Server, Password) ->
(auth_module()):set_password(User, Server, Password).
try_register(User, Password) ->
(auth_module()):try_register(User, Password).
try_register(User, Server, Password) ->
(auth_module()):try_register(User, Server, Password).
dirty_get_registered_users() ->
(auth_module()):dirty_get_registered_users().
get_password(User) ->
(auth_module()):get_password(User).
get_vh_registered_users(Server) ->
(auth_module()):get_vh_registered_users(Server).
get_password_s(User) ->
(auth_module()):get_password_s(User).
get_password(User, Server) ->
(auth_module()):get_password(User, Server).
is_user_exists(User) ->
(auth_module()):is_user_exists(User).
get_password_s(User, Server) ->
(auth_module()):get_password_s(User, Server).
remove_user(User) ->
(auth_module()):remove_user(User).
is_user_exists(User, Server) ->
(auth_module()):is_user_exists(User, Server).
remove_user(User, Password) ->
(auth_module()):remove_user(User, Password).
remove_user(User, Server) ->
(auth_module()):remove_user(User, Server).
remove_user(User, Server, Password) ->
(auth_module()):remove_user(User, Server, Password).
%%%----------------------------------------------------------------------
%%% Internal functions

View File

@ -12,16 +12,17 @@
%% External exports
-export([start/0,
set_password/2,
check_password/2,
check_password/4,
try_register/2,
set_password/3,
check_password/3,
check_password/5,
try_register/3,
dirty_get_registered_users/0,
get_password/1,
get_password_s/1,
is_user_exists/1,
remove_user/1,
get_vh_registered_users/1,
get_password/2,
get_password_s/2,
is_user_exists/2,
remove_user/2,
remove_user/3,
plain_password_required/0
]).
@ -35,33 +36,36 @@ start() ->
plain_password_required() ->
true.
check_password(User, Password) ->
check_password(User, _Server, Password) ->
extauth:check_password(User, Password).
check_password(User, Password, _StreamID, _Digest) ->
check_password(User, Password).
check_password(User, Server, Password, _StreamID, _Digest) ->
check_password(User, Server, Password).
set_password(User, Password) ->
set_password(User, _Server, Password) ->
extauth:set_password(User, Password).
try_register(_User, _Password) ->
try_register(_User, _Server, _Password) ->
{error, not_allowed}.
dirty_get_registered_users() ->
[].
get_password(_User) ->
get_vh_registered_users(_Server) ->
[].
get_password(_User, _Server) ->
false.
get_password_s(_User) ->
get_password_s(_User, _Server) ->
"".
is_user_exists(User) ->
is_user_exists(User, _Server) ->
extauth:is_user_exists(User).
remove_user(_User) ->
remove_user(_User, _Server) ->
{error, not_allowed}.
remove_user(_User, _Password) ->
remove_user(_User, _Server, _Password) ->
not_allowed.

View File

@ -12,20 +12,23 @@
%% External exports
-export([start/0,
set_password/2,
check_password/2,
check_password/4,
try_register/2,
set_password/3,
check_password/3,
check_password/5,
try_register/3,
dirty_get_registered_users/0,
get_password/1,
get_password_s/1,
is_user_exists/1,
remove_user/1,
get_vh_registered_users/1,
get_password/2,
get_password_s/2,
is_user_exists/2,
remove_user/2,
remove_user/3,
plain_password_required/0
]).
-record(passwd, {user, password}).
-include("ejabberd.hrl").
-record(passwd, {us, password}).
%%%----------------------------------------------------------------------
%%% API
@ -33,23 +36,28 @@
start() ->
mnesia:create_table(passwd, [{disc_copies, [node()]},
{attributes, record_info(fields, passwd)}]),
update_table(),
ok.
plain_password_required() ->
false.
check_password(User, Password) ->
check_password(User, Server, Password) ->
LUser = jlib:nodeprep(User),
case catch mnesia:dirty_read({passwd, LUser}) of
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[#passwd{password = Password}] ->
true;
_ ->
false
end.
check_password(User, Password, StreamID, Digest) ->
check_password(User, Server, Password, StreamID, Digest) ->
LUser = jlib:nodeprep(User),
case catch mnesia:dirty_read({passwd, LUser}) of
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[#passwd{password = Passwd}] ->
DigRes = if
Digest /= "" ->
@ -66,26 +74,34 @@ check_password(User, Password, StreamID, Digest) ->
false
end.
set_password(User, Password) ->
case jlib:nodeprep(User) of
error -> {error, invalid_jid};
LUser ->
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() ->
mnesia:write(#passwd{user = LUser,
mnesia:write(#passwd{us = US,
password = Password})
end,
mnesia:transaction(F)
end.
try_register(User, Password) ->
case jlib:nodeprep(User) of
error -> {error, invalid_jid};
LUser ->
try_register(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() ->
case mnesia:read({passwd, LUser}) of
case mnesia:read({passwd, US}) of
[] ->
mnesia:write(#passwd{user = LUser,
mnesia:write(#passwd{us = US,
password = Password}),
ok;
[_E] ->
@ -98,27 +114,41 @@ try_register(User, Password) ->
dirty_get_registered_users() ->
mnesia:dirty_all_keys(passwd).
get_password(User) ->
get_vh_registered_users(Server) ->
LServer = jlib:nameprep(Server),
mnesia:dirty_select(
passwd,
[{#passwd{us = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, LServer}],
['$1']}]).
get_password(User, Server) ->
LUser = jlib:nodeprep(User),
case catch mnesia:dirty_read(passwd, LUser) of
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read(passwd, US) of
[#passwd{password = Password}] ->
Password;
_ ->
false
end.
get_password_s(User) ->
get_password_s(User, Server) ->
LUser = jlib:nodeprep(User),
case catch mnesia:dirty_read(passwd, LUser) of
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read(passwd, US) of
[#passwd{password = Password}] ->
Password;
_ ->
[]
end.
is_user_exists(User) ->
is_user_exists(User, Server) ->
LUser = jlib:nodeprep(User),
case catch mnesia:dirty_read({passwd, LUser}) of
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[] ->
false;
[_] ->
@ -127,20 +157,24 @@ is_user_exists(User) ->
false
end.
remove_user(User) ->
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
mnesia:delete({passwd, LUser})
mnesia:delete({passwd, US})
end,
mnesia:transaction(F),
ejabberd_hooks:run(remove_user, [User]).
ejabberd_hooks:run(remove_user, [User, Server]).
remove_user(User, Password) ->
remove_user(User, Server, Password) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
case mnesia:read({passwd, LUser}) of
case mnesia:read({passwd, US}) of
[#passwd{password = Password}] ->
mnesia:delete({passwd, LUser}),
mnesia:delete({passwd, US}),
ok;
[_] ->
not_allowed;
@ -150,10 +184,56 @@ remove_user(User, Password) ->
end,
case mnesia:transaction(F) of
{atomic, ok} ->
ejabberd_hooks:run(remove_user, [User]),
ejabberd_hooks:run(remove_user, [User, Server]),
ok;
{atomic, Res} ->
Res;
_ ->
bad_request
end.
update_table() ->
Fields = record_info(fields, passwd),
case mnesia:table_info(passwd, attributes) of
Fields ->
ok;
[user, password] ->
?INFO_MSG("Converting passwd table from "
"{user, password} format", []),
Host = ?MYNAME,
{atomic, ok} = mnesia:create_table(
ejabberd_auth_internal_tmp_table,
[{disc_only_copies, [node()]},
{type, bag},
{local_content, true},
{record_name, passwd},
{attributes, record_info(fields, passwd)}]),
mnesia:transform_table(passwd, ignore, Fields),
F1 = fun() ->
mnesia:write_lock_table(ejabberd_auth_internal_tmp_table),
mnesia:foldl(
fun(#passwd{us = U} = R, _) ->
mnesia:dirty_write(
ejabberd_auth_internal_tmp_table,
R#passwd{us = {U, Host}})
end, ok, passwd)
end,
mnesia:transaction(F1),
mnesia:clear_table(passwd),
F2 = fun() ->
mnesia:write_lock_table(passwd),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, ejabberd_auth_internal_tmp_table)
end,
mnesia:transaction(F2),
mnesia:delete_table(ejabberd_auth_internal_tmp_table);
_ ->
?INFO_MSG("Recreating passwd table", []),
mnesia:transform_table(passwd, ignore, Fields)
end.

View File

@ -12,16 +12,17 @@
%% External exports
-export([start/0,
set_password/2,
check_password/2,
check_password/4,
try_register/2,
set_password/3,
check_password/3,
check_password/5,
try_register/3,
dirty_get_registered_users/0,
get_password/1,
get_password_s/1,
is_user_exists/1,
remove_user/1,
get_vh_registered_users/1,
get_password/2,
get_password_s/2,
is_user_exists/2,
remove_user/2,
remove_user/3,
plain_password_required/0
]).
@ -41,7 +42,7 @@ start() ->
plain_password_required() ->
true.
check_password(User, Password) ->
check_password(User, _Server, Password) ->
case find_user_dn(User) of
false ->
false;
@ -54,25 +55,28 @@ check_password(User, Password) ->
end
end.
check_password(User, Password, _StreamID, _Digest) ->
check_password(User, Password).
check_password(User, Server, Password, _StreamID, _Digest) ->
check_password(User, Server, Password).
set_password(_User, _Password) ->
set_password(_User, _Server, _Password) ->
{error, not_allowed}.
try_register(_User, _Password) ->
try_register(_User, _Server, _Password) ->
{error, not_allowed}.
dirty_get_registered_users() ->
[].
get_password(_User) ->
get_vh_registered_users(_Server) ->
[].
get_password(_User, _Server) ->
false.
get_password_s(_User) ->
get_password_s(_User, _Server) ->
"".
is_user_exists(User) ->
is_user_exists(User, _Server) ->
case find_user_dn(User) of
false ->
false;
@ -80,10 +84,10 @@ is_user_exists(User) ->
true
end.
remove_user(_User) ->
remove_user(_User, _Server) ->
{error, not_allowed}.
remove_user(_User, _Password) ->
remove_user(_User, _Server, _Password) ->
not_allowed.

View File

@ -12,16 +12,16 @@
%% External exports
-export([start/0,
set_password/2,
check_password/2,
check_password/4,
try_register/2,
set_password/3,
check_password/3,
check_password/5,
try_register/3,
dirty_get_registered_users/0,
get_password/1,
get_password_s/1,
is_user_exists/1,
remove_user/1,
get_password/2,
get_password_s/2,
is_user_exists/2,
remove_user/2,
remove_user/3,
plain_password_required/0
]).
@ -36,7 +36,7 @@ start() ->
plain_password_required() ->
false.
check_password(User, Password) ->
check_password(User, _Server, Password) ->
case jlib:nodeprep(User) of
error ->
false;
@ -52,7 +52,7 @@ check_password(User, Password) ->
end
end.
check_password(User, Password, StreamID, Digest) ->
check_password(User, _Server, Password, StreamID, Digest) ->
case jlib:nodeprep(User) of
error ->
false;
@ -78,7 +78,7 @@ check_password(User, Password, StreamID, Digest) ->
end
end.
set_password(User, Password) ->
set_password(User, _Server, Password) ->
case jlib:nodeprep(User) of
error ->
{error, invalid_jid};
@ -93,7 +93,7 @@ set_password(User, Password) ->
end.
try_register(User, Password) ->
try_register(User, _Server, Password) ->
case jlib:nodeprep(User) of
error ->
{error, invalid_jid};
@ -118,7 +118,10 @@ dirty_get_registered_users() ->
[]
end.
get_password(User) ->
dirty_get_registered_users(Server) ->
dirty_get_registered_users().
get_password(User, _Server) ->
case jlib:nodeprep(User) of
error ->
false;
@ -134,7 +137,7 @@ get_password(User) ->
end
end.
get_password_s(User) ->
get_password_s(User, _Server) ->
case jlib:nodeprep(User) of
error ->
"";
@ -150,7 +153,7 @@ get_password_s(User) ->
end
end.
is_user_exists(User) ->
is_user_exists(User, _Server) ->
case jlib:nodeprep(User) of
error ->
false;
@ -166,7 +169,7 @@ is_user_exists(User) ->
end
end.
remove_user(User) ->
remove_user(User, _Server) ->
case jlib:nodeprep(User) of
error ->
error;
@ -177,7 +180,7 @@ remove_user(User) ->
ejabberd_hooks:run(remove_user, [User])
end.
remove_user(User, Password) ->
remove_user(User, _Server, Password) ->
case jlib:nodeprep(User) of
error ->
error;

View File

@ -83,6 +83,8 @@
xml:element_to_string(?SERR_INVALID_NAMESPACE)).
-define(INVALID_XML_ERR,
xml:element_to_string(?SERR_XML_NOT_WELL_FORMED)).
-define(HOST_UNKNOWN_ERR,
xml:element_to_string(?SERR_HOST_UNKNOWN)).
-define(POLICY_VIOLATION_ERR(Lang, Text),
xml:element_to_string(?SERRT_POLICY_VIOLATION(Lang, Text))).
@ -164,19 +166,31 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
end,
case xml:get_attr_s("xmlns:stream", Attrs) of
?NS_STREAM ->
Server = jlib:nameprep(xml:get_attr_s("to", Attrs)),
case lists:member(Server, ?MYHOSTS) of
true ->
Lang = xml:get_attr_s("xml:lang", Attrs),
case xml:get_attr_s("version", Attrs) of
"1.0" ->
Header = io_lib:format(?STREAM_HEADER,
[StateData#state.streamid,
?MYNAME,
Server,
" version='1.0'",
DefaultLang]),
send_text(StateData, Header),
case StateData#state.authentificated of
false ->
SASLState =
cyrsasl:server_new("jabber", ?MYNAME, "", []),
cyrsasl:server_new(
"jabber", Server, "", [],
fun(U) ->
ejabberd_auth:get_password(
U, Server)
end,
fun(U, P) ->
ejabberd_auth:check_password(
U, Server, P)
end),
Mechs = lists:map(
fun(S) ->
{xmlelement, "mechanism", [],
@ -214,7 +228,8 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
[{"xmlns", ?NS_FEATURE_IQREGISTER}],
[]}]}),
{next_state, wait_for_feature_request,
StateData#state{sasl_state = SASLState,
StateData#state{server = Server,
sasl_state = SASLState,
lang = Lang}};
_ ->
case StateData#state.resource of
@ -227,26 +242,38 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
{xmlelement, "session",
[{"xmlns", ?NS_SESSION}], []}]}),
{next_state, wait_for_bind,
StateData#state{lang = Lang}};
StateData#state{server = Server,
lang = Lang}};
_ ->
send_element(
StateData,
{xmlelement, "stream:features", [], []}),
{next_state, wait_for_session,
StateData#state{lang = Lang}}
StateData#state{server = Server,
lang = Lang}}
end
end;
_ ->
Header = io_lib:format(
?STREAM_HEADER,
[StateData#state.streamid, ?MYNAME, "", DefaultLang]),
[StateData#state.streamid, Server, "", DefaultLang]),
send_text(StateData, Header),
{next_state, wait_for_auth, StateData#state{lang = Lang}}
{next_state, wait_for_auth,
StateData#state{server = Server,
lang = Lang}}
end;
_ ->
Header = io_lib:format(
?STREAM_HEADER,
[StateData#state.streamid, ?MYNAME, "", ""]),
[StateData#state.streamid, ?MYNAME, "", DefaultLang]),
send_text(StateData,
Header ++ ?HOST_UNKNOWN_ERR ++ ?STREAM_TRAILER),
{stop, normal, StateData}
end;
_ ->
Header = io_lib:format(
?STREAM_HEADER,
[StateData#state.streamid, ?MYNAME, "", DefaultLang]),
send_text(StateData,
Header ++ ?INVALID_NS_ERR ++ ?STREAM_TRAILER),
{stop, normal, StateData}
@ -297,19 +324,20 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
send_element(StateData, Err),
{next_state, wait_for_auth, StateData};
{auth, _ID, set, {U, P, D, R}} ->
io:format("AUTH: ~p~n", [{U, P, D, R}]),
JID = jlib:make_jid(U, StateData#state.server, R),
case (JID /= error) andalso
(acl:match_rule(StateData#state.access, JID) == allow) of
true ->
case ejabberd_auth:check_password(
U, P, StateData#state.streamid, D) of
U, StateData#state.server, P,
StateData#state.streamid, D) of
true ->
?INFO_MSG(
"(~w) Accepted legacy authentication for ~s",
[StateData#state.socket,
jlib:jid_to_string(JID)]),
ejabberd_sm:open_session(U, R),
ejabberd_sm:open_session(
U, StateData#state.server, R),
Res1 = jlib:make_result_iq_reply(El),
Res = setelement(4, Res1, []),
send_element(StateData, Res),
@ -317,13 +345,14 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
{Fs, Ts} = ejabberd_hooks:run_fold(
roster_get_subscription_lists,
{[], []},
[U]),
[U, StateData#state.server]),
LJID = jlib:jid_tolower(
jlib:jid_remove_resource(JID)),
Fs1 = [LJID | Fs],
Ts1 = [LJID | Ts],
PrivList =
case catch mod_privacy:get_user_list(U) of
case catch mod_privacy:get_user_list(
U, StateData#state.server) of
{'EXIT', _} -> none;
PL -> PL
end,
@ -368,8 +397,10 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
case jlib:iq_query_info(El) of
#iq{xmlns = ?NS_REGISTER} = IQ ->
ResIQ = mod_register:process_iq(
{"", "", ""}, {"", ?MYNAME, ""}, IQ),
Res1 = jlib:replace_from_to({"", ?MYNAME, ""},
{"", "", ""},
jlib:make_jid("", StateData#state.server, ""),
IQ),
Res1 = jlib:replace_from_to({"", StateData#state.server, ""},
{"", "", ""},
jlib:iq_to_xml(ResIQ)),
Res = jlib:remove_attr("to", Res1),
@ -414,8 +445,7 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) ->
?INFO_MSG("(~w) Accepted authentication for ~s",
[StateData#state.socket, U]),
{next_state, wait_for_stream,
StateData#state{streamid = new_id(),
authentificated = true,
StateData#state{authentificated = true,
user = U
}};
{continue, ServerOut, NewSASLState} ->
@ -445,7 +475,6 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) ->
{next_state, wait_for_stream,
StateData#state{sockmod = tls,
socket = TLSSocket,
streamid = new_id(),
tls_enabled = true
}};
_ ->
@ -461,8 +490,10 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) ->
case jlib:iq_query_info(El) of
#iq{xmlns = ?NS_REGISTER} = IQ ->
ResIQ = mod_register:process_iq(
{"", "", ""}, {"", ?MYNAME, ""}, IQ),
Res1 = jlib:replace_from_to({"", ?MYNAME, ""},
{"", "", ""},
jlib:make_jid("", StateData#state.server, ""),
IQ),
Res1 = jlib:replace_from_to({"", StateData#state.server, ""},
{"", "", ""},
jlib:iq_to_xml(ResIQ)),
Res = jlib:remove_attr("to", Res1),
@ -502,8 +533,7 @@ wait_for_sasl_response({xmlstreamelement, El}, StateData) ->
?INFO_MSG("(~w) Accepted authentication for ~s",
[StateData#state.socket, U]),
{next_state, wait_for_stream,
StateData#state{streamid = new_id(),
authentificated = true,
StateData#state{authentificated = true,
user = U
}};
{continue, ServerOut, NewSASLState} ->
@ -525,8 +555,10 @@ wait_for_sasl_response({xmlstreamelement, El}, StateData) ->
case jlib:iq_query_info(El) of
#iq{xmlns = ?NS_REGISTER} = IQ ->
ResIQ = mod_register:process_iq(
{"", "", ""}, {"", ?MYNAME, ""}, IQ),
Res1 = jlib:replace_from_to({"", ?MYNAME, ""},
{"", "", ""},
jlib:make_jid("", StateData#state.server, ""),
IQ),
Res1 = jlib:replace_from_to({"", StateData#state.server, ""},
{"", "", ""},
jlib:iq_to_xml(ResIQ)),
Res = jlib:remove_attr("to", Res1),
@ -601,26 +633,27 @@ wait_for_session({xmlstreamelement, El}, StateData) ->
#iq{type = set, xmlns = ?NS_SESSION} ->
U = StateData#state.user,
R = StateData#state.resource,
io:format("SASLAUTH: ~p~n", [{U, R}]),
JID = StateData#state.jid,
case acl:match_rule(StateData#state.access, JID) of
allow ->
?INFO_MSG("(~w) Opened session for ~s",
[StateData#state.socket,
jlib:jid_to_string(JID)]),
ejabberd_sm:open_session(U, R),
ejabberd_sm:open_session(
U, StateData#state.server, R),
Res = jlib:make_result_iq_reply(El),
send_element(StateData, Res),
change_shaper(StateData, JID),
{Fs, Ts} = ejabberd_hooks:run_fold(
roster_get_subscription_lists,
{[], []},
[U]),
[U, StateData#state.server]),
LJID = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
Fs1 = [LJID | Fs],
Ts1 = [LJID | Ts],
PrivList =
case catch mod_privacy:get_user_list(U) of
case catch mod_privacy:get_user_list(
U, StateData#state.server) of
{'EXIT', _} -> none;
PL -> PL
end,
@ -835,6 +868,7 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
%-ifdef(PRIVACY_SUPPORT).
case catch mod_privacy:check_packet(
StateData#state.user,
StateData#state.server,
StateData#state.privacy_list,
{From, To, Packet},
in) of
@ -884,6 +918,7 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
#iq{} ->
case catch mod_privacy:check_packet(
StateData#state.user,
StateData#state.server,
StateData#state.privacy_list,
{From, To, Packet},
in) of
@ -905,6 +940,7 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
"message" ->
case catch mod_privacy:check_packet(
StateData#state.user,
StateData#state.server,
StateData#state.privacy_list,
{From, To, Packet},
in) of
@ -955,6 +991,7 @@ terminate(_Reason, StateName, StateData) ->
[{xmlelement, "status", [],
[{xmlcdata, "Replaced by new connection"}]}]},
ejabberd_sm:unset_presence(StateData#state.user,
StateData#state.server,
StateData#state.resource,
"Replaced by new connection"),
presence_broadcast(
@ -966,6 +1003,7 @@ terminate(_Reason, StateName, StateData) ->
[StateData#state.socket,
jlib:jid_to_string(StateData#state.jid)]),
ejabberd_sm:close_session(StateData#state.user,
StateData#state.server,
StateData#state.resource),
Tmp = ?SETS:new(),
@ -980,6 +1018,7 @@ terminate(_Reason, StateName, StateData) ->
Packet = {xmlelement, "presence",
[{"type", "unavailable"}], []},
ejabberd_sm:unset_presence(StateData#state.user,
StateData#state.server,
StateData#state.resource,
""),
presence_broadcast(
@ -1070,6 +1109,7 @@ process_presence_probe(From, To, StateData) ->
%-ifdef(PRIVACY_SUPPORT).
case catch mod_privacy:check_packet(
StateData#state.user,
StateData#state.server,
StateData#state.privacy_list,
{To, From, Packet},
out) of
@ -1102,6 +1142,7 @@ presence_update(From, Packet, StateData) ->
xml:get_tag_cdata(StatusTag)
end,
ejabberd_sm:unset_presence(StateData#state.user,
StateData#state.server,
StateData#state.resource,
Status),
presence_broadcast(StateData, From, StateData#state.pres_a, Packet),
@ -1173,6 +1214,7 @@ presence_track(From, To, Packet, StateData) ->
{xmlelement, _Name, Attrs, _Els} = Packet,
LTo = jlib:jid_tolower(To),
User = StateData#state.user,
Server = StateData#state.server,
case xml:get_attr_s("type", Attrs) of
"unavailable" ->
ejabberd_router:route(From, To, Packet),
@ -1189,22 +1231,22 @@ presence_track(From, To, Packet, StateData) ->
"subscribe" ->
ejabberd_router:route(jlib:jid_remove_resource(From), To, Packet),
ejabberd_hooks:run(roster_out_subscription,
[User, To, subscribe]),
[User, Server, To, subscribe]),
StateData;
"subscribed" ->
ejabberd_router:route(jlib:jid_remove_resource(From), To, Packet),
ejabberd_hooks:run(roster_out_subscription,
[User, To, subscribed]),
[User, Server, To, subscribed]),
StateData;
"unsubscribe" ->
ejabberd_router:route(jlib:jid_remove_resource(From), To, Packet),
ejabberd_hooks:run(roster_out_subscription,
[User, To, unsubscribe]),
[User, Server, To, unsubscribe]),
StateData;
"unsubscribed" ->
ejabberd_router:route(jlib:jid_remove_resource(From), To, Packet),
ejabberd_hooks:run(roster_out_subscription,
[User, To, unsubscribed]),
[User, Server, To, unsubscribed]),
StateData;
"error" ->
ejabberd_router:route(From, To, Packet),
@ -1216,6 +1258,7 @@ presence_track(From, To, Packet, StateData) ->
%-ifdef(PRIVACY_SUPPORT).
case catch mod_privacy:check_packet(
StateData#state.user,
StateData#state.server,
StateData#state.privacy_list,
{From, To, Packet},
out) of
@ -1239,6 +1282,7 @@ presence_broadcast(StateData, From, JIDSet, Packet) ->
%-ifdef(PRIVACY_SUPPORT).
case catch mod_privacy:check_packet(
StateData#state.user,
StateData#state.server,
StateData#state.privacy_list,
{From, FJID, Packet},
out) of
@ -1261,6 +1305,7 @@ presence_broadcast_to_trusted(StateData, From, T, A, Packet) ->
%-ifdef(PRIVACY_SUPPORT).
case catch mod_privacy:check_packet(
StateData#state.user,
StateData#state.server,
StateData#state.privacy_list,
{From, FJID, Packet},
out) of
@ -1300,6 +1345,7 @@ presence_broadcast_first(From, StateData, Packet) ->
%-ifdef(PRIVACY_SUPPORT).
case catch mod_privacy:check_packet(
StateData#state.user,
StateData#state.server,
StateData#state.privacy_list,
{From, FJID, Packet},
out) of
@ -1395,6 +1441,7 @@ update_priority(El, StateData) ->
end
end,
ejabberd_sm:set_presence(StateData#state.user,
StateData#state.server,
StateData#state.resource,
Pri).
@ -1439,15 +1486,17 @@ process_privacy_iq(From, To,
resend_offline_messages(#state{user = User,
server = Server,
privacy_list = PrivList} = StateData) ->
case ejabberd_hooks:run_fold(resend_offline_messages_hook, [],
[User]) of
[User, Server]) of
Rs when list(Rs) ->
lists:foreach(
fun({route,
From, To, {xmlelement, Name, Attrs, Els} = Packet}) ->
Pass = case catch mod_privacy:check_packet(
User,
Server,
PrivList,
{From, To, Packet},
in) of

View File

@ -78,13 +78,15 @@ process_term(Term, State) ->
State#state{opts = [#config{key = {shaper, Name},
value = Data} |
State#state.opts]};
{host, Host} ->
add_option(hosts, [Host], State);
{Opt, Val} ->
add_option(Opt, Val, State)
end.
add_option(Opt, Val, State) ->
Table = case Opt of
host ->
hosts ->
config;
language ->
config;

View File

@ -147,7 +147,7 @@ process(Node, ["load", Path]) ->
process(Node, ["restore", Path]) ->
case rpc:call(Node,
mnesia, restore, [Path, [{default_op, keep_tables}]]) of
{atomic, _} ->
{atomic, ok} ->
?STATUS_SUCCESS;
{error, Reason} ->
io:format("Can't restore backup from ~p on node ~p: ~p~n",

View File

@ -31,7 +31,10 @@ start_link() ->
{ok, Pid}.
init() ->
ejabberd_router:register_route(?MYNAME, {apply, ?MODULE, route}),
lists:foreach(
fun(Host) ->
ejabberd_router:register_route(Host, {apply, ?MODULE, route})
end, ?MYHOSTS),
catch ets:new(?IQTABLE, [named_table, public]),
ejabberd_hooks:add(local_send_to_resource_hook,
?MODULE, bounce_resource_packet, 100),

View File

@ -13,7 +13,9 @@
-export([route/3,
register_route/1,
register_route/2,
register_routes/1,
unregister_route/1,
unregister_routes/1,
dirty_get_all_routes/0,
dirty_get_all_domains/0
]).
@ -130,33 +132,58 @@ route(From, To, Packet) ->
end.
register_route(Domain) ->
case jlib:nameprep(Domain) of
error ->
[] = {invalid_domain, Domain};
LDomain ->
Pid = self(),
F = fun() ->
mnesia:write(#route{domain = Domain,
mnesia:write(#route{domain = LDomain,
pid = Pid})
end,
mnesia:transaction(F).
mnesia:transaction(F)
end.
register_route(Domain, LocalHint) ->
case jlib:nameprep(Domain) of
error ->
[] = {invalid_domain, Domain};
LDomain ->
Pid = self(),
F = fun() ->
mnesia:write(#route{domain = Domain,
mnesia:write(#route{domain = LDomain,
pid = Pid,
local_hint = LocalHint})
end,
mnesia:transaction(F).
mnesia:transaction(F)
end.
register_routes(Domains) ->
lists:foreach(fun(Domain) ->
register_route(Domain)
end, Domains).
unregister_route(Domain) ->
case jlib:nameprep(Domain) of
error ->
[] = {invalid_domain, Domain};
LDomain ->
Pid = self(),
F = fun() ->
mnesia:delete_object(#route{domain = Domain,
mnesia:delete_object(#route{domain = LDomain,
pid = Pid})
end,
mnesia:transaction(F).
mnesia:transaction(F)
end.
unregister_routes(Domains) ->
lists:foreach(fun(Domain) ->
unregister_route(Domain)
end, Domains).
dirty_get_all_routes() ->
lists:delete(?MYNAME, lists:usort(mnesia:dirty_all_keys(route))).
lists:usort(mnesia:dirty_all_keys(route)) -- ?MYHOSTS.
dirty_get_all_domains() ->
lists:usort(mnesia:dirty_all_keys(route)).

View File

@ -12,13 +12,15 @@
-export([start_link/0, init/0,
route/3,
open_session/2, close_session/2,
open_session/3, close_session/3,
bounce_offline_message/3,
get_user_resources/1,
set_presence/3,
unset_presence/3,
disconnect_removed_user/2,
get_user_resources/2,
set_presence/4,
unset_presence/4,
dirty_get_sessions_list/0,
dirty_get_my_sessions_list/0,
get_vh_session_list/1,
register_iq_handler/3,
register_iq_handler/4,
unregister_iq_handler/1
@ -27,8 +29,8 @@
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(session, {ur, user, pid}).
-record(presence, {ur, user, priority}).
-record(session, {usr, us, pid}).
-record(presence, {usr, us, priority}).
start_link() ->
Pid = proc_lib:spawn_link(ejabberd_sm, init, []),
@ -39,16 +41,18 @@ init() ->
update_tables(),
mnesia:create_table(session, [{ram_copies, [node()]},
{attributes, record_info(fields, session)}]),
mnesia:add_table_index(session, user),
mnesia:add_table_index(session, us),
mnesia:add_table_copy(session, node(), ram_copies),
mnesia:create_table(presence,
[{ram_copies, [node()]},
{attributes, record_info(fields, presence)}]),
mnesia:add_table_index(presence, user),
mnesia:add_table_index(presence, us),
mnesia:subscribe(system),
ets:new(sm_iqtable, [named_table]),
ejabberd_hooks:add(offline_message_hook,
ejabberd_sm, bounce_offline_message, 100),
ejabberd_hooks:add(remove_user,
ejabberd_sm, disconnect_removed_user, 100),
loop().
loop() ->
@ -62,12 +66,6 @@ loop() ->
ok
end,
loop();
{open_session, User, Resource, From} ->
register_connection(User, Resource, From),
loop();
{close_session, User, Resource} ->
remove_connection(User, Resource),
loop();
{mnesia_system_event, {mnesia_down, Node}} ->
clean_table_from_bad_node(Node),
loop();
@ -100,20 +98,22 @@ route(From, To, Packet) ->
ok
end.
open_session(User, Resource) ->
register_connection(User, Resource, self()).
open_session(User, Server, Resource) ->
register_connection(User, Server, Resource, self()).
close_session(User, Resource) ->
remove_connection(User, Resource).
close_session(User, Server, Resource) ->
remove_connection(User, Server, Resource).
register_connection(User, Resource, Pid) ->
register_connection(User, Server, Resource, Pid) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
LResource = jlib:resourceprep(Resource),
UR = {LUser, LResource},
US = {LUser, LServer},
USR = {LUser, LServer, LResource},
F = fun() ->
Ss = mnesia:wread({session, UR}),
mnesia:write(#session{ur = UR, user = LUser, pid = Pid}),
Ss = mnesia:wread({session, USR}),
mnesia:write(#session{usr = USR, us = US, pid = Pid}),
Ss
end,
case mnesia:transaction(F) of
@ -127,12 +127,13 @@ register_connection(User, Resource, Pid) ->
end.
remove_connection(User, Resource) ->
remove_connection(User, Server, Resource) ->
LUser = jlib:nodeprep(User),
LResource = jlib:resourceprep(Resource),
UR = {LUser, LResource},
LServer = jlib:nameprep(Server),
USR = {LUser, LServer, LResource},
F = fun() ->
mnesia:delete({session, UR})
mnesia:delete({session, USR})
end,
mnesia:transaction(F).
@ -145,8 +146,7 @@ clean_table_from_bad_node(Node) ->
[{'==', {node, '$1'}, Node}],
['$_']}]),
lists:foreach(fun(E) ->
mnesia:delete_object(E),
mnesia:delete({presence, E#session.ur})
mnesia:delete_object(E)
end, Es)
end,
mnesia:transaction(F).
@ -157,7 +157,7 @@ clean_table_from_bad_node(Node) ->
do_route(From, To, Packet) ->
?DEBUG("session manager~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
[From, To, Packet, 8]),
#jid{user = User,
#jid{user = User, server = Server,
luser = LUser, lserver = LServer, lresource = LResource} = To,
{xmlelement, Name, Attrs, _Els} = Packet,
case LResource of
@ -170,32 +170,33 @@ do_route(From, To, Packet) ->
{ejabberd_hooks:run_fold(
roster_in_subscription,
false,
[User, From, subscribe]),
[User, Server, From, subscribe]),
true};
"subscribed" ->
{ejabberd_hooks:run_fold(
roster_in_subscription,
false,
[User, From, subscribed]),
[User, Server, From, subscribed]),
true};
"unsubscribe" ->
{ejabberd_hooks:run_fold(
roster_in_subscription,
false,
[User, From, unsubscribe]),
[User, Server, From, unsubscribe]),
true};
"unsubscribed" ->
{ejabberd_hooks:run_fold(
roster_in_subscription,
false,
[User, From, unsubscribed]),
[User, Server, From, unsubscribed]),
true};
_ ->
{true, false}
end,
if Pass ->
LFrom = jlib:jid_tolower(From),
PResources = get_user_present_resources(User),
PResources = get_user_present_resources(
LUser, LServer),
if
PResources /= [] ->
lists:foreach(
@ -213,7 +214,8 @@ do_route(From, To, Packet) ->
true ->
if
Subsc ->
case ejabberd_auth:is_user_exists(LUser) of
case ejabberd_auth:is_user_exists(
LUser, LServer) of
true ->
ejabberd_hooks:run(
offline_subscription_hook,
@ -240,13 +242,13 @@ do_route(From, To, Packet) ->
do_route(From,
jlib:jid_replace_resource(To, R),
Packet)
end, get_user_resources(User));
end, get_user_resources(User, Server));
_ ->
ok
end;
_ ->
LUR = {LUser, LResource},
case mnesia:dirty_read({session, LUR}) of
USR = {LUser, LServer, LResource},
case mnesia:dirty_read({session, USR}) of
[] ->
case Name of
"message" ->
@ -273,12 +275,13 @@ do_route(From, To, Packet) ->
route_message(From, To, Packet) ->
LUser = To#jid.luser,
case catch lists:max(get_user_present_resources(LUser)) of
LServer = To#jid.lserver,
case catch lists:max(get_user_present_resources(LUser, LServer)) of
{Priority, R} when is_integer(Priority),
Priority >= 0 ->
LResource = jlib:resourceprep(R),
LUR = {LUser, LResource},
case mnesia:dirty_read({session, LUR}) of
USR = {LUser, LServer, LResource},
case mnesia:dirty_read({session, USR}) of
[] ->
ok; % Race condition
[Sess] ->
@ -291,7 +294,7 @@ route_message(From, To, Packet) ->
"error" ->
ok;
_ ->
case ejabberd_auth:is_user_exists(LUser) of
case ejabberd_auth:is_user_exists(LUser, LServer) of
true ->
ejabberd_hooks:run(offline_message_hook,
[From, To, Packet]);
@ -308,46 +311,58 @@ bounce_offline_message(From, To, Packet) ->
ejabberd_router:route(To, From, Err),
stop.
disconnect_removed_user(User, Server) ->
ejabberd_sm:route(jlib:make_jid("", "", ""),
jlib:make_jid(User, Server, ""),
{xmlelement, "broadcast", [],
[{exit, "User removed"}]}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
get_user_resources(User) ->
get_user_resources(User, Server) ->
LUser = jlib:nodeprep(User),
case catch mnesia:dirty_index_read(session, LUser, #session.user) of
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_index_read(session, US, #session.us) of
{'EXIT', _Reason} ->
[];
Rs ->
lists:map(fun(R) ->
element(2, R#session.ur)
element(3, R#session.usr)
end, Rs)
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
set_presence(User, Resource, Priority) ->
set_presence(User, Server, Resource, Priority) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
USR = {User, Server, Resource},
US = {LUser, LServer},
F = fun() ->
UR = {User, Resource},
mnesia:write(#presence{ur = UR, user = LUser,
mnesia:write(#presence{usr = USR, us = US,
priority = Priority})
end,
mnesia:transaction(F).
unset_presence(User, Resource, Status) ->
unset_presence(User, Server, Resource, Status) ->
USR = {User, Server, Resource},
F = fun() ->
UR = {User, Resource},
mnesia:delete({presence, UR})
mnesia:delete({presence, USR})
end,
mnesia:transaction(F),
ejabberd_hooks:run(unset_presence_hook, [User, Resource, Status]).
ejabberd_hooks:run(unset_presence_hook, [User, Server, Resource, Status]).
get_user_present_resources(LUser) ->
case catch mnesia:dirty_index_read(presence, LUser, #presence.user) of
get_user_present_resources(LUser, LServer) ->
US = {LUser, LServer},
case catch mnesia:dirty_index_read(presence, US, #presence.us) of
{'EXIT', _Reason} ->
[];
Rs ->
lists:map(fun(R) ->
{R#presence.priority, element(2, R#presence.ur)}
{R#presence.priority, element(3, R#presence.usr)}
end, Rs)
end.
@ -361,6 +376,14 @@ dirty_get_my_sessions_list() ->
[{'==', {node, '$1'}, node()}],
['$_']}]).
get_vh_session_list(Server) ->
LServer = jlib:nameprep(Server),
mnesia:dirty_select(
session,
[{#session{usr = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, LServer}],
['$1']}]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@ -410,6 +433,16 @@ update_tables() ->
[ur, user, node] ->
mnesia:delete_table(session);
[ur, user, pid] ->
mnesia:delete_table(session);
[usr, us, pid] ->
ok;
{'EXIT', _} ->
ok
end,
case catch mnesia:table_info(presence, attributes) of
[ur, user, priority] ->
mnesia:delete_table(presence);
[usr, us, priority] ->
ok;
{'EXIT', _} ->
ok

View File

@ -26,6 +26,13 @@ init([]) ->
brutal_kill,
worker,
[ejabberd_hooks]},
StringPrep =
{stringprep,
{stringprep, start_link, []},
permanent,
brutal_kill,
worker,
[stringprep]},
Router =
{ejabberd_router,
{ejabberd_router, start_link, []},
@ -61,13 +68,6 @@ init([]) ->
infinity,
supervisor,
[ejabberd_listener]},
StringPrep =
{stringprep,
{stringprep, start_link, []},
permanent,
brutal_kill,
worker,
[stringprep]},
C2SSupervisor =
{ejabberd_c2s_sup,
{ejabberd_tmp_sup, start_link, [ejabberd_c2s_sup, ejabberd_c2s]},
@ -125,11 +125,11 @@ init([]) ->
[ejabberd_tmp_sup]},
{ok, {{one_for_one, 10, 1},
[Hooks,
StringPrep,
Router,
SM,
S2S,
Local,
StringPrep,
C2SSupervisor,
S2SInSupervisor,
S2SOutSupervisor,

2972
src/eldap/ELDAPv3.erl Normal file

File diff suppressed because it is too large Load Diff

74
src/eldap/ELDAPv3.hrl Normal file
View File

@ -0,0 +1,74 @@
%% Generated by the Erlang ASN.1 compiler version:1.3.2
%% Purpose: Erlang record definitions for each named and unnamed
%% SEQUENCE and SET, and macro definitions for each value
%% definition,in module ELDAPv3
-record('LDAPMessage',{
messageID, protocolOp, controls = asn1_NOVALUE}).
-record('AttributeValueAssertion',{
attributeDesc, assertionValue}).
-record('Attribute',{
type, vals}).
-record('LDAPResult',{
resultCode, matchedDN, errorMessage, referral = asn1_NOVALUE}).
-record('Control',{
controlType, criticality = asn1_DEFAULT, controlValue = asn1_NOVALUE}).
-record('BindRequest',{
version, name, authentication}).
-record('SaslCredentials',{
mechanism, credentials = asn1_NOVALUE}).
-record('BindResponse',{
resultCode, matchedDN, errorMessage, referral = asn1_NOVALUE, serverSaslCreds = asn1_NOVALUE}).
-record('SearchRequest',{
baseObject, scope, derefAliases, sizeLimit, timeLimit, typesOnly, filter, attributes}).
-record('SubstringFilter',{
type, substrings}).
-record('MatchingRuleAssertion',{
matchingRule = asn1_NOVALUE, type = asn1_NOVALUE, matchValue, dnAttributes = asn1_DEFAULT}).
-record('SearchResultEntry',{
objectName, attributes}).
-record('PartialAttributeList_SEQOF',{
type, vals}).
-record('ModifyRequest',{
object, modification}).
-record('ModifyRequest_modification_SEQOF',{
operation, modification}).
-record('AttributeTypeAndValues',{
type, vals}).
-record('AddRequest',{
entry, attributes}).
-record('AttributeList_SEQOF',{
type, vals}).
-record('ModifyDNRequest',{
entry, newrdn, deleteoldrdn, newSuperior = asn1_NOVALUE}).
-record('CompareRequest',{
entry, ava}).
-record('ExtendedRequest',{
requestName, requestValue = asn1_NOVALUE}).
-record('ExtendedResponse',{
resultCode, matchedDN, errorMessage, referral = asn1_NOVALUE, responseName = asn1_NOVALUE, response = asn1_NOVALUE}).
-define('maxInt', 2147483647).

View File

@ -16,7 +16,8 @@
get_opt/2,
get_opt/3,
get_module_opt/3,
loaded_modules/0]).
loaded_modules/0,
get_hosts/2]).
-export([behaviour_info/1]).
@ -27,7 +28,7 @@
behaviour_info(callbacks) ->
[{start, 1},
{stop, 0}];
behaviour_info(Other) ->
behaviour_info(_Other) ->
undefined.
start() ->
@ -87,3 +88,15 @@ loaded_modules() ->
ets:select(ejabberd_modules,
[{#ejabberd_module{_ = '_', module = '$1'}, [],['$1']}]).
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
end.

View File

@ -10,246 +10,46 @@
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-behaviour(gen_fsm).
%% External exports
-export([start/1,
start/2,
import_file/1,
-export([import_file/1,
import_dir/1]).
%% gen_fsm callbacks
-export([init/1,
wait_for_xdb/2,
xdb_data/2,
handle_event/3,
handle_sync_event/4,
code_change/4,
handle_info/3,
terminate/3]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(state, {socket, pid, xml_stream_pid,
user = "", server = ?MYNAME, resource = ""
}).
%-define(DBGFSM, true).
-ifdef(DBGFSM).
-define(FSMOPTS, [{debug, [trace]}]).
-else.
-define(FSMOPTS, []).
-endif.
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(File) ->
User = filename:rootname(filename:basename(File)),
start(File, User).
start(File, User) ->
gen_fsm:start(?MODULE, [File, User, self()], ?FSMOPTS).
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok, StateName, StateData} |
%% {ok, StateName, StateData, Timeout} |
%% ignore |
%% {stop, StopReason}
%%----------------------------------------------------------------------
init([File, User, Pid]) ->
% Profiling
%eprof:start(),
%eprof:profile([self()]),
XMLStreamPid = xml_stream:start(self()),
{ok, Text} = file:read_file(File),
xml_stream:send_text(XMLStreamPid, Text),
{ok, wait_for_xdb, #state{user = User, pid = Pid,
xml_stream_pid = XMLStreamPid}}.
%%----------------------------------------------------------------------
%% Func: StateName/2
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
wait_for_xdb({xmlstreamstart, Name, _Attrs}, StateData) ->
case Name of
"xdb" ->
{next_state, xdb_data, StateData};
_ ->
{stop, normal, StateData}
end;
wait_for_xdb(closed, StateData) ->
{stop, normal, StateData}.
xdb_data({xmlstreamelement, El}, StateData) ->
{xmlelement, _Name, Attrs, _Els} = El,
Server = StateData#state.server,
From = jlib:make_jid(StateData#state.user, Server, ""),
NewState =
case xml:get_attr_s("xmlns", Attrs) of
?NS_AUTH ->
Password = xml:get_tag_cdata(El),
ejabberd_auth:set_password(StateData#state.user, Password),
StateData;
?NS_ROSTER ->
%catch mod_roster:process_iq(
% From,
% {"", ?MYNAME, ""},
% #iq{type = set, xmlns = ?NS_ROSTER, sub_el = El}),
catch mod_roster:set_items(StateData#state.user, El),
StateData;
?NS_VCARD ->
catch mod_vcard:process_sm_iq(
From,
jlib:make_jid("", ?MYNAME, ""),
#iq{type = set, xmlns = ?NS_VCARD, sub_el = El}),
StateData;
"jabber:x:offline" ->
process_offline(From, El),
StateData;
%?NS_REGISTER ->
% catch mod_register:process_iq(
% {"", "", ""}, {"", ?MYNAME, ""},
% #iq{type =set, xmlns = ?NS_REGISTER, xub_el = El}),
% User = xml:get_path_s(El, [{elem, "username"}, cdata]),
% io:format("user ~s~n", [User]),
% StateData;
XMLNS ->
case xml:get_attr_s("j_private_flag", Attrs) of
"1" ->
catch mod_private:process_local_iq(
From,
jlib:make_jid("", ?MYNAME, ""),
#iq{type = set, xmlns = ?NS_PRIVATE,
sub_el = {xmlelement, "query", [],
[jlib:remove_attr(
"j_private_flag",
jlib:remove_attr("xdbns", El))]}}),
StateData;
_ ->
io:format("jd2ejd: Unknown namespace \"~s\"~n",
[XMLNS]),
StateData
end
end,
{next_state, xdb_data, NewState};
xdb_data({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
xdb_data(closed, StateData) ->
{stop, normal, StateData}.
%%----------------------------------------------------------------------
%% Func: StateName/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {reply, Reply, NextStateName, NextStateData} |
%% {reply, Reply, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
%state_name(Event, From, StateData) ->
% Reply = ok,
% {reply, Reply, state_name, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_event/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_sync_event/4
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {reply, Reply, NextStateName, NextStateData} |
%% {reply, Reply, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
handle_sync_event(_Event, _From, StateName, StateData) ->
Reply = ok,
{reply, Reply, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_info/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_info(_, StateName, StateData) ->
{next_state, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: terminate/3
%% Purpose: Shutdown the fsm
%% Returns: any
%%----------------------------------------------------------------------
terminate(Reason, _StateName, StateData) ->
exit(StateData#state.xml_stream_pid, closed),
StateData#state.pid ! {jd2ejd, Reason},
% Profiling
%eprof:log("/tmp/eprof"),
%eprof:analyse(),
%eprof:stop(),
ok.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
process_offline(To, {xmlelement, _, _, Els}) ->
lists:foreach(fun({xmlelement, _, Attrs, _} = El) ->
FromS = xml:get_attr_s("from", Attrs),
From = case FromS of
"" ->
{"", ?MYNAME, ""};
_ ->
jlib:string_to_jid(FromS)
end,
case From of
error ->
ok;
_ ->
catch mod_offline:store_packet(From, To, El)
end
end, Els).
import_file(File) ->
clear_queue(),
start(File),
receive
{jd2ejd, Result} -> Result
%after 4000 -> timeout
end.
clear_queue() ->
receive
{jd2ejd, _Result} -> clear_queue
after 0 -> ok
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",
[File, Reason]);
_ ->
ok
end;
{error, Reason} ->
?ERROR_MSG("Can't parse file \"~s\": ~p~n",
[File, Reason])
end;
{error, Reason} ->
?ERROR_MSG("Can't read file \"~s\": ~p~n", [File, Reason])
end;
false ->
?ERROR_MSG("Incorrect user/server name in file \"~s\"~n", [File])
end.
@ -271,5 +71,74 @@ import_dir(Dir) ->
end, MsgFiles),
ok.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
process_xdb(User, Server, {xmlelement, Name, _Attrs, Els}) ->
case Name of
"xdb" ->
lists:foreach(
fun(El) ->
xdb_data(User, Server, El)
end, Els);
_ ->
ok
end.
xdb_data(User, Server, El) ->
{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_VCARD ->
catch mod_vcard:process_sm_iq(
From,
jlib:make_jid("", ?MYNAME, ""),
#iq{type = set, xmlns = ?NS_VCARD, sub_el = El}),
ok;
"jabber:x:offline" ->
process_offline(From, El),
ok;
XMLNS ->
case xml:get_attr_s("j_private_flag", Attrs) of
"1" ->
catch mod_private:process_local_iq(
From,
jlib:make_jid("", ?MYNAME, ""),
#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
end.
process_offline(To, {xmlelement, _, _, Els}) ->
lists:foreach(fun({xmlelement, _, Attrs, _} = El) ->
FromS = xml:get_attr_s("from", Attrs),
From = case FromS of
"" ->
jlib:make_jid("", ?MYNAME, "");
_ ->
jlib:string_to_jid(FromS)
end,
case From of
error ->
ok;
_ ->
catch mod_offline:store_packet(From, To, El)
end
end, Els).

View File

@ -20,8 +20,8 @@
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(motd, {id, packet}).
-record(motd_users, {luser, dummy = []}).
-record(motd, {server, packet}).
-record(motd_users, {us, dummy = []}).
-define(PROCNAME, ejabberd_announce).
@ -30,6 +30,7 @@ start(_) ->
{attributes, record_info(fields, motd)}]),
mnesia:create_table(motd_users, [{disc_copies, [node()]},
{attributes, record_info(fields, motd_users)}]),
update_tables(),
ejabberd_hooks:add(local_send_to_resource_hook,
?MODULE, announce, 50),
ejabberd_hooks:add(user_available_hook,
@ -47,6 +48,9 @@ loop() ->
{announce_online, From, To, Packet} ->
announce_online(From, To, Packet),
loop();
{announce_all_hosts_online, From, To, Packet} ->
announce_all_hosts_online(From, To, Packet),
loop();
{announce_motd, From, To, Packet} ->
announce_motd(From, To, Packet),
loop();
@ -79,6 +83,9 @@ announce(From, To, Packet) ->
{"announce/online", "message"} ->
?PROCNAME ! {announce_online, From, To, Packet},
stop;
{"announce/all-hosts/online", "message"} ->
?PROCNAME ! {announce_all_hosts_online, From, To, Packet},
stop;
{"announce/motd", "message"} ->
?PROCNAME ! {announce_motd, From, To, Packet},
stop;
@ -102,13 +109,12 @@ announce_all(From, To, Packet) ->
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
ejabberd_router:route(To, From, Err);
allow ->
Server = ?MYNAME,
Local = jlib:make_jid("", Server, ""),
Local = jlib:make_jid("", To#jid.server, ""),
lists:foreach(
fun(U) ->
Dest = jlib:make_jid(U, Server, ""),
fun({User, Server}) ->
Dest = jlib:make_jid(User, Server, ""),
ejabberd_router:route(Local, Dest, Packet)
end, ejabberd_auth:dirty_get_registered_users())
end, ejabberd_auth:get_vh_registered_users(To#jid.lserver))
end.
announce_online(From, To, Packet) ->
@ -118,15 +124,28 @@ announce_online(From, To, Packet) ->
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
ejabberd_router:route(To, From, Err);
allow ->
announce_online1(ejabberd_sm:dirty_get_sessions_list(), Packet)
announce_online1(ejabberd_sm:get_vh_session_list(To#jid.lserver),
To#jid.server,
Packet)
end.
announce_online1(Sessions, Packet) ->
Server = ?MYNAME,
announce_all_hosts_online(From, To, Packet) ->
Access = gen_mod:get_module_opt(?MODULE, access, none),
case acl:match_rule(Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
ejabberd_router:route(To, From, Err);
allow ->
announce_online1(ejabberd_sm:dirty_get_sessions_list(),
To#jid.server,
Packet)
end.
announce_online1(Sessions, Server, Packet) ->
Local = jlib:make_jid("", Server, ""),
lists:foreach(
fun({U, R}) ->
Dest = jlib:make_jid(U, Server, R),
fun({U, S, R}) ->
Dest = jlib:make_jid(U, S, R),
ejabberd_router:route(Local, Dest, Packet)
end, Sessions).
@ -137,13 +156,13 @@ announce_motd(From, To, Packet) ->
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
ejabberd_router:route(To, From, Err);
allow ->
announce_motd_update(Packet),
Sessions = ejabberd_sm:dirty_get_sessions_list(),
announce_online1(Sessions, Packet),
announce_motd_update(To#jid.lserver, Packet),
Sessions = ejabberd_sm:get_vh_session_list(To#jid.lserver),
announce_online1(Sessions, To#jid.server, Packet),
F = fun() ->
lists:foreach(
fun({U, _R}) ->
mnesia:write(#motd_users{luser = U})
fun({U, S, _R}) ->
mnesia:write(#motd_users{us = {U, S}})
end, Sessions)
end,
mnesia:transaction(F)
@ -156,13 +175,13 @@ announce_motd_update(From, To, Packet) ->
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
ejabberd_router:route(To, From, Err);
allow ->
announce_motd_update(Packet)
announce_motd_update(To#jid.lserver, Packet)
end.
announce_motd_update(Packet) ->
announce_motd_delete(),
announce_motd_update(LServer, Packet) ->
announce_motd_delete(LServer),
F = fun() ->
mnesia:write(#motd{id = motd, packet = Packet})
mnesia:write(#motd{server = LServer, packet = Packet})
end,
mnesia:transaction(F).
@ -173,24 +192,36 @@ announce_motd_delete(From, To, Packet) ->
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
ejabberd_router:route(To, From, Err);
allow ->
announce_motd_delete()
announce_motd_delete(To#jid.lserver)
end.
announce_motd_delete() ->
mnesia:clear_table(motd),
mnesia:clear_table(motd_users).
announce_motd_delete(LServer) ->
F = fun() ->
mnesia:delete({motd, LServer}),
mnesia:write_lock_table(motd_users),
Users = mnesia:select(
motd_users,
[{#motd_users{us = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, LServer}],
['$1']}]),
lists:foreach(fun(US) ->
mnesia:delete({motd_users, US})
end, Users)
end,
mnesia:transaction(F).
send_motd(#jid{luser = LUser} = JID) ->
case catch mnesia:dirty_read({motd, motd}) of
send_motd(#jid{luser = LUser, lserver = LServer} = JID) ->
case catch mnesia:dirty_read({motd, LServer}) of
[#motd{packet = Packet}] ->
case catch mnesia:dirty_read({motd_users, LUser}) of
US = {LUser, LServer},
case catch mnesia:dirty_read({motd_users, US}) of
[#motd_users{}] ->
ok;
_ ->
Local = jlib:make_jid("", ?MYNAME, ""),
Local = jlib:make_jid("", LServer, ""),
ejabberd_router:route(Local, JID, Packet),
F = fun() ->
mnesia:write(#motd_users{luser = LUser})
mnesia:write(#motd_users{us = US})
end,
mnesia:transaction(F)
end;
@ -198,3 +229,92 @@ send_motd(#jid{luser = LUser} = JID) ->
ok
end.
update_tables() ->
update_motd_table(),
update_motd_users_table().
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);
_ ->
?INFO_MSG("Recreating motd table", []),
mnesia:transform_table(motd, ignore, Fields)
end.
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);
_ ->
?INFO_MSG("Recreating motd_users table", []),
mnesia:transform_table(motd_users, ignore, Fields)
end.

View File

@ -690,11 +690,6 @@ set_form(["config", "remusers"], Lang, XData) ->
fun({Var, Vals}) ->
case Vals of
["1"] ->
ejabberd_sm ! {route,
jlib:make_jid("", "", ""),
jlib:make_jid(Var, "", ""),
{xmlelement, "broadcast", [],
[{exit, "User removed"}]}},
catch ejabberd_auth:remove_user(Var);
_ ->
ok
@ -728,7 +723,7 @@ process_sm_iq(From, To,
deny ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
allow ->
#jid{user = User} = To,
#jid{user = User, server = Server} = To,
case Type of
set ->
XDataEl = find_xdata_el(SubEl),
@ -753,7 +748,8 @@ process_sm_iq(From, To,
xml:get_tag_attr_s("node", SubEl),
"/"),
case set_sm_form(
User, Node, Lang, XData) of
User, Server, Node,
Lang, XData) of
{result, Res} ->
IQ#iq{type = result,
sub_el =
@ -774,7 +770,7 @@ process_sm_iq(From, To,
get ->
Node =
string:tokens(xml:get_tag_attr_s("node", SubEl), "/"),
case get_sm_form(User, Node, Lang) of
case get_sm_form(User, Server, Node, Lang) of
{result, Res} ->
IQ#iq{type = result,
sub_el =
@ -788,7 +784,7 @@ process_sm_iq(From, To,
end.
get_sm_form(User, [], Lang) ->
get_sm_form(User, Server, [], Lang) ->
{result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
[{xmlelement, "title", [],
[{xmlcdata,
@ -811,7 +807,7 @@ get_sm_form(User, [], Lang) ->
[{xmlelement, "value", [], [{xmlcdata, "remove"}]}]}
]},
?XFIELD("text-private", "Password", "password",
ejabberd_auth:get_password_s(User))
ejabberd_auth:get_password_s(User, Server))
%{xmlelement, "field", [{"type", "text-single"},
% {"label",
% translate:translate(Lang, "Host name")},
@ -819,32 +815,27 @@ get_sm_form(User, [], Lang) ->
% [{xmlelement, "value", [], [{xmlcdata, ?MYNAME}]}]}
]}]};
get_sm_form(_, _, Lang) ->
get_sm_form(_User, _Server, _Node, Lang) ->
{error, ?ERR_SERVICE_UNAVAILABLE}.
set_sm_form(User, [], Lang, XData) ->
set_sm_form(User, Server, [], Lang, XData) ->
case lists:keysearch("action", 1, XData) of
{value, {_, ["edit"]}} ->
case lists:keysearch("password", 1, XData) of
{value, {_, [Password]}} ->
ejabberd_auth:set_password(User, Password),
ejabberd_auth:set_password(User, Server, Password),
{result, []};
_ ->
{error, ?ERR_BAD_REQUEST}
end;
{value, {_, ["remove"]}} ->
ejabberd_sm ! {route,
jlib:make_jid("", "", ""),
jlib:make_jid(User, "", ""),
{xmlelement, "broadcast", [],
[{exit, "User removed"}]}},
catch ejabberd_auth:remove_user(User),
catch ejabberd_auth:remove_user(User, Server),
{result, []};
_ ->
{error, ?ERR_BAD_REQUEST}
end;
set_sm_form(_, _, Lang, XData) ->
set_sm_form(_User, _Server, _Node, Lang, XData) ->
{error, ?ERR_SERVICE_UNAVAILABLE}.
find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) ->

View File

@ -102,10 +102,11 @@ process_local_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} =
IQ#iq{type = result,
sub_el = [{xmlelement, "query",
[{"xmlns", ?NS_DISCO_ITEMS}],
get_services_only()
get_services_only(To#jid.lserver)
}]};
_ ->
case get_local_items(Node, jlib:jid_to_string(To), Lang) of
case get_local_items(To#jid.lserver, Node,
jlib:jid_to_string(To), Lang) of
{result, Res} ->
IQ#iq{type = result,
sub_el = [{xmlelement, "query",
@ -222,15 +223,15 @@ domain_to_xml(Domain) ->
{"node", Node}], []}).
get_services_only() ->
get_services_only(Host) ->
lists:map(fun domain_to_xml/1,
ejabberd_router:dirty_get_all_routes()) ++
get_vh_services(Host)) ++
lists:map(fun domain_to_xml/1, ets:tab2list(disco_extra_domains)).
get_local_items([], Server, Lang) ->
get_local_items(Host, [], Server, Lang) ->
Domains =
lists:map(fun domain_to_xml/1,
ejabberd_router:dirty_get_all_routes()) ++
get_vh_services(Host)) ++
lists:map(fun domain_to_xml/1, ets:tab2list(disco_extra_domains)),
{result,
Domains ++
@ -242,61 +243,61 @@ get_local_items([], Server, Lang) ->
?NODE("Stopped Nodes", "stopped nodes")
]};
get_local_items(["config"], Server, Lang) ->
get_local_items(Host, ["config"], Server, Lang) ->
{result,
[?NODE("Host Name", "config/hostname"),
?NODE("Access Control Lists", "config/acls"),
?NODE("Access Rules", "config/access"),
?NODE("Remove Users", "config/remusers")
?NODE("Access Rules", "config/access")
% Too expensive on big hosts
%?NODE("Remove Users", "config/remusers")
]};
get_local_items(["config", _], Server, Lang) ->
get_local_items(Host, ["config", _], Server, Lang) ->
{result, []};
get_local_items(["online users"], Server, Lang) ->
{result, get_online_users()};
get_local_items(Host, ["online users"], Server, Lang) ->
{result, get_online_vh_users(Host)};
get_local_items(["all users"], Server, Lang) ->
{result, get_all_users()};
get_local_items(Host, ["all users"], Server, Lang) ->
{result, get_all_vh_users(Host)};
get_local_items(["all users", [$@ | Diap]], Server, Lang) ->
get_local_items(Host, ["all users", [$@ | Diap]], Server, Lang) ->
case catch ejabberd_auth:dirty_get_registered_users() of
{'EXIT', Reason} ->
?ERR_INTERNAL_SERVER_ERROR;
Users ->
SUsers = lists:sort(Users),
SUsers = lists:sort([{S, U} || {U, S} <- Users]),
case catch begin
{ok, [S1, S2]} = regexp:split(Diap, "-"),
N1 = list_to_integer(S1),
N2 = list_to_integer(S2),
Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
lists:map(fun(U) ->
lists:map(fun({S, U}) ->
{xmlelement, "item",
[{"jid", U ++ "@" ++ ?MYNAME},
{"name", U}], []}
[{"jid", U ++ "@" ++ S},
{"name", U ++ "@" ++ S}], []}
end, Sub)
end of
{'EXIT', Reason} ->
% TODO: must be "not acceptable"
?ERR_BAD_REQUEST;
?ERR_NOT_ACCEPTABLE;
Res ->
{result, Res}
end
end;
get_local_items(["outgoing s2s"], Server, Lang) ->
{result, get_outgoing_s2s(Lang)};
get_local_items(Host, ["outgoing s2s"], Server, Lang) ->
{result, get_outgoing_s2s(Host, Lang)};
get_local_items(["outgoing s2s", To], Server, Lang) ->
{result, get_outgoing_s2s(Lang, To)};
get_local_items(Host, ["outgoing s2s", To], Server, Lang) ->
{result, get_outgoing_s2s(Host, Lang, To)};
get_local_items(["running nodes"], Server, Lang) ->
get_local_items(Host, ["running nodes"], Server, Lang) ->
{result, get_running_nodes(Lang)};
get_local_items(["stopped nodes"], Server, Lang) ->
get_local_items(Host, ["stopped nodes"], Server, Lang) ->
{result, get_stopped_nodes(Lang)};
get_local_items(["running nodes", ENode], Server, Lang) ->
get_local_items(Host, ["running nodes", ENode], Server, Lang) ->
{result,
[?NODE("DB", "running nodes/" ++ ENode ++ "/DB"),
?NODE("Modules", "running nodes/" ++ ENode ++ "/modules"),
@ -305,19 +306,19 @@ get_local_items(["running nodes", ENode], Server, Lang) ->
"running nodes/" ++ ENode ++ "/import")
]};
get_local_items(["running nodes", ENode, "DB"], Server, Lang) ->
get_local_items(Host, ["running nodes", ENode, "DB"], Server, Lang) ->
{result, []};
get_local_items(["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(["running nodes", ENode, "modules", _], Server, Lang) ->
get_local_items(Host, ["running nodes", ENode, "modules", _], Server, Lang) ->
{result, []};
get_local_items(["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"),
@ -325,49 +326,54 @@ get_local_items(["running nodes", ENode, "backup"], Server, Lang) ->
"running nodes/" ++ ENode ++ "/backup/textfile")
]};
get_local_items(["running nodes", ENode, "backup", _], Server, Lang) ->
get_local_items(Host, ["running nodes", ENode, "backup", _], Server, Lang) ->
{result, []};
get_local_items(["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(["running nodes", ENode, "import", _], Server, Lang) ->
get_local_items(Host, ["running nodes", ENode, "import", _], Server, Lang) ->
{result, []};
get_local_items(_, _, _) ->
get_local_items(_Host, _, _, _) ->
{error, ?ERR_FEATURE_NOT_IMPLEMENTED}.
get_vh_services(Host) ->
DotHost = "." ++ Host,
lists:filter(fun(H) ->
lists:suffix(DotHost, H)
end, ejabberd_router:dirty_get_all_routes()).
get_online_users() ->
case catch ejabberd_sm:dirty_get_sessions_list() of
get_online_vh_users(Host) ->
case catch ejabberd_sm:get_vh_session_list(Host) of
{'EXIT', Reason} ->
[];
URs ->
lists:map(fun({U, R}) ->
USRs ->
SURs = lists:sort([{S, U, R} || {U, S, R} <- USRs]),
lists:map(fun({S, U, R}) ->
{xmlelement, "item",
[{"jid", U ++ "@" ++ ?MYNAME ++ "/" ++ R},
{"name", U}], []}
end, lists:sort(URs))
[{"jid", U ++ "@" ++ S ++ "/" ++ R},
{"name", U ++ "@" ++ S}], []}
end, SURs)
end.
get_all_users() ->
case catch ejabberd_auth:dirty_get_registered_users() of
get_all_vh_users(Host) ->
case catch ejabberd_auth:get_vh_registered_users(Host) of
{'EXIT', Reason} ->
[];
Users ->
SUsers = lists:sort(Users),
SUsers = lists:sort([{S, U} || {U, S} <- Users]),
case length(SUsers) of
N when N =< 100 ->
lists:map(fun(U) ->
lists:map(fun({S, U}) ->
{xmlelement, "item",
[{"jid", U ++ "@" ++ ?MYNAME},
{"name", U}], []}
[{"jid", U ++ "@" ++ S},
{"name", U ++ "@" ++ S}], []}
end, SUsers);
N ->
NParts = trunc(math:sqrt(N * 0.618)) + 1,
@ -377,30 +383,35 @@ get_all_users() ->
Node =
"@" ++ integer_to_list(K) ++
"-" ++ integer_to_list(L),
Last = if L < N -> lists:nth(L, SUsers);
{FS, FU} = lists:nth(K, SUsers),
{LS, LU} =
if L < N -> lists:nth(L, SUsers);
true -> lists:last(SUsers)
end,
Name =
lists:nth(K, SUsers) ++ " -- " ++
Last,
FU ++ "@" ++ FS ++
" -- " ++
LU ++ "@" ++ LS,
{xmlelement, "item",
[{"jid", ?MYNAME},
[{"jid", Host},
{"node", "all users/" ++ Node},
{"name", Name}], []}
end, lists:seq(1, N, M))
end
end.
get_outgoing_s2s(Lang) ->
get_outgoing_s2s(Host, Lang) ->
case catch ejabberd_s2s:dirty_get_connections() of
{'EXIT', Reason} ->
[];
Connections ->
TConns = [element(2, C) || C <- Connections],
DotHost = "." ++ Host,
TConns = [TH || {FH, TH} <- Connections,
Host == FH orelse lists:suffix(DotHost, FH)],
lists:map(
fun(T) ->
{xmlelement, "item",
[{"jid", ?MYNAME},
[{"jid", Host},
{"node", "outgoing s2s/" ++ T},
{"name",
lists:flatten(
@ -410,7 +421,7 @@ get_outgoing_s2s(Lang) ->
end, lists:usort(TConns))
end.
get_outgoing_s2s(Lang, To) ->
get_outgoing_s2s(Host, Lang, To) ->
case catch ejabberd_s2s:dirty_get_connections() of
{'EXIT', Reason} ->
[];
@ -418,7 +429,7 @@ get_outgoing_s2s(Lang, To) ->
lists:map(
fun({F, T}) ->
{xmlelement, "item",
[{"jid", ?MYNAME},
[{"jid", Host},
{"node", "outgoing s2s/" ++ To ++ "/" ++ F},
{"name",
lists:flatten(
@ -576,7 +587,7 @@ process_sm_iq_info(From, To, #iq{type = Type, xmlns = XMLNS,
get_user_resources(User) ->
Rs = ejabberd_sm:get_user_resources(User),
Rs = ejabberd_sm:get_user_resources(User, 'TODO'),
lists:map(fun(R) ->
{xmlelement, "item",
[{"jid", User ++ "@" ++ ?MYNAME ++ "/" ++ R},

View File

@ -12,48 +12,51 @@
-behaviour(gen_mod).
-export([start/1, init/2, stop/0, closed_conection/2,
get_user_and_encoding/2]).
-export([start/1, init/2, stop/0,
closed_connection/3,
get_user_and_encoding/3]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-define(DEFAULT_IRC_ENCODING, "koi8-r").
-record(irc_connection, {userserver, pid}).
-record(irc_custom, {userserver, data}).
-record(irc_connection, {jid_server_host, pid}).
-record(irc_custom, {us_host, data}).
start(Opts) ->
iconv:start(),
mnesia:create_table(irc_custom,
[{disc_copies, [node()]},
{attributes, record_info(fields, irc_custom)}]),
Host = gen_mod:get_opt(host, Opts, "irc." ++ ?MYNAME),
Hosts = gen_mod:get_hosts(Opts, "irc."),
Host = hd(Hosts),
update_table(Host),
Access = gen_mod:get_opt(access, Opts, all),
register(ejabberd_mod_irc, spawn(?MODULE, init, [Host, Access])).
register(ejabberd_mod_irc, spawn(?MODULE, init, [Hosts, Access])).
init(Host, Access) ->
init(Hosts, Access) ->
catch ets:new(irc_connection, [named_table,
public,
{keypos, #irc_connection.userserver}]),
ejabberd_router:register_route(Host),
loop(Host, Access).
{keypos, #irc_connection.jid_server_host}]),
ejabberd_router:register_routes(Hosts),
loop(Hosts, Access).
loop(Host, Access) ->
loop(Hosts, Access) ->
receive
{route, From, To, Packet} ->
case catch do_route(Host, Access, From, To, Packet) of
case catch do_route(To#jid.lserver, Access, From, To, Packet) of
{'EXIT', Reason} ->
?ERROR_MSG("~p", [Reason]);
_ ->
ok
end,
loop(Host, Access);
loop(Hosts, Access);
stop ->
ejabberd_router:unregister_global_route(Host),
ejabberd_router:unregister_routes(Hosts),
ok;
_ ->
loop(Host, Access)
loop(Hosts, Access)
end.
@ -96,7 +99,7 @@ do_route1(Host, From, To, Packet) ->
From,
jlib:iq_to_xml(Res));
#iq{xmlns = ?NS_REGISTER} = IQ ->
process_register(From, To, IQ);
process_register(Host, From, To, IQ);
#iq{type = get, xmlns = ?NS_VCARD = XMLNS,
lang = Lang} = IQ ->
Res = IQ#iq{type = result,
@ -121,17 +124,17 @@ do_route1(Host, From, To, Packet) ->
_ ->
case string:tokens(ChanServ, "%") of
[[_ | _] = Channel, [_ | _] = Server] ->
case ets:lookup(irc_connection, {From, Server}) of
case ets:lookup(irc_connection, {From, Server, Host}) of
[] ->
io:format("open new connection~n"),
{Username, Encoding} = get_user_and_encoding(
From, Server),
Host, From, Server),
{ok, Pid} = mod_irc_connection:start(
From, Host, Server,
Username, Encoding),
ets:insert(
irc_connection,
#irc_connection{userserver = {From, Server},
#irc_connection{jid_server_host = {From, Server, Host},
pid = Pid}),
mod_irc_connection:route_chan(
Pid, Channel, Resource, Packet),
@ -147,7 +150,7 @@ do_route1(Host, From, To, Packet) ->
_ ->
case string:tokens(ChanServ, "!") of
[[_ | _] = Nick, [_ | _] = Server] ->
case ets:lookup(irc_connection, {From, Server}) of
case ets:lookup(irc_connection, {From, Server, Host}) of
[] ->
Err = jlib:make_error_reply(
Packet, ?ERR_SERVICE_UNAVAILABLE),
@ -175,8 +178,8 @@ stop() ->
ejabberd_mod_irc ! stop,
ok.
closed_conection(From, Server) ->
ets:delete(irc_connection, {From, Server}).
closed_connection(Host, From, Server) ->
ets:delete(irc_connection, {From, Server, Host}).
iq_disco() ->
@ -201,8 +204,8 @@ iq_get_vcard(Lang) ->
[{xmlcdata, translate:translate(Lang, "ejabberd IRC module\n"
"Copyright (c) 2003-2005 Alexey Shchepin")}]}].
process_register(From, To, #iq{} = IQ) ->
case catch process_irc_register(From, To, IQ) of
process_register(Host, From, To, #iq{} = IQ) ->
case catch process_irc_register(Host, From, To, IQ) of
{'EXIT', Reason} ->
?ERROR_MSG("~p", [Reason]);
ResIQ ->
@ -232,7 +235,7 @@ find_xdata_el1([{xmlelement, Name, Attrs, SubEls} | Els]) ->
find_xdata_el1([_ | Els]) ->
find_xdata_el1(Els).
process_irc_register(From, To,
process_irc_register(Host, From, To,
#iq{type = Type, xmlns = XMLNS,
lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
@ -257,7 +260,8 @@ process_irc_register(From, To,
Node = string:tokens(
xml:get_tag_attr_s("node", SubEl),
"/"),
case set_form(From, Node, Lang, XData) of
case set_form(
Host, From, Node, Lang, XData) of
{result, Res} ->
IQ#iq{type = result,
sub_el = [{xmlelement, "query",
@ -277,7 +281,7 @@ process_irc_register(From, To,
get ->
Node =
string:tokens(xml:get_tag_attr_s("node", SubEl), "/"),
case get_form(From, Node, Lang) of
case get_form(Host, From, Node, Lang) of
{result, Res} ->
IQ#iq{type = result,
sub_el = [{xmlelement, "query",
@ -292,11 +296,12 @@ process_irc_register(From, To,
get_form(From, [], Lang) ->
get_form(Host, From, [], Lang) ->
#jid{user = User, server = Server,
luser = LUser, lserver = LServer} = From,
US = {LUser, LServer},
Customs =
case catch mnesia:dirty_read({irc_custom, {LUser, LServer}}) of
case catch mnesia:dirty_read({irc_custom, {US, Host}}) of
{'EXIT', Reason} ->
{error, ?ERR_INTERNAL_SERVER_ERROR};
[] ->
@ -306,7 +311,7 @@ get_form(From, [], Lang) ->
xml:get_attr_s(encodings, Data)}
end,
case Customs of
{error, _, _} ->
{error, _Error} ->
Customs;
{Username, Encodings} ->
{result,
@ -370,15 +375,15 @@ get_form(From, [], Lang) ->
]}]}
end;
get_form(_, _, Lang) ->
get_form(_Host, _, _, Lang) ->
{error, ?ERR_SERVICE_UNAVAILABLE}.
set_form(From, [], Lang, XData) ->
set_form(Host, From, [], Lang, XData) ->
{LUser, LServer, _} = jlib:jid_tolower(From),
US = {LUser, LServer},
case {lists:keysearch("username", 1, XData),
lists:keysearch("encodings", 1, XData)} of
{{value, {_, [Username]}}, {value, {_, Strings}}} ->
@ -392,8 +397,8 @@ set_form(From, [], Lang, XData) ->
case mnesia:transaction(
fun() ->
mnesia:write(
#irc_custom{userserver =
{LUser, LServer},
#irc_custom{us_host =
{US, Host},
data =
[{username,
Username},
@ -416,14 +421,15 @@ set_form(From, [], Lang, XData) ->
end;
set_form(_, _, Lang, XData) ->
set_form(_Host, _, _, Lang, XData) ->
{error, ?ERR_SERVICE_UNAVAILABLE}.
get_user_and_encoding(From, IRCServer) ->
get_user_and_encoding(Host, From, IRCServer) ->
#jid{user = User, server = Server,
luser = LUser, lserver = LServer} = From,
case catch mnesia:dirty_read({irc_custom, {LUser, LServer}}) of
US = {LUser, LServer},
case catch mnesia:dirty_read({irc_custom, {US, Host}}) of
{'EXIT', Reason} ->
{User, ?DEFAULT_IRC_ENCODING};
[] ->
@ -436,3 +442,44 @@ get_user_and_encoding(From, IRCServer) ->
end}
end.
update_table(Host) ->
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)
end.

View File

@ -32,7 +32,7 @@
-define(SETS, gb_sets).
-record(state, {socket, encoding, receiver, queue,
user, myname, server, nick,
user, host, server, nick,
channels = dict:new(),
inbuf = "", outbuf = ""}).
@ -67,7 +67,7 @@ init([From, Host, Server, Username, Encoding]) ->
encoding = Encoding,
user = From,
nick = Username,
myname = Host,
host = Host,
server = Server}}.
%%----------------------------------------------------------------------
@ -90,7 +90,7 @@ open_socket(init, StateData) ->
"USER ~s ~s ~s :~s\r\n",
[StateData#state.nick,
StateData#state.nick,
StateData#state.myname,
StateData#state.host,
StateData#state.nick])),
send_text(NewStateData,
io_lib:format("CODEPAGE ~s\r\n", [StateData#state.encoding])),
@ -231,7 +231,7 @@ handle_info({route_chan, Channel, Resource,
jlib:make_jid(
lists:concat(
[Channel, "%", StateData#state.server]),
StateData#state.myname, StateData#state.nick),
StateData#state.host, StateData#state.nick),
StateData#state.user, El),
Body = xml:get_path_s(El, [{elem, "body"}, cdata]),
case Body of
@ -304,7 +304,7 @@ handle_info({route_chan, Channel, Resource,
StateName, StateData) ->
From = StateData#state.user,
To = jlib:make_jid(lists:concat([Channel, "%", StateData#state.server]),
StateData#state.myname, StateData#state.nick),
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);
@ -474,7 +474,8 @@ handle_info({tcp_error, Socket, Reason}, StateName, StateData) ->
%% Returns: any
%%----------------------------------------------------------------------
terminate(Reason, StateName, StateData) ->
mod_irc:closed_conection(StateData#state.user,
mod_irc:closed_connection(StateData#state.host,
StateData#state.user,
StateData#state.server),
bounce_messages("Server Connect Failed"),
lists:foreach(
@ -482,7 +483,7 @@ terminate(Reason, StateName, StateData) ->
ejabberd_router:route(
jlib:make_jid(
lists:concat([Chan, "%", StateData#state.server]),
StateData#state.myname, StateData#state.nick),
StateData#state.host, StateData#state.nick),
StateData#state.user,
{xmlelement, "presence", [{"type", "error"}],
[{xmlelement, "error", [{"code", "502"}],
@ -592,7 +593,7 @@ process_channel_list_user(StateData, Chan, User) ->
end,
ejabberd_router:route(
jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
StateData#state.myname, User2),
StateData#state.host, User2),
StateData#state.user,
{xmlelement, "presence", [],
[{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
@ -618,7 +619,7 @@ process_channel_topic(StateData, Chan, String) ->
ejabberd_router:route(
jlib:make_jid(
lists:concat([Chan, "%", StateData#state.server]),
StateData#state.myname, FromUser),
StateData#state.host, FromUser),
StateData#state.user,
{xmlelement, "message", [{"type", "groupchat"}],
[{xmlelement, "subject", [], [{xmlcdata, Msg1}]}]}).
@ -636,7 +637,7 @@ process_chanprivmsg(StateData, Chan, From, String) ->
Msg2 = filter_message(Msg1),
ejabberd_router:route(
jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
StateData#state.myname, FromUser),
StateData#state.host, FromUser),
StateData#state.user,
{xmlelement, "message", [{"type", "groupchat"}],
[{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}).
@ -655,7 +656,7 @@ process_channotice(StateData, Chan, From, String) ->
Msg2 = filter_message(Msg1),
ejabberd_router:route(
jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
StateData#state.myname, FromUser),
StateData#state.host, FromUser),
StateData#state.user,
{xmlelement, "message", [{"type", "groupchat"}],
[{xmlelement, "body", [], [{xmlcdata, "NOTICE: " ++ Msg2}]}]}).
@ -675,7 +676,7 @@ process_privmsg(StateData, Nick, From, String) ->
Msg2 = filter_message(Msg1),
ejabberd_router:route(
jlib:make_jid(lists:concat([FromUser, "!", StateData#state.server]),
StateData#state.myname, ""),
StateData#state.host, ""),
StateData#state.user,
{xmlelement, "message", [{"type", "chat"}],
[{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}).
@ -693,7 +694,7 @@ process_notice(StateData, Nick, From, String) ->
Msg2 = filter_message(Msg1),
ejabberd_router:route(
jlib:make_jid(lists:concat([FromUser, "!", StateData#state.server]),
StateData#state.myname, ""),
StateData#state.host, ""),
StateData#state.user,
{xmlelement, "message", [{"type", "chat"}],
[{xmlelement, "body", [], [{xmlcdata, "NOTICE: " ++ Msg2}]}]}).
@ -719,7 +720,7 @@ process_topic(StateData, Chan, From, String) ->
Msg1 = filter_message(Msg),
ejabberd_router:route(
jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
StateData#state.myname, FromUser),
StateData#state.host, FromUser),
StateData#state.user,
{xmlelement, "message", [{"type", "groupchat"}],
[{xmlelement, "subject", [], [{xmlcdata, Msg1}]},
@ -733,7 +734,7 @@ process_part(StateData, Chan, From, String) ->
Msg1 = filter_message(Msg),
ejabberd_router:route(
jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
StateData#state.myname, FromUser),
StateData#state.host, FromUser),
StateData#state.user,
{xmlelement, "message", [{"type", "groupchat"}],
[{xmlelement, "body", [],
@ -742,7 +743,7 @@ process_part(StateData, Chan, From, String) ->
ejabberd_router:route(
jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
StateData#state.myname, FromUser),
StateData#state.host, FromUser),
StateData#state.user,
{xmlelement, "presence", [{"type", "unavailable"}],
[{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
@ -777,7 +778,7 @@ process_quit(StateData, From, String) ->
ejabberd_router:route(
jlib:make_jid(
lists:concat([Chan, "%", StateData#state.server]),
StateData#state.myname, FromUser),
StateData#state.host, FromUser),
StateData#state.user,
{xmlelement, "message", [{"type", "groupchat"}],
[{xmlelement, "body", [],
@ -787,7 +788,7 @@ process_quit(StateData, From, String) ->
ejabberd_router:route(
jlib:make_jid(
lists:concat([Chan, "%", StateData#state.server]),
StateData#state.myname, FromUser),
StateData#state.host, FromUser),
StateData#state.user,
{xmlelement, "presence", [{"type", "unavailable"}],
[{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
@ -811,7 +812,7 @@ process_join(StateData, Channel, From, String) ->
Chan = lists:subtract(Channel, ":#"),
ejabberd_router:route(
jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
StateData#state.myname, FromUser),
StateData#state.host, FromUser),
StateData#state.user,
{xmlelement, "presence", [],
[{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
@ -825,7 +826,7 @@ process_join(StateData, Channel, From, String) ->
Msg1 = filter_message(Msg),
ejabberd_router:route(
jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
StateData#state.myname, FromUser),
StateData#state.host, FromUser),
StateData#state.user,
{xmlelement, "message", [{"type", "groupchat"}],
[{xmlelement, "body", [],
@ -848,7 +849,7 @@ 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.myname, Nick),
StateData#state.host, Nick),
StateData#state.user,
{xmlelement, "presence", [],
[{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
@ -861,7 +862,7 @@ process_kick(StateData, Chan, From, Nick) ->
%Msg = lists:last(string:tokens(String, ":")),
ejabberd_router:route(
jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
StateData#state.myname, Nick),
StateData#state.host, Nick),
StateData#state.user,
{xmlelement, "presence", [{"type", "unavailable"}],
[{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
@ -883,7 +884,7 @@ process_nick(StateData, From, NewNick) ->
ejabberd_router:route(
jlib:make_jid(
lists:concat([Chan, "%", StateData#state.server]),
StateData#state.myname, FromUser),
StateData#state.host, FromUser),
StateData#state.user,
{xmlelement, "presence", [{"type", "unavailable"}],
[{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
@ -897,7 +898,7 @@ process_nick(StateData, From, NewNick) ->
ejabberd_router:route(
jlib:make_jid(
lists:concat([Chan, "%", StateData#state.server]),
StateData#state.myname, Nick),
StateData#state.host, Nick),
StateData#state.user,
{xmlelement, "presence", [],
[{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
@ -921,7 +922,7 @@ process_error(StateData, String) ->
ejabberd_router:route(
jlib:make_jid(
lists:concat([Chan, "%", StateData#state.server]),
StateData#state.myname, StateData#state.nick),
StateData#state.host, StateData#state.nick),
StateData#state.user,
{xmlelement, "presence", [{"type", "error"}],
[{xmlelement, "error", [{"code", "502"}],

View File

@ -16,13 +16,13 @@
stop/0,
process_local_iq/3,
process_sm_iq/3,
on_presence_update/3,
remove_user/1]).
on_presence_update/4,
remove_user/2]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(last_activity, {user, timestamp, status}).
-record(last_activity, {us, timestamp, status}).
start(Opts) ->
@ -68,24 +68,25 @@ process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
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, {none, []}, [User, From]),
roster_get_jid_info, {none, []}, [User, Server, From]),
if
(Subscription == both) or (Subscription == from) ->
case catch mod_privacy:get_user_list(User) of
{'EXIT', _Reason} ->
get_last(IQ, SubEl, User);
get_last(IQ, SubEl, User, Server);
List ->
case catch mod_privacy:check_packet(
User, List,
User, Server, List,
{From, To,
{xmlelement, "presence", [], []}},
out) of
{'EXIT', _Reason} ->
get_last(IQ, SubEl, User);
get_last(IQ, SubEl, User, Server);
allow ->
get_last(IQ, SubEl, User);
get_last(IQ, SubEl, User, Server);
deny ->
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
@ -97,8 +98,8 @@ process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
end
end.
get_last(IQ, SubEl, LUser) ->
case catch mnesia:dirty_read(last_activity, LUser) of
get_last(IQ, SubEl, LUser, LServer) ->
case catch mnesia:dirty_read(last_activity, {LUser, LServer}) of
{'EXIT', _Reason} ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
[] ->
@ -116,22 +117,26 @@ get_last(IQ, SubEl, LUser) ->
on_presence_update(User, _Resource, Status) ->
on_presence_update(User, Server, _Resource, Status) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
{MegaSecs, Secs, _MicroSecs} = now(),
TimeStamp = MegaSecs * 1000000 + Secs,
F = fun() ->
mnesia:write(#last_activity{user = LUser,
mnesia:write(#last_activity{us = US,
timestamp = TimeStamp,
status = Status})
end,
mnesia:transaction(F).
remove_user(User) ->
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
mnesia:delete({last_activity, LUser})
mnesia:delete({last_activity, US})
end,
mnesia:transaction(F).
@ -141,13 +146,44 @@ update_table() ->
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{user = U, timestamp = T, status = ""}
end, Fields);
#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)

View File

@ -72,7 +72,7 @@ process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
get_last(IQ, SubEl, User);
List ->
case catch mod_privacy:check_packet(
User, List,
User, ?MYNAME, List, % TODO
{From, To,
{xmlelement, "presence", [], []}},
out) of

View File

@ -15,20 +15,20 @@
-export([start/1,
init/2,
stop/0,
room_destroyed/1,
store_room/2,
restore_room/1,
forget_room/1,
room_destroyed/2,
store_room/3,
restore_room/2,
forget_room/2,
process_iq_disco_items/4,
can_use_nick/2]).
can_use_nick/3]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(muc_room, {name, opts}).
-record(muc_online_room, {name, pid}).
-record(muc_registered, {user, nick}).
-record(muc_room, {name_host, opts}).
-record(muc_online_room, {name_host, pid}).
-record(muc_registered, {us_host, nick}).
start(Opts) ->
@ -38,43 +38,45 @@ start(Opts) ->
mnesia:create_table(muc_registered,
[{disc_copies, [node()]},
{attributes, record_info(fields, muc_registered)}]),
Hosts = gen_mod:get_hosts(Opts, "conference."),
Host = hd(Hosts),
update_tables(Host),
mnesia:add_table_index(muc_registered, nick),
Host = gen_mod:get_opt(host, Opts, "conference." ++ ?MYNAME),
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),
register(ejabberd_mod_muc,
spawn(?MODULE, init, [Host, {Access, AccessCreate, AccessAdmin}])).
spawn(?MODULE, init,
[Hosts, {Access, AccessCreate, AccessAdmin}])).
init(Host, Access) ->
init(Hosts, Access) ->
catch ets:new(muc_online_room, [named_table,
public,
{keypos, #muc_online_room.name}]),
ejabberd_router:register_route(Host),
load_permanent_rooms(Host, Access),
loop(Host, Access).
{keypos, #muc_online_room.name_host}]),
ejabberd_router:register_routes(Hosts),
load_permanent_rooms(Access),
loop(Hosts, Access).
loop(Host, Access) ->
loop(Hosts, Access) ->
receive
{route, From, To, Packet} ->
case catch do_route(Host, Access, From, To, Packet) of
case catch do_route(To#jid.lserver, Access, From, To, Packet) of
{'EXIT', Reason} ->
?ERROR_MSG("~p", [Reason]);
_ ->
ok
end,
loop(Host, Access);
{room_destroyed, Room} ->
ets:delete(muc_online_room, Room),
loop(Host, Access);
loop(Hosts, Access);
{room_destroyed, RoomHost} ->
ets:delete(muc_online_room, RoomHost),
loop(Hosts, Access);
stop ->
% TODO
ejabberd_router:unregister_global_route(Host),
ejabberd_router:unregister_routes(Hosts),
ok;
_ ->
loop(Host, Access)
loop(Hosts, Access)
end.
@ -127,7 +129,7 @@ do_route1(Host, Access, From, To, Packet) ->
[{xmlelement, "query",
[{"xmlns", XMLNS}],
iq_get_register_info(
From, Host, Lang)}]},
Host, From, Lang)}]},
ejabberd_router:route(To,
From,
jlib:iq_to_xml(Res));
@ -135,7 +137,7 @@ do_route1(Host, Access, From, To, Packet) ->
xmlns = ?NS_REGISTER = XMLNS,
lang = Lang,
sub_el = SubEl} = IQ ->
case process_iq_register_set(From, SubEl, Lang) of
case process_iq_register_set(Host, From, SubEl, Lang) of
{result, IQRes} ->
Res = IQ#iq{type = result,
sub_el =
@ -180,7 +182,7 @@ do_route1(Host, Access, From, To, Packet) ->
Msg = xml:get_path_s(
Packet,
[{elem, "body"}, cdata]),
broadcast_service_message(Msg);
broadcast_service_message(Host, Msg);
_ ->
Lang = xml:get_attr_s("xml:lang", Attrs),
ErrText = "Only service administrators "
@ -208,7 +210,7 @@ do_route1(Host, Access, From, To, Packet) ->
end
end;
_ ->
case ets:lookup(muc_online_room, Room) of
case ets:lookup(muc_online_room, {Room, Host}) of
[] ->
Type = xml:get_attr_s("type", Attrs),
case {Name, Type} of
@ -220,7 +222,8 @@ do_route1(Host, Access, From, To, Packet) ->
Host, Access, Room, From, Nick),
ets:insert(
muc_online_room,
#muc_online_room{name = Room, pid = Pid}),
#muc_online_room{name_host = {Room, Host},
pid = Pid}),
mod_muc_room:route(Pid, From, Nick, Packet),
ok;
_ ->
@ -248,8 +251,8 @@ do_route1(Host, Access, From, To, Packet) ->
room_destroyed(Room) ->
ejabberd_mod_muc ! {room_destroyed, Room},
room_destroyed(Host, Room) ->
ejabberd_mod_muc ! {room_destroyed, {Room, Host}},
ok.
stop() ->
@ -257,15 +260,15 @@ stop() ->
ok.
store_room(Name, Opts) ->
store_room(Host, Name, Opts) ->
F = fun() ->
mnesia:write(#muc_room{name = Name,
mnesia:write(#muc_room{name_host = {Name, Host},
opts = Opts})
end,
mnesia:transaction(F).
restore_room(Name) ->
case catch mnesia:dirty_read(muc_room, Name) of
restore_room(Host, Name) ->
case catch mnesia:dirty_read(muc_room, {Name, Host}) of
[#muc_room{opts = Opts}] ->
Opts;
_ ->
@ -273,21 +276,21 @@ restore_room(Name) ->
end.
forget_room(Name) ->
forget_room(Host, Name) ->
F = fun() ->
mnesia:delete({muc_room, Name})
mnesia:delete({muc_room, {Name, Host}})
end,
mnesia:transaction(F).
load_permanent_rooms(Host, Access) ->
load_permanent_rooms(Access) ->
case catch mnesia:dirty_select(muc_room, [{'_', [], ['$_']}]) of
{'EXIT', Reason} ->
?ERROR_MSG("~p", [Reason]),
ok;
Rs ->
lists:foreach(fun(R) ->
Room = R#muc_room.name,
{Room, Host} = R#muc_room.name_host,
{ok, Pid} = mod_muc_room:start(
Host,
Access,
@ -295,7 +298,8 @@ load_permanent_rooms(Host, Access) ->
R#muc_room.opts),
ets:insert(
muc_online_room,
#muc_online_room{name = Room, pid = Pid})
#muc_online_room{name_host = {Room, Host},
pid = Pid})
end, Rs)
end.
@ -320,7 +324,7 @@ process_iq_disco_items(Host, From, To, #iq{lang = Lang} = IQ) ->
jlib:iq_to_xml(Res)).
iq_disco_items(Host, From, Lang) ->
lists:zf(fun(#muc_online_room{name = Name, pid = Pid}) ->
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} ->
@ -331,7 +335,7 @@ iq_disco_items(Host, From, Lang) ->
_ ->
false
end
end, ets:tab2list(muc_online_room)).
end, get_vh_rooms(Host)).
-define(XFIELD(Type, Label, Var, Val),
@ -340,10 +344,11 @@ iq_disco_items(Host, From, Lang) ->
{"var", Var}],
[{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
iq_get_register_info(From, Host, Lang) ->
iq_get_register_info(Host, From, Lang) ->
{LUser, LServer, _} = jlib:jid_tolower(From),
LUS = {LUser, LServer},
{Nick, Registered} = case catch mnesia:dirty_read(muc_registered, LUS) of
{Nick, Registered} =
case catch mnesia:dirty_read(muc_registered, {LUS, Host}) of
{'EXIT', _Reason} ->
{"", []};
[] ->
@ -368,7 +373,7 @@ iq_get_register_info(From, Host, Lang) ->
Lang, "Enter nickname you want to register")}]},
?XFIELD("text-single", "Nickname", "nick", Nick)]}].
iq_set_register_info(From, XData, Lang) ->
iq_set_register_info(Host, From, XData, Lang) ->
{LUser, LServer, _} = jlib:jid_tolower(From),
LUS = {LUser, LServer},
case lists:keysearch("nick", 1, XData) of
@ -379,22 +384,26 @@ iq_set_register_info(From, XData, Lang) ->
F = fun() ->
case Nick of
"" ->
mnesia:delete({muc_registered, LUS}),
mnesia:delete({muc_registered, {LUS, Host}}),
ok;
_ ->
Allow = case mnesia:index_read(
Allow =
case mnesia:select(
muc_registered,
Nick,
#muc_registered.nick) of
[{#muc_registered{us_host = '$1',
nick = Nick,
_ = '_'},
[{'==', {element, 2, '$1'}, Host}],
['$_']}]) of
[] ->
true;
[#muc_registered{user = U}] ->
[#muc_registered{us_host = {U, _Host}}] ->
U == LUS
end,
if
Allow ->
mnesia:write(
#muc_registered{user = LUS,
#muc_registered{us_host = {LUS, Host},
nick = Nick}),
ok;
true ->
@ -413,7 +422,7 @@ iq_set_register_info(From, XData, Lang) ->
end
end.
process_iq_register_set(From, SubEl, Lang) ->
process_iq_register_set(Host, From, SubEl, Lang) ->
{xmlelement, _Name, _Attrs, Els} = SubEl,
case xml:remove_cdata(Els) of
[{xmlelement, "x", _Attrs1, _Els1} = XEl] ->
@ -427,7 +436,7 @@ process_iq_register_set(From, SubEl, Lang) ->
invalid ->
{error, ?ERR_BAD_REQUEST};
_ ->
iq_set_register_info(From, XData, Lang)
iq_set_register_info(Host, From, XData, Lang)
end;
_ ->
{error, ?ERR_BAD_REQUEST}
@ -447,30 +456,125 @@ iq_get_vcard(Lang) ->
"Copyright (c) 2003-2005 Alexey Shchepin")}]}].
broadcast_service_message(Msg) ->
broadcast_service_message(Host, Msg) ->
lists:foreach(
fun(#muc_online_room{pid = Pid}) ->
gen_fsm:send_all_state_event(
Pid, {service_message, Msg})
end, ets:tab2list(muc_online_room)).
end, get_vh_rooms(Host)).
get_vh_rooms(Host) ->
ets:select(muc_online_room,
[{#muc_online_room{name_host = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, Host}],
['$_']}]).
can_use_nick(_JID, "") ->
can_use_nick(_Host, _JID, "") ->
false;
can_use_nick(JID, Nick) ->
can_use_nick(Host, JID, Nick) ->
{LUser, LServer, _} = jlib:jid_tolower(JID),
LUS = {LUser, LServer},
case catch mnesia:dirty_index_read(muc_registered,
Nick,
#muc_registered.nick) of
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{user = U}] ->
[#muc_registered{us_host = {U, _Host}}] ->
U == LUS
end.
update_tables(Host) ->
update_muc_room_table(Host),
update_muc_registered_table(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)
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)
end.

View File

@ -149,6 +149,7 @@ normal_state({route, From, "",
case (NSD#state.config)#config.persistent of
true ->
mod_muc:store_room(
NSD#state.host,
NSD#state.room,
make_opts(NSD));
_ ->
@ -364,7 +365,8 @@ normal_state({route, From, Nick,
case is_nick_change(From, Nick, StateData) of
true ->
case {is_nick_exists(Nick, StateData),
mod_muc:can_use_nick(From, Nick)} of
mod_muc:can_use_nick(
StateData#state.host, From, Nick)} of
{true, _} ->
Lang = xml:get_attr_s("xml:lang", Attrs),
ErrText = "Nickname is already in use by another occupant",
@ -639,7 +641,7 @@ handle_info(_Info, StateName, StateData) ->
%% Returns: any
%%----------------------------------------------------------------------
terminate(_Reason, _StateName, StateData) ->
mod_muc:room_destroyed(StateData#state.room),
mod_muc:room_destroyed(StateData#state.host, StateData#state.room),
ok.
%%%----------------------------------------------------------------------
@ -806,8 +808,8 @@ filter_presence({xmlelement, "presence", Attrs, Els}) ->
case El of
{xmlcdata, _} ->
false;
{xmlelement, Name1, Attrs1, _Els1} ->
XMLNS = xml:get_attr_s("xmlns", Attrs1),
{xmlelement, Name1, _Attrs1, _Els1} ->
XMLNS = xml:get_attr_s("xmlns", Attrs),
case {Name1, XMLNS} of
{"show", ""} ->
true;
@ -872,7 +874,7 @@ is_nick_change(JID, Nick, StateData) ->
add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) ->
Lang = xml:get_attr_s("xml:lang", Attrs),
case {is_nick_exists(Nick, StateData),
mod_muc:can_use_nick(From, Nick)} of
mod_muc:can_use_nick(StateData#state.host, From, Nick)} of
{true, _} ->
ErrText = "Nickname is already in use by another occupant",
Err = jlib:make_error_reply(Packet, ?ERRT_CONFLICT(Lang, ErrText)),
@ -1517,7 +1519,8 @@ process_admin_items_set(UJID, Items, Lang, StateData) ->
io:format("MUC SET: ~p~n", [Res]),
case (NSD#state.config)#config.persistent of
true ->
mod_muc:store_room(NSD#state.room, make_opts(NSD));
mod_muc:store_room(NSD#state.host, NSD#state.room,
make_opts(NSD));
_ ->
ok
end,
@ -2043,9 +2046,9 @@ change_config(Config, StateData) ->
case {(StateData#state.config)#config.persistent,
Config#config.persistent} of
{_, true} ->
mod_muc:store_room(NSD#state.room, make_opts(NSD));
mod_muc:store_room(NSD#state.host, NSD#state.room, make_opts(NSD));
{true, false} ->
mod_muc:forget_room(NSD#state.room);
mod_muc:forget_room(NSD#state.host, NSD#state.room);
{false, false} ->
ok
end,
@ -2130,7 +2133,7 @@ destroy_room(DEls, StateData) ->
end, ?DICT:to_list(StateData#state.users)),
case (StateData#state.config)#config.persistent of
true ->
mod_muc:forget_room(StateData#state.room);
mod_muc:forget_room(StateData#state.host, StateData#state.room);
false ->
ok
end,

View File

@ -15,16 +15,16 @@
init/0,
stop/0,
store_packet/3,
resend_offline_messages/1,
pop_offline_messages/2,
resend_offline_messages/2,
pop_offline_messages/3,
remove_expired_messages/0,
remove_old_messages/1,
remove_user/1]).
remove_user/2]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(offline_msg, {user, timestamp, expire, from, to, packet}).
-record(offline_msg, {us, timestamp, expire, from, to, packet}).
-define(PROCNAME, ejabberd_offline).
-define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000).
@ -97,11 +97,11 @@ store_packet(From, To, Packet) ->
(Type /= "error") and (Type /= "groupchat") ->
case check_event(From, To, Packet) of
true ->
#jid{luser = LUser} = To,
#jid{luser = LUser, lserver = LServer} = To,
TimeStamp = now(),
{xmlelement, _Name, _Attrs, Els} = Packet,
Expire = find_x_expire(TimeStamp, Els),
?PROCNAME ! #offline_msg{user = LUser,
?PROCNAME ! #offline_msg{us = {LUser, LServer},
timestamp = TimeStamp,
expire = Expire,
from = From,
@ -189,11 +189,13 @@ find_x_expire(TimeStamp, [El | Els]) ->
end.
resend_offline_messages(User) ->
resend_offline_messages(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
Rs = mnesia:wread({offline_msg, LUser}),
mnesia:delete({offline_msg, LUser}),
Rs = mnesia:wread({offline_msg, US}),
mnesia:delete({offline_msg, US}),
Rs
end,
case mnesia:transaction(F) of
@ -216,11 +218,13 @@ resend_offline_messages(User) ->
ok
end.
pop_offline_messages(Ls, User) ->
pop_offline_messages(Ls, User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
Rs = mnesia:wread({offline_msg, LUser}),
mnesia:delete({offline_msg, LUser}),
Rs = mnesia:wread({offline_msg, US}),
mnesia:delete({offline_msg, US}),
Rs
end,
case mnesia:transaction(F) of
@ -290,10 +294,12 @@ remove_old_messages(Days) ->
end,
mnesia:transaction(F).
remove_user(User) ->
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
mnesia:delete({offline_msg, LUser})
mnesia:delete({offline_msg, US})
end,
mnesia:transaction(F).
@ -302,23 +308,83 @@ update_table() ->
case mnesia:table_info(offline_msg, attributes) of
Fields ->
ok;
[user, timestamp, expire, from, to, packet] ->
?INFO_MSG("Converting offline_msg table from "
"{user, timestamp, expire, from, to, packet} format", []),
Host = ?MYNAME,
{atomic, ok} = mnesia:create_table(
mod_offline_tmp_table,
[{disc_only_copies, [node()]},
{type, bag},
{local_content, true},
{record_name, offline_msg},
{attributes, record_info(fields, offline_msg)}]),
mnesia:transform_table(offline_msg, ignore, Fields),
F1 = fun() ->
mnesia:write_lock_table(mod_offline_tmp_table),
mnesia:foldl(
fun(#offline_msg{us = U} = R, _) ->
mnesia:dirty_write(
mod_offline_tmp_table,
R#offline_msg{us = {U, Host}})
end, ok, offline_msg)
end,
mnesia:transaction(F1),
mnesia:clear_table(offline_msg),
F2 = fun() ->
mnesia:write_lock_table(offline_msg),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, mod_offline_tmp_table)
end,
mnesia:transaction(F2),
mnesia:delete_table(mod_offline_tmp_table);
[user, timestamp, from, to, packet] ->
?INFO_MSG("Converting offline_msg table from "
"{user, timestamp, from, to, packet} format", []),
Host = ?MYNAME,
{atomic, ok} = mnesia:create_table(
mod_offline_tmp_table,
[{disc_only_copies, [node()]},
{type, bag},
{local_content, true},
{record_name, offline_msg},
{attributes, record_info(fields, offline_msg)}]),
mnesia:transform_table(
offline_msg,
fun({_, U, TS, F, T, P}) ->
{xmlelement, _Name, _Attrs, Els} = P,
Expire = find_x_expire(TS, Els),
#offline_msg{user = U,
#offline_msg{us = U,
timestamp = TS,
expire = Expire,
from = F,
to = T,
packet = P}
end, Fields);
end, Fields),
F1 = fun() ->
mnesia:write_lock_table(mod_offline_tmp_table),
mnesia:foldl(
fun(#offline_msg{us = U} = R, _) ->
mnesia:dirty_write(
mod_offline_tmp_table,
R#offline_msg{us = {U, Host}})
end, ok, offline_msg)
end,
mnesia:transaction(F1),
mnesia:clear_table(offline_msg),
F2 = fun() ->
mnesia:write_lock_table(offline_msg),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, mod_offline_tmp_table)
end,
mnesia:transaction(F2),
mnesia:delete_table(mod_offline_tmp_table);
_ ->
?INFO_MSG("Recreating offline_msg table", []),
mnesia:transform_table(last_activity, ignore, Fields)
mnesia:transform_table(offline_msg, ignore, Fields)
end.

View File

@ -16,15 +16,15 @@
process_iq/3,
process_iq_set/3,
process_iq_get/4,
get_user_list/1,
check_packet/4,
get_user_list/2,
check_packet/5,
updated_list/2]).
%-include_lib("mnemosyne/include/mnemosyne.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(privacy, {user,
-record(privacy, {us,
default = none,
lists = []}).
@ -46,6 +46,7 @@ start(Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
mnesia:create_table(privacy, [{disc_copies, [node()]},
{attributes, record_info(fields, privacy)}]),
update_table(),
gen_iq_handler:add_iq_handler(ejabberd_sm, ?NS_PRIVACY,
?MODULE, process_iq, IQDisc).
@ -78,16 +79,16 @@ process_iq(From, _To, IQ) ->
process_iq_get(From, _To, #iq{sub_el = SubEl},
#userlist{name = Active}) ->
#jid{luser = LUser} = From,
#jid{luser = LUser, lserver = LServer} = From,
{xmlelement, _, _, Els} = SubEl,
case xml:remove_cdata(Els) of
[] ->
process_lists_get(LUser, Active);
process_lists_get(LUser, LServer, Active);
[{xmlelement, Name, Attrs, _SubEls}] ->
case Name of
"list" ->
ListName = xml:get_attr("name", Attrs),
process_list_get(LUser, ListName);
process_list_get(LUser, LServer, ListName);
_ ->
{error, ?ERR_BAD_REQUEST}
end;
@ -96,8 +97,8 @@ process_iq_get(From, _To, #iq{sub_el = SubEl},
end.
process_lists_get(LUser, Active) ->
case catch mnesia:dirty_read(privacy, LUser) of
process_lists_get(LUser, LServer, Active) ->
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
{'EXIT', _Reason} ->
{error, ?ERR_INTERNAL_SERVER_ERROR};
[] ->
@ -135,8 +136,8 @@ process_lists_get(LUser, Active) ->
end
end.
process_list_get(LUser, {value, Name}) ->
case catch mnesia:dirty_read(privacy, LUser) of
process_list_get(LUser, LServer, {value, Name}) ->
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
{'EXIT', _Reason} ->
{error, ?ERR_INTERNAL_SERVER_ERROR};
[] ->
@ -155,7 +156,7 @@ process_list_get(LUser, {value, Name}) ->
end
end;
process_list_get(_LUser, false) ->
process_list_get(_LUser, _LServer, false) ->
{error, ?ERR_BAD_REQUEST}.
item_to_xml(Item) ->
@ -242,18 +243,19 @@ list_to_action(S) ->
process_iq_set(From, _To, #iq{sub_el = SubEl}) ->
#jid{luser = LUser} = From,
#jid{luser = LUser, lserver = LServer} = From,
{xmlelement, _, _, Els} = SubEl,
case xml:remove_cdata(Els) of
[{xmlelement, Name, Attrs, SubEls}] ->
ListName = xml:get_attr("name", Attrs),
case Name of
"list" ->
process_list_set(LUser, ListName, xml:remove_cdata(SubEls));
process_list_set(LUser, LServer, ListName,
xml:remove_cdata(SubEls));
"active" ->
process_active_set(LUser, ListName);
process_active_set(LUser, LServer, ListName);
"default" ->
process_default_set(LUser, ListName);
process_default_set(LUser, LServer, ListName);
_ ->
{error, ?ERR_BAD_REQUEST}
end;
@ -262,9 +264,9 @@ process_iq_set(From, _To, #iq{sub_el = SubEl}) ->
end.
process_default_set(LUser, {value, Name}) ->
process_default_set(LUser, LServer, {value, Name}) ->
F = fun() ->
case mnesia:read({privacy, LUser}) of
case mnesia:read({privacy, {LUser, LServer}}) of
[] ->
{error, ?ERR_ITEM_NOT_FOUND};
[#privacy{lists = Lists} = P] ->
@ -287,9 +289,9 @@ process_default_set(LUser, {value, Name}) ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end;
process_default_set(LUser, false) ->
process_default_set(LUser, LServer, false) ->
F = fun() ->
case mnesia:read({privacy, LUser}) of
case mnesia:read({privacy, {LUser, LServer}}) of
[] ->
{result, []};
[R] ->
@ -307,8 +309,8 @@ process_default_set(LUser, false) ->
end.
process_active_set(LUser, {value, Name}) ->
case catch mnesia:dirty_read(privacy, LUser) of
process_active_set(LUser, LServer, {value, Name}) ->
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
[] ->
{error, ?ERR_ITEM_NOT_FOUND};
[#privacy{lists = Lists}] ->
@ -320,7 +322,7 @@ process_active_set(LUser, {value, Name}) ->
end
end;
process_active_set(_LUser, false) ->
process_active_set(_LUser, _LServer, false) ->
{result, [], #userlist{}}.
@ -328,14 +330,14 @@ process_active_set(_LUser, false) ->
process_list_set(LUser, {value, Name}, Els) ->
process_list_set(LUser, LServer, {value, Name}, Els) ->
case parse_items(Els) of
false ->
{error, ?ERR_BAD_REQUEST};
remove ->
F =
fun() ->
case mnesia:read({privacy, LUser}) of
case mnesia:read({privacy, {LUser, LServer}}) of
[] ->
{result, []};
[#privacy{default = Default, lists = Lists} = P] ->
@ -368,10 +370,10 @@ process_list_set(LUser, {value, Name}, Els) ->
List ->
F =
fun() ->
case mnesia:wread({privacy, LUser}) of
case mnesia:wread({privacy, {LUser, LServer}}) of
[] ->
NewLists = [{Name, List}],
mnesia:write(#privacy{user = LUser,
mnesia:write(#privacy{us = {LUser, LServer},
lists = NewLists}),
{result, []};
[#privacy{lists = Lists} = P] ->
@ -396,7 +398,7 @@ process_list_set(LUser, {value, Name}, Els) ->
end
end;
process_list_set(_LUser, false, _Els) ->
process_list_set(_LUser, _LServer, false, _Els) ->
{error, ?ERR_BAD_REQUEST}.
@ -511,9 +513,10 @@ parse_matches1(_Item, [{xmlelement, _, _, _} | _Els]) ->
get_user_list(User) ->
get_user_list(User, Server) ->
LUser = jlib:nodeprep(User),
case catch mnesia:dirty_read(privacy, LUser) of
LServer = jlib:nameprep(Server),
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
[] ->
#userlist{};
[#privacy{default = Default, lists = Lists}] ->
@ -534,7 +537,7 @@ get_user_list(User) ->
end.
check_packet(User,
check_packet(User, Server,
#userlist{list = List},
{From, To, {xmlelement, PName, _, _}},
Dir) ->
@ -552,28 +555,32 @@ check_packet(User,
LJID = jlib:jid_tolower(From),
{Subscription, Groups} =
ejabberd_hooks:run_fold(
roster_get_jid_info, {none, []}, [User, LJID]),
roster_get_jid_info, {none, []},
[User, Server, LJID]),
check_packet_aux(List, message,
LJID, Subscription, Groups);
{iq, in} ->
LJID = jlib:jid_tolower(From),
{Subscription, Groups} =
ejabberd_hooks:run_fold(
roster_get_jid_info, {none, []}, [User, LJID]),
roster_get_jid_info, {none, []},
[User, Server, LJID]),
check_packet_aux(List, iq,
LJID, Subscription, Groups);
{presence, in} ->
LJID = jlib:jid_tolower(From),
{Subscription, Groups} =
ejabberd_hooks:run_fold(
roster_get_jid_info, {none, []}, [User, LJID]),
roster_get_jid_info, {none, []},
[User, Server, LJID]),
check_packet_aux(List, presence_in,
LJID, Subscription, Groups);
{presence, out} ->
LJID = jlib:jid_tolower(To),
{Subscription, Groups} =
ejabberd_hooks:run_fold(
roster_get_jid_info, {none, []}, [User, LJID]),
roster_get_jid_info, {none, []},
[User, Server, LJID]),
check_packet_aux(List, presence_out,
LJID, Subscription, Groups);
_ ->
@ -662,4 +669,46 @@ updated_list(#userlist{name = OldName} = Old,
update_table() ->
Fields = record_info(fields, privacy),
case mnesia:table_info(privacy, attributes) of
Fields ->
ok;
[user, default, lists] ->
?INFO_MSG("Converting privacy table from "
"{user, default, lists} format", []),
Host = ?MYNAME,
{atomic, ok} = mnesia:create_table(
mod_privacy_tmp_table,
[{disc_only_copies, [node()]},
{type, bag},
{local_content, true},
{record_name, privacy},
{attributes, record_info(fields, privacy)}]),
mnesia:transform_table(privacy, ignore, Fields),
F1 = fun() ->
mnesia:write_lock_table(mod_privacy_tmp_table),
mnesia:foldl(
fun(#privacy{us = U} = R, _) ->
mnesia:dirty_write(
mod_privacy_tmp_table,
R#privacy{us = {U, Host}})
end, ok, privacy)
end,
mnesia:transaction(F1),
mnesia:clear_table(privacy),
F2 = fun() ->
mnesia:write_lock_table(privacy),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, mod_privacy_tmp_table)
end,
mnesia:transaction(F2),
mnesia:delete_table(mod_privacy_tmp_table);
_ ->
?INFO_MSG("Recreating privacy table", []),
mnesia:transform_table(privacy, ignore, Fields)
end.

View File

@ -15,18 +15,19 @@
-export([start/1,
stop/0,
process_sm_iq/3,
remove_user/1]).
remove_user/2]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(private_storage, {userns, xml}).
-record(private_storage, {usns, xml}).
start(Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
mnesia:create_table(private_storage,
[{disc_only_copies, [node()]},
{attributes, record_info(fields, private_storage)}]),
update_table(),
ejabberd_hooks:add(remove_user,
?MODULE, remove_user, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, ?NS_PRIVATE,
@ -40,22 +41,22 @@ stop() ->
process_sm_iq(From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
#jid{luser = LUser, lserver = LServer} = From,
case ?MYNAME of
LServer ->
case lists:member(LServer, ?MYHOSTS) of
true ->
{xmlelement, Name, Attrs, Els} = SubEl,
case Type of
set ->
F = fun() ->
lists:foreach(
fun(El) ->
set_data(LUser, El)
set_data(LUser, LServer, El)
end, Els)
end,
mnesia:transaction(F),
IQ#iq{type = result,
sub_el = [{xmlelement, Name, Attrs, []}]};
get ->
case catch get_data(LUser, Els) of
case catch get_data(LUser, LServer, Els) of
{'EXIT', _Reason} ->
IQ#iq{type = error,
sub_el = [SubEl,
@ -65,11 +66,11 @@ process_sm_iq(From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
sub_el = [{xmlelement, Name, Attrs, Res}]}
end
end;
_ ->
false ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
end.
set_data(LUser, El) ->
set_data(LUser, LServer, El) ->
case El of
{xmlelement, _Name, Attrs, _Els} ->
XMLNS = xml:get_attr_s("xmlns", Attrs),
@ -77,40 +78,45 @@ set_data(LUser, El) ->
"" ->
ignore;
_ ->
mnesia:write(#private_storage{userns = {LUser, XMLNS},
mnesia:write(
#private_storage{usns = {LUser, LServer, XMLNS},
xml = El})
end;
_ ->
ignore
end.
get_data(LUser, Els) ->
get_data(LUser, Els, []).
get_data(LUser, LServer, Els) ->
get_data(LUser, LServer, Els, []).
get_data(_LUser, [], Res) ->
get_data(_LUser, _LServer, [], Res) ->
lists:reverse(Res);
get_data(LUser, [El | Els], Res) ->
get_data(LUser, LServer, [El | Els], Res) ->
case El of
{xmlelement, _Name, Attrs, _} ->
XMLNS = xml:get_attr_s("xmlns", Attrs),
case mnesia:dirty_read(private_storage, {LUser, XMLNS}) of
case mnesia:dirty_read(private_storage, {LUser, LServer, XMLNS}) of
[R] ->
get_data(LUser, Els, [R#private_storage.xml | Res]);
get_data(LUser, LServer, Els,
[R#private_storage.xml | Res]);
[] ->
get_data(LUser, Els, [El | Res])
get_data(LUser, LServer, Els,
[El | Res])
end;
_ ->
get_data(LUser, Els, Res)
get_data(LUser, LServer, Els, Res)
end.
remove_user(User) ->
% TODO: use mnesia:select
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
F = fun() ->
lists:foreach(
fun({U, _} = Key) ->
fun({U, S, _} = Key) ->
if
U == LUser ->
(U == LUser) and (S == LServer) ->
mnesia:delete({private_storage, Key});
true ->
ok
@ -119,3 +125,47 @@ remove_user(User) ->
end,
mnesia:transaction(F).
update_table() ->
Fields = record_info(fields, private_storage),
case mnesia:table_info(private_storage, attributes) of
Fields ->
ok;
[userns, xml] ->
?INFO_MSG("Converting private_storage table from "
"{user, default, lists} format", []),
Host = ?MYNAME,
{atomic, ok} = mnesia:create_table(
mod_private_tmp_table,
[{disc_only_copies, [node()]},
{type, bag},
{local_content, true},
{record_name, private_storage},
{attributes, record_info(fields, private_storage)}]),
mnesia:transform_table(private_storage, ignore, Fields),
F1 = fun() ->
mnesia:write_lock_table(mod_private_tmp_table),
mnesia:foldl(
fun(#private_storage{usns = {U, NS}} = R, _) ->
mnesia:dirty_write(
mod_private_tmp_table,
R#private_storage{usns = {U, Host, NS}})
end, ok, private_storage)
end,
mnesia:transaction(F1),
mnesia:clear_table(private_storage),
F2 = fun() ->
mnesia:write_lock_table(private_storage),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, mod_private_tmp_table)
end,
mnesia:transaction(F2),
mnesia:delete_table(mod_private_tmp_table);
_ ->
?INFO_MSG("Recreating private_storage table", []),
mnesia:transform_table(private_storage, ignore, Fields)
end.

View File

@ -26,7 +26,7 @@
-define(DICT, dict).
-define(MAXITEMS, 10).
-record(pubsub_node, {node, parent, info}).
-record(pubsub_node, {host_node, host_parent, info}).
-record(nodeinfo, {items = [],
options = [],
entities = ?DICT:new()
@ -40,48 +40,54 @@ start(Opts) ->
mnesia:create_table(pubsub_node,
[{disc_only_copies, [node()]},
{attributes, record_info(fields, pubsub_node)}]),
mnesia:add_table_index(pubsub_node, parent),
Host = gen_mod:get_opt(host, Opts, "pubsub." ++ ?MYNAME),
ServedHosts = gen_mod:get_opt(served_hosts, Opts, [?MYNAME]),
Hosts = gen_mod:get_hosts(Opts, "pubsub."),
Host = hd(Hosts),
update_table(Host),
mnesia:add_table_index(pubsub_node, host_parent),
ServedHosts = gen_mod:get_opt(served_hosts, Opts, []),
register(ejabberd_mod_pubsub,
proc_lib:spawn_link(?MODULE, init, [Host, ServedHosts, self()])).
proc_lib:spawn_link(?MODULE, init, [Hosts, ServedHosts, self()])).
-define(MYJID, #jid{user = "", server = Host, resource = "",
luser = "", lserver = Host, lresource = ""}).
init(Host, ServedHosts, Parent) ->
ejabberd_router:register_route(Host),
init(Hosts, ServedHosts, Parent) ->
ejabberd_router:register_routes(Hosts),
lists:foreach(
fun(Host) ->
create_new_node(Host, ["pubsub"], ?MYJID),
create_new_node(Host, ["pubsub", "nodes"], ?MYJID),
create_new_node(Host, ["home"], ?MYJID),
create_new_node(Host, ["home", find_my_host(Host)], ?MYJID),
lists:foreach(fun(H) ->
create_new_node(Host, ["home", H], ?MYJID)
end, ServedHosts),
loop(Host, Parent).
end, ServedHosts)
end, Hosts),
loop(Hosts, Parent).
loop(Host, Parent) ->
loop(Hosts, Parent) ->
receive
{route, From, To, Packet} ->
case catch do_route(Host, From, To, Packet) of
case catch do_route(To#jid.lserver, From, To, Packet) of
{'EXIT', Reason} ->
?ERROR_MSG("~p", [Reason]);
_ ->
ok
end,
loop(Host, Parent);
loop(Hosts, Parent);
{room_destroyed, Room} ->
ets:delete(muc_online_room, Room),
loop(Host, Parent);
loop(Hosts, Parent);
stop ->
ejabberd_router:unregister_global_route(Host),
ejabberd_router:unregister_global_routes(Hosts),
ok;
reload ->
?MODULE:loop(Host, Parent);
?MODULE:loop(Hosts, Parent);
{system, From, Request} ->
sys:handle_system_msg(Request, From, Parent, ?MODULE, [], Host);
sys:handle_system_msg(Request, From, Parent, ?MODULE, [], Hosts);
_ ->
loop(Host, Parent)
loop(Hosts, Parent)
end.
@ -228,13 +234,13 @@ iq_disco_info(SNode) ->
iq_disco_items(Host, From, SNode) ->
Node = string:tokens(SNode, "/"),
F = fun() ->
case mnesia:read({pubsub_node, Node}) of
case mnesia:read({pubsub_node, {Host, Node}}) of
[#pubsub_node{info = Info}] ->
SubNodes = mnesia:index_read(pubsub_node,
Node,
#pubsub_node.parent),
{Host, Node},
#pubsub_node.host_parent),
SubItems =
lists:map(fun(#pubsub_node{node = N}) ->
lists:map(fun(#pubsub_node{host_node = {_, N}}) ->
SN = node_to_string(N),
{xmlelement, "item",
[{"jid", Host},
@ -255,10 +261,10 @@ iq_disco_items(Host, From, SNode) ->
[] ->
SubNodes = mnesia:index_read(
pubsub_node,
Node,
#pubsub_node.parent),
{Host, Node},
#pubsub_node.host_parent),
lists:map(
fun(#pubsub_node{node = N}) ->
fun(#pubsub_node{host_node = {_, N}}) ->
SN = node_to_string(N),
{xmlelement, "item",
[{"jid", Host},
@ -432,9 +438,9 @@ iq_pubsub(Host, From, Type, SubEl) ->
{set, "purge"} ->
purge_node(Host, From, Node);
{get, "entities"} ->
get_entities(From, Node);
get_entities(Host, From, Node);
{set, "entities"} ->
set_entities(From, Node, xml:remove_cdata(Els));
set_entities(Host, From, Node, xml:remove_cdata(Els));
%{get, "configure"} ->
% get_node_config(From, Node);
_ ->
@ -467,7 +473,7 @@ create_new_node(Host, Node, Owner) ->
Parent = lists:sublist(Node, length(Node) - 1),
F = fun() ->
ParentExists = (Parent == []) orelse
case mnesia:read({pubsub_node, Parent}) of
case mnesia:read({pubsub_node, {Host, Parent}}) of
[_] ->
true;
[] ->
@ -477,7 +483,7 @@ create_new_node(Host, Node, Owner) ->
false ->
{error, ?ERR_CONFLICT};
_ ->
case mnesia:read({pubsub_node, Node}) of
case mnesia:read({pubsub_node, {Host, Node}}) of
[_] ->
{error, ?ERR_CONFLICT};
[] ->
@ -488,8 +494,8 @@ create_new_node(Host, Node, Owner) ->
subscription = none},
?DICT:new()),
mnesia:write(
#pubsub_node{node = Node,
parent = Parent,
#pubsub_node{host_node = {Host, Node},
host_parent = {Host, Parent},
info = #nodeinfo{
entities = Entities}}),
ok
@ -530,7 +536,7 @@ create_new_node(Host, Node, Owner) ->
publish_item(Host, JID, Node, ItemID, Payload) ->
Publisher = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
F = fun() ->
case mnesia:read({pubsub_node, Node}) of
case mnesia:read({pubsub_node, {Host, Node}}) of
[#pubsub_node{info = Info} = N] ->
Affiliation = get_affiliation(Info, Publisher),
if
@ -563,7 +569,7 @@ publish_item(Host, JID, Node, ItemID, Payload) ->
delete_item(Host, JID, Node, ItemID) ->
Publisher = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
F = fun() ->
case mnesia:read({pubsub_node, Node}) of
case mnesia:read({pubsub_node, {Host, Node}}) of
[#pubsub_node{info = Info} = N] ->
case check_item_publisher(Info, ItemID, Publisher)
orelse
@ -603,7 +609,7 @@ subscribe_node(Host, From, JID, Node) ->
end,
Subscriber = jlib:jid_tolower(SubscriberJID),
F = fun() ->
case mnesia:read({pubsub_node, Node}) of
case mnesia:read({pubsub_node, {Host, Node}}) of
[#pubsub_node{info = Info} = N] ->
Affiliation = get_affiliation(Info, Subscriber),
if
@ -646,7 +652,7 @@ unsubscribe_node(Host, From, JID, Node) ->
end,
Subscriber = jlib:jid_tolower(SubscriberJID),
F = fun() ->
case mnesia:read({pubsub_node, Node}) of
case mnesia:read({pubsub_node, {Host, Node}}) of
[#pubsub_node{info = Info} = N] ->
Subscription = get_subscription(Info, Subscriber),
if
@ -695,7 +701,7 @@ get_items(Host, JID, Node, SMaxItems) ->
{error, _} = Error ->
Error;
_ ->
case catch mnesia:dirty_read(pubsub_node, Node) of
case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
[#pubsub_node{info = Info}] ->
Items = lists:sublist(Info#nodeinfo.items, MaxItems),
ItemsEls =
@ -722,14 +728,14 @@ get_items(Host, JID, Node, SMaxItems) ->
delete_node(Host, JID, Node) ->
Owner = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
F = fun() ->
case mnesia:read({pubsub_node, Node}) of
case mnesia:read({pubsub_node, {Host, Node}}) of
[#pubsub_node{info = Info}] ->
case get_affiliation(Info, Owner) of
owner ->
% TODO: don't iterate over all table
% TODO: don't iterate over entire table
Removed =
mnesia:foldl(
fun(#pubsub_node{node = N,
fun(#pubsub_node{host_node = {_, N},
info = #nodeinfo{
entities = Entities
}}, Acc) ->
@ -742,7 +748,7 @@ delete_node(Host, JID, Node) ->
end, [], pubsub_node),
lists:foreach(
fun({N, _}) ->
mnesia:delete({pubsub_node, N})
mnesia:delete({pubsub_node, {Host, N}})
end, Removed),
{removed, Removed};
_ ->
@ -769,7 +775,7 @@ delete_node(Host, JID, Node) ->
purge_node(Host, JID, Node) ->
Owner = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
F = fun() ->
case mnesia:read({pubsub_node, Node}) of
case mnesia:read({pubsub_node, {Host, Node}}) of
[#pubsub_node{info = Info} = N] ->
case get_affiliation(Info, Owner) of
owner ->
@ -798,9 +804,9 @@ purge_node(Host, JID, Node) ->
end.
get_entities(OJID, Node) ->
get_entities(Host, OJID, Node) ->
Owner = jlib:jid_tolower(jlib:jid_remove_resource(OJID)),
case catch mnesia:dirty_read(pubsub_node, Node) of
case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
[#pubsub_node{info = Info}] ->
case get_affiliation(Info, Owner) of
owner ->
@ -832,7 +838,7 @@ get_entities(OJID, Node) ->
end.
set_entities(OJID, Node, EntitiesEls) ->
set_entities(Host, OJID, Node, EntitiesEls) ->
Owner = jlib:jid_tolower(jlib:jid_remove_resource(OJID)),
Entities =
lists:foldl(
@ -883,7 +889,7 @@ set_entities(OJID, Node, EntitiesEls) ->
{error, ?ERR_BAD_REQUEST};
_ ->
F = fun() ->
case mnesia:read({pubsub_node, Node}) of
case mnesia:read({pubsub_node, {Host, Node}}) of
[#pubsub_node{info = Info} = N] ->
case get_affiliation(Info, Owner) of
owner ->
@ -1071,7 +1077,7 @@ set_info_entities(Info, Entities) ->
broadcast_publish_item(Host, Node, ItemID, Payload) ->
case catch mnesia:dirty_read(pubsub_node, Node) of
case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
[#pubsub_node{info = Info}] ->
?DICT:fold(
fun(JID, #entity{subscription = Subscription}, _) ->
@ -1103,7 +1109,7 @@ broadcast_publish_item(Host, Node, ItemID, Payload) ->
broadcast_retract_item(Host, Node, ItemID) ->
case catch mnesia:dirty_read(pubsub_node, Node) of
case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
[#pubsub_node{info = Info}] ->
?DICT:fold(
fun(JID, #entity{subscription = Subscription}, _) ->
@ -1166,3 +1172,73 @@ system_terminate(Reason, Parent, _, State) ->
system_code_change(State, _Mod, Ver, _Extra) ->
{ok, State}.
find_my_host(LServer) ->
Parts = string:tokens(LServer, "."),
find_my_host(Parts, ?MYHOSTS).
find_my_host([], _Hosts) ->
?MYNAME;
find_my_host([_ | Tail] = Parts, Hosts) ->
Domain = parts_to_string(Parts),
case lists:member(Domain, Hosts) of
true ->
Domain;
false ->
find_my_host(Tail, Hosts)
end.
parts_to_string(Parts) ->
string:strip(lists:flatten(lists:map(fun(S) -> [S, $.] end, Parts)),
right, $.).
update_table(Host) ->
Fields = record_info(fields, pubsub_node),
case mnesia:table_info(pubsub_node, attributes) of
Fields ->
ok;
[node, parent, info] ->
?INFO_MSG("Converting pubsub_node table from "
"{node, parent, info} format", []),
{atomic, ok} = mnesia:create_table(
mod_pubsub_tmp_table,
[{disc_only_copies, [node()]},
{type, bag},
{local_content, true},
{record_name, pubsub_node},
{attributes, record_info(fields, pubsub_node)}]),
mnesia:del_table_index(pubsub_node, parent),
mnesia:transform_table(pubsub_node, ignore, Fields),
F1 = fun() ->
mnesia:write_lock_table(mod_pubsub_tmp_table),
mnesia:foldl(
fun(#pubsub_node{host_node = N,
host_parent = P} = R, _) ->
mnesia:dirty_write(
mod_pubsub_tmp_table,
R#pubsub_node{host_node = {Host, N},
host_parent = {Host, P}})
end, ok, pubsub_node)
end,
mnesia:transaction(F1),
mnesia:clear_table(pubsub_node),
F2 = fun() ->
mnesia:write_lock_table(pubsub_node),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, mod_pubsub_tmp_table)
end,
mnesia:transaction(F2),
mnesia:delete_table(mod_pubsub_tmp_table);
_ ->
?INFO_MSG("Recreating pubsub_node table", []),
mnesia:transform_table(pubsub_node, ignore, Fields)
end.

View File

@ -29,25 +29,26 @@ stop() ->
gen_iq_handler:remove_iq_handler(ejabberd_local, ?NS_REGISTER),
gen_iq_handler:remove_iq_handler(ejabberd_sm, ?NS_REGISTER).
process_iq(From, _To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
process_iq(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
UTag = xml:get_subtag(SubEl, "username"),
PTag = xml:get_subtag(SubEl, "password"),
RTag = xml:get_subtag(SubEl, "remove"),
Server = ?MYNAME,
Server = To#jid.lserver,
if
(UTag /= false) and (RTag /= false) ->
User = xml:get_tag_cdata(UTag),
case From of
#jid{user = User, lserver = Server} ->
ejabberd_auth:remove_user(User),
ejabberd_auth:remove_user(User, Server),
IQ#iq{type = result, sub_el = [SubEl]};
_ ->
if
PTag /= false ->
Password = xml:get_tag_cdata(PTag),
case ejabberd_auth:remove_user(User,
Server,
Password) of
ok ->
IQ#iq{type = result,
@ -74,7 +75,7 @@ process_iq(From, _To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
(UTag == false) and (RTag /= false) ->
case From of
#jid{user = User, lserver = Server} ->
ejabberd_auth:remove_user(User),
ejabberd_auth:remove_user(User, Server),
IQ#iq{type = result, sub_el = [SubEl]};
_ ->
IQ#iq{type = error,
@ -85,10 +86,10 @@ process_iq(From, _To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
Password = xml:get_tag_cdata(PTag),
case From of
#jid{user = User, lserver = Server} ->
ejabberd_auth:set_password(User, Password),
ejabberd_auth:set_password(User, Server, Password),
IQ#iq{type = result, sub_el = [SubEl]};
_ ->
case try_register(User, Password) of
case try_register(User, Server, Password) of
ok ->
IQ#iq{type = result, sub_el = [SubEl]};
{error, Error} ->
@ -116,18 +117,18 @@ process_iq(From, _To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
end.
try_register(User, Password) ->
try_register(User, Server, Password) ->
case jlib:is_nodename(User) of
false ->
{error, ?ERR_BAD_REQUEST};
_ ->
JID = jlib:make_jid(User, ?MYNAME, ""),
JID = jlib:make_jid(User, Server, ""),
Access = gen_mod:get_module_opt(?MODULE, access, all),
case acl:match_rule(Access, JID) of
deny ->
{error, ?ERR_CONFLICT};
allow ->
case ejabberd_auth:try_register(User, Password) of
case ejabberd_auth:try_register(User, Server, Password) of
{atomic, ok} ->
send_welcome_message(JID),
send_registration_notifications(JID),

View File

@ -15,31 +15,27 @@
-export([start/1, stop/0,
process_iq/3,
process_local_iq/3,
get_subscription_lists/2,
in_subscription/4,
out_subscription/3,
set_items/2,
remove_user/1,
get_jid_info/3]).
get_user_roster/2,
get_subscription_lists/3,
in_subscription/5,
out_subscription/4,
set_items/3,
remove_user/2,
get_jid_info/4]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("mod_roster.hrl").
-record(roster, {uj,
user,
jid,
name = "",
subscription = none,
ask = none,
groups = [],
xattrs = [],
xs = []}).
start(Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
mnesia:create_table(roster,[{disc_copies, [node()]},
{attributes, record_info(fields, roster)}]),
mnesia:add_table_index(roster, user),
update_table(),
mnesia:add_table_index(roster, us),
ejabberd_hooks:add(roster_get,
?MODULE, get_user_roster, 50),
ejabberd_hooks:add(roster_in_subscription,
?MODULE, in_subscription, 50),
ejabberd_hooks:add(roster_out_subscription,
@ -54,6 +50,8 @@ start(Opts) ->
?MODULE, process_iq, IQDisc).
stop() ->
ejabberd_hooks:delete(roster_get,
?MODULE, get_user_roster, 50),
ejabberd_hooks:delete(roster_in_subscription,
?MODULE, in_subscription, 50),
ejabberd_hooks:delete(roster_out_subscription,
@ -74,8 +72,8 @@ stop() ->
process_iq(From, To, IQ) ->
#iq{sub_el = SubEl} = IQ,
#jid{lserver = LServer} = From,
case ?MYNAME of
LServer ->
case lists:member(LServer, ?MYHOSTS) of
true ->
ResIQ = process_local_iq(From, To, IQ),
ejabberd_router:route(From, From,
jlib:iq_to_xml(ResIQ)),
@ -89,8 +87,8 @@ process_iq(From, To, IQ) ->
process_iq(From, To, IQ) ->
#iq{sub_el = SubEl} = IQ,
#jid{lserver = LServer} = From,
case ?MYNAME of
LServer ->
case lists:member(LServer, ?MYHOSTS) of
true ->
process_local_iq(From, To, IQ);
_ ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]}
@ -109,8 +107,10 @@ process_local_iq(From, To, #iq{type = Type} = IQ) ->
process_iq_get(From, _To, #iq{sub_el = SubEl} = IQ) ->
#jid{luser = LUser} = From,
case catch mnesia:dirty_index_read(roster, LUser, #roster.user) of
LUser = From#jid.luser,
LServer = From#jid.lserver,
US = {LUser, LServer},
case catch ejabberd_hooks:run_fold(roster_get, [], [US]) of
Items when is_list(Items) ->
XItems = lists:map(fun item_to_xml/1, Items),
IQ#iq{type = result,
@ -121,6 +121,17 @@ process_iq_get(From, _To, #iq{sub_el = SubEl} = IQ) ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
end.
get_user_roster(Acc, US) ->
case catch mnesia:dirty_index_read(roster, US, #roster.us) of
Items when is_list(Items) ->
Items ++ Acc;
_ ->
Acc
end.
item_to_xml(Item) ->
Attrs1 = [{"jid", jlib:jid_to_string(Item#roster.jid)}],
Attrs2 = case Item#roster.name of
@ -164,7 +175,7 @@ process_iq_set(From, To, #iq{sub_el = SubEl} = IQ) ->
process_item_set(From, To, {xmlelement, _Name, Attrs, Els}) ->
JID1 = jlib:string_to_jid(xml:get_attr_s("jid", Attrs)),
#jid{user = User, luser = LUser} = From,
#jid{user = User, luser = LUser, lserver = LServer} = From,
case JID1 of
error ->
ok;
@ -172,15 +183,14 @@ process_item_set(From, To, {xmlelement, _Name, Attrs, Els}) ->
JID = {JID1#jid.user, JID1#jid.server, JID1#jid.resource},
LJID = jlib:jid_tolower(JID1),
F = fun() ->
Res = mnesia:read({roster, {LUser, LJID}}),
Res = mnesia:read({roster, {LUser, LServer, LJID}}),
Item = case Res of
[] ->
#roster{uj = {LUser, LJID},
user = LUser,
#roster{usj = {LUser, LServer, LJID},
us = {LUser, LServer},
jid = JID};
[I] ->
I#roster{user = LUser,
jid = JID,
I#roster{jid = JID,
name = "",
groups = [],
xattrs = [],
@ -190,7 +200,7 @@ process_item_set(From, To, {xmlelement, _Name, Attrs, Els}) ->
Item2 = process_item_els(Item1, Els),
case Item2#roster.subscription of
remove ->
mnesia:delete({roster, {LUser, LJID}});
mnesia:delete({roster, {LUser, LServer, LJID}});
_ ->
mnesia:write(Item2)
end,
@ -198,7 +208,7 @@ process_item_set(From, To, {xmlelement, _Name, Attrs, Els}) ->
end,
case mnesia:transaction(F) of
{atomic, {OldItem, Item}} ->
push_item(User, To, Item),
push_item(User, LServer, To, Item),
case Item#roster.subscription of
remove ->
IsTo = case OldItem#roster.subscription of
@ -292,47 +302,49 @@ process_item_els(Item, []) ->
Item.
push_item(User, From, Item) ->
push_item(User, Server, From, Item) ->
ejabberd_sm:route(jlib:make_jid("", "", ""),
jlib:make_jid(User, "", ""),
jlib:make_jid(User, Server, ""),
{xmlelement, "broadcast", [],
[{item,
Item#roster.jid,
Item#roster.subscription}]}),
lists:foreach(fun(Resource) ->
push_item(User, Resource, From, Item)
end, ejabberd_sm:get_user_resources(User)).
push_item(User, Server, Resource, From, Item)
end, ejabberd_sm:get_user_resources(User, Server)).
% TODO: don't push to those who not load roster
-ifdef(PSI_ROSTER_WORKAROUND).
push_item(User, Resource, _From, Item) ->
push_item(User, Server, Resource, _From, Item) ->
ResIQ = #iq{type = set, xmlns = ?NS_ROSTER,
sub_el = [{xmlelement, "query",
[{"xmlns", ?NS_ROSTER}],
[item_to_xml(Item)]}]},
ejabberd_router:route(
jlib:make_jid(User, ?MYNAME, Resource),
jlib:make_jid(User, ?MYNAME, Resource),
jlib:make_jid(User, Server, Resource),
jlib:make_jid(User, Server, Resource),
jlib:iq_to_xml(ResIQ)).
-else.
push_item(User, Resource, From, Item) ->
push_item(User, Server, Resource, From, Item) ->
ResIQ = #iq{type = set, xmlns = ?NS_ROSTER,
sub_el = [{xmlelement, "query",
[{"xmlns", ?NS_ROSTER}],
[item_to_xml(Item)]}]},
ejabberd_router:route(
From,
jlib:make_jid(User, ?MYNAME, Resource),
jlib:make_jid(User, Server, Resource),
jlib:iq_to_xml(ResIQ)).
-endif.
get_subscription_lists(_, User) ->
get_subscription_lists(_, User, Server) ->
LUser = jlib:nodeprep(User),
case mnesia:dirty_index_read(roster, LUser, #roster.user) of
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case mnesia:dirty_index_read(roster, US, #roster.us) of
Items when is_list(Items) ->
fill_subscription_lists(Items, [], []);
_ ->
@ -340,7 +352,7 @@ get_subscription_lists(_, User) ->
end.
fill_subscription_lists([I | Is], F, T) ->
J = element(2, I#roster.uj),
J = element(3, I#roster.usj),
case I#roster.subscription of
both ->
fill_subscription_lists(Is, [J | F], [J | T]);
@ -360,23 +372,25 @@ ask_to_pending(Ask) -> Ask.
in_subscription(_, User, JID, Type) ->
process_subscription(in, User, JID, Type).
in_subscription(_, User, Server, JID, Type) ->
process_subscription(in, User, Server, JID, Type).
out_subscription(User, JID, Type) ->
process_subscription(out, User, JID, Type).
out_subscription(User, Server, JID, Type) ->
process_subscription(out, User, Server, JID, Type).
process_subscription(Direction, User, JID1, Type) ->
process_subscription(Direction, User, Server, JID1, Type) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
LJID = jlib:jid_tolower(JID1),
F = fun() ->
Item = case mnesia:read({roster, {LUser, LJID}}) of
Item = case mnesia:read({roster, {LUser, LServer, LJID}}) of
[] ->
JID = {JID1#jid.user,
JID1#jid.server,
JID1#jid.resource},
#roster{uj = {LUser, LJID},
user = LUser,
#roster{usj = {LUser, LServer, LJID},
us = US,
jid = JID};
[I] ->
I
@ -420,12 +434,13 @@ process_subscription(Direction, User, JID1, Type) ->
unsubscribed -> "unsubscribed"
end,
ejabberd_router:route(
jlib:make_jid(User, ?MYNAME, ""), JID1,
jlib:make_jid(User, Server, ""), JID1,
{xmlelement, "presence", [{"type", T}], []})
end,
case Push of
{push, Item} ->
push_item(User, jlib:make_jid("", ?MYNAME, ""), Item),
push_item(User, Server,
jlib:make_jid("", Server, ""), Item),
true;
none ->
false
@ -525,27 +540,32 @@ in_auto_reply(both, none, unsubscribe) -> unsubscribed;
in_auto_reply(_, _, _) -> none.
remove_user(User) ->
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
lists:foreach(fun(R) ->
mnesia:delete_object(R)
end,
mnesia:index_read(roster, LUser, #roster.user))
mnesia:index_read(roster, US, #roster.us))
end,
mnesia:transaction(F).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
set_items(User, SubEl) ->
set_items(User, Server, SubEl) ->
{xmlelement, _Name, _Attrs, Els} = SubEl,
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
F = fun() ->
lists:foreach(fun(El) -> process_item_set_t(LUser, El) end, Els)
lists:foreach(fun(El) ->
process_item_set_t(LUser, LServer, El)
end, Els)
end,
mnesia:transaction(F).
process_item_set_t(LUser, {xmlelement, _Name, Attrs, Els}) ->
process_item_set_t(LUser, LServer, {xmlelement, _Name, Attrs, Els}) ->
JID1 = jlib:string_to_jid(xml:get_attr_s("jid", Attrs)),
case JID1 of
error ->
@ -553,19 +573,19 @@ process_item_set_t(LUser, {xmlelement, _Name, Attrs, Els}) ->
_ ->
JID = {JID1#jid.user, JID1#jid.server, JID1#jid.resource},
LJID = {JID1#jid.luser, JID1#jid.lserver, JID1#jid.lresource},
Item = #roster{uj = {LUser, LJID},
user = LUser,
Item = #roster{usj = {LUser, LServer, LJID},
us = {LUser, LServer},
jid = JID},
Item1 = process_item_attrs_ws(Item, Attrs),
Item2 = process_item_els(Item1, Els),
case Item2#roster.subscription of
remove ->
mnesia:delete({roster, {LUser, LJID}});
mnesia:delete({roster, {LUser, LServer, LJID}});
_ ->
mnesia:write(Item2)
end
end;
process_item_set_t(_LUser, _) ->
process_item_set_t(_LUser, _LServer, _) ->
ok.
process_item_attrs_ws(Item, [{Attr, Val} | Attrs]) ->
@ -613,10 +633,11 @@ process_item_attrs_ws(Item, []) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
get_jid_info(_, User, JID) ->
get_jid_info(_, User, Server, JID) ->
LUser = jlib:nodeprep(User),
LJID = jlib:jid_tolower(JID),
case catch mnesia:dirty_read(roster, {LUser, LJID}) of
LServer = jlib:nameprep(Server),
case catch mnesia:dirty_read(roster, {LUser, LServer, LJID}) of
[#roster{subscription = Subscription, groups = Groups}] ->
{Subscription, Groups};
_ ->
@ -625,7 +646,8 @@ get_jid_info(_, User, JID) ->
LRJID == LJID ->
{none, []};
true ->
case catch mnesia:dirty_read(roster, {LUser, LRJID}) of
case catch mnesia:dirty_read(
roster, {LUser, LServer, LRJID}) of
[#roster{subscription = Subscription,
groups = Groups}] ->
{Subscription, Groups};
@ -635,4 +657,51 @@ get_jid_info(_, User, JID) ->
end
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
update_table() ->
Fields = record_info(fields, roster),
case mnesia:table_info(roster, attributes) of
Fields ->
ok;
[uj, user, jid, name, subscription, ask, groups, xattrs, xs] ->
?INFO_MSG("Converting roster table from "
"{uj, user, jid, name, subscription, ask, groups, xattrs, xs} format", []),
Host = ?MYNAME,
{atomic, ok} = mnesia:create_table(
mod_roster_tmp_table,
[{disc_only_copies, [node()]},
{type, bag},
{local_content, true},
{record_name, roster},
{attributes, record_info(fields, roster)}]),
mnesia:del_table_index(roster, user),
mnesia:transform_table(roster, ignore, Fields),
F1 = fun() ->
mnesia:write_lock_table(mod_roster_tmp_table),
mnesia:foldl(
fun(#roster{usj = {U, JID}, us = U} = R, _) ->
mnesia:dirty_write(
mod_roster_tmp_table,
R#roster{usj = {U, Host, JID},
us = {U, Host}})
end, ok, roster)
end,
mnesia:transaction(F1),
mnesia:clear_table(roster),
F2 = fun() ->
mnesia:write_lock_table(roster),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, mod_roster_tmp_table)
end,
mnesia:transaction(F2),
mnesia:delete_table(mod_roster_tmp_table);
_ ->
?INFO_MSG("Recreating roster table", []),
mnesia:transform_table(roster, ignore, Fields)
end.

18
src/mod_roster.hrl Normal file
View File

@ -0,0 +1,18 @@
%%%----------------------------------------------------------------------
%%% File : mod_roster.hrl
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose : Roster management
%%% Created : 5 Mar 2005 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id: mod_roster.hrl 11 2005-03-06 22:36:15Z alexey $
%%%----------------------------------------------------------------------
-record(roster, {usj,
us,
jid,
name = "",
subscription = none,
ask = none,
groups = [],
xattrs = [],
xs = []}).

View File

@ -333,7 +333,7 @@ push_item(User, From, Item) ->
Item#roster.subscription}]}),
lists:foreach(fun(Resource) ->
push_item(User, Resource, From, Item)
end, ejabberd_sm:get_user_resources(User)).
end, ejabberd_sm:get_user_resources(User, 'TODO')).
% TODO: don't push to those who not load roster
-ifdef(PSI_ROSTER_WORKAROUND).

291
src/mod_shared_roster.erl Normal file
View File

@ -0,0 +1,291 @@
%%%----------------------------------------------------------------------
%%% File : mod_shared_roster.erl
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose : Shared roster management
%%% Created : 5 Mar 2005 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id: mod_shared_roster.erl 24 2005-04-14 01:15:31Z alexey $
%%%----------------------------------------------------------------------
-module(mod_shared_roster).
-author('alexey@sevcom.net').
-vsn('$Revision: 24 $ ').
-behaviour(gen_mod).
-export([start/1, stop/0,
get_user_roster/2,
get_subscription_lists/3,
get_jid_info/4,
in_subscription/5,
out_subscription/4,
list_groups/1,
create_group/2,
create_group/3,
delete_group/2,
get_group_opts/2,
set_group_opts/3,
get_group_users/2,
add_user_to_group/3,
remove_user_from_group/3]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("mod_roster.hrl").
-record(sr_group, {group_host, opts}).
-record(sr_user, {us, group_host}).
start(_Opts) ->
mnesia:create_table(sr_group,
[{disc_copies, [node()]},
{attributes, record_info(fields, sr_group)}]),
mnesia:create_table(sr_user,
[{disc_copies, [node()]},
{type, bag},
{attributes, record_info(fields, sr_user)}]),
mnesia:add_table_index(sr_user, group_host),
ejabberd_hooks:add(roster_get,
?MODULE, get_user_roster, 70),
ejabberd_hooks:add(roster_in_subscription,
?MODULE, in_subscription, 30),
ejabberd_hooks:add(roster_out_subscription,
?MODULE, out_subscription, 30),
ejabberd_hooks:add(roster_get_subscription_lists,
?MODULE, get_subscription_lists, 70),
ejabberd_hooks:add(roster_get_jid_info,
?MODULE, get_jid_info, 70).
%ejabberd_hooks:add(remove_user,
% ?MODULE, remove_user, 50),
stop() ->
ejabberd_hooks:delete(roster_get,
?MODULE, get_user_roster, 70),
ejabberd_hooks:delete(roster_in_subscription,
?MODULE, in_subscription, 30),
ejabberd_hooks:delete(roster_out_subscription,
?MODULE, out_subscription, 30),
ejabberd_hooks:delete(roster_get_subscription_lists,
?MODULE, get_subscription_lists, 70),
ejabberd_hooks:delete(roster_get_jid_info,
?MODULE, get_jid_info, 70).
%ejabberd_hooks:delete(remove_user,
% ?MODULE, remove_user, 50),
get_user_roster(Items, US) ->
{U, S} = US,
DisplayedGroups = get_user_displayed_groups(US),
SRUsers =
lists:foldl(
fun(Group, Acc1) ->
lists:foldl(
fun(User, Acc2) ->
dict:append(User, get_group_name(S, Group), Acc2)
end, Acc1, get_group_users(S, Group))
end, dict:new(), DisplayedGroups),
{NewItems1, SRUsersRest} =
lists:mapfoldl(
fun(Item, SRUsers1) ->
{_, _, {U1, S1, _}} = Item#roster.usj,
US1 = {U1, S1},
case dict:find(US1, SRUsers1) of
{ok, _GroupNames} ->
{Item#roster{subscription = both, ask = none},
dict:erase(US1, SRUsers1)};
error ->
{Item, SRUsers1}
end
end, SRUsers, Items),
SRItems = [#roster{usj = {U, S, {U1, S1, ""}},
us = US,
jid = {U1, S1, ""},
name = "",
subscription = both,
ask = none,
groups = GroupNames} ||
{{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)],
SRItems ++ NewItems1.
get_subscription_lists({F, T}, User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
DisplayedGroups = get_user_displayed_groups(US),
SRUsers =
lists:usort(
lists:flatmap(
fun(Group) ->
get_group_users(LServer, Group)
end, DisplayedGroups)),
SRJIDs = [{U1, S1, ""} || {U1, S1} <- SRUsers],
{lists:usort(SRJIDs ++ F), lists:usort(SRJIDs ++ T)}.
get_jid_info({Subscription, Groups}, User, Server, JID) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
{U1, S1, _} = jlib:jid_tolower(JID),
US1 = {U1, S1},
DisplayedGroups = get_user_displayed_groups(US),
SRUsers =
lists:foldl(
fun(Group, Acc1) ->
lists:foldl(
fun(User1, Acc2) ->
dict:append(
User1, get_group_name(LServer, Group), Acc2)
end, Acc1, get_group_users(LServer, Group))
end, dict:new(), DisplayedGroups),
case dict:find(US1, SRUsers) of
{ok, GroupNames} ->
NewGroups = if
Groups == [] -> GroupNames;
true -> Groups
end,
{both, NewGroups};
error ->
{Subscription, Groups}
end.
in_subscription(Acc, User, Server, JID, Type) ->
process_subscription(in, User, Server, JID, Type, Acc).
out_subscription(User, Server, JID, Type) ->
process_subscription(out, User, Server, JID, Type, false).
process_subscription(Direction, User, Server, JID, _Type, Acc) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
{U1, S1, _} = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
US1 = {U1, S1},
DisplayedGroups = get_user_displayed_groups(US),
SRUsers =
lists:usort(
lists:flatmap(
fun(Group) ->
get_group_users(LServer, Group)
end, DisplayedGroups)),
case lists:member(US1, SRUsers) of
true ->
case Direction of
in ->
{stop, false};
out ->
stop
end;
false ->
Acc
end.
list_groups(Host) ->
mnesia:dirty_select(
sr_group,
[{#sr_group{group_host = {'$1', '$2'},
_ = '_'},
[{'==', '$2', Host}],
['$1']}]).
create_group(Host, Group) ->
create_group(Host, Group, []).
create_group(Host, Group, Opts) ->
R = #sr_group{group_host = {Group, Host}, opts = Opts},
F = fun() ->
mnesia:write(R)
end,
mnesia:transaction(F).
delete_group(Host, Group) ->
F = fun() ->
mnesia:delete({sr_group, {Group, Host}})
end,
mnesia:transaction(F).
get_group_opts(Host, Group) ->
case catch mnesia:dirty_read(sr_group, {Group, Host}) of
[#sr_group{opts = Opts}] ->
Opts;
_ ->
error
end.
set_group_opts(Host, Group, Opts) ->
R = #sr_group{group_host = {Group, Host}, opts = Opts},
F = fun() ->
mnesia:write(R)
end,
mnesia:transaction(F).
get_user_groups(US) ->
Host = element(2, US),
case catch mnesia:dirty_read(sr_user, US) of
Rs when is_list(Rs) ->
[Group || #sr_user{group_host = {Group, H}} <- Rs, H == Host];
_ ->
[]
end.
is_group_enabled(Host, Group) ->
case catch mnesia:dirty_read(sr_group, {Group, Host}) of
[#sr_group{opts = Opts}] ->
not lists:member(disabled, Opts);
_ ->
false
end.
get_group_opt(Host, Group, Opt, Default) ->
case catch mnesia:dirty_read(sr_group, {Group, Host}) of
[#sr_group{opts = Opts}] ->
case lists:keysearch(Opt, 1, Opts) of
{value, {_, Val}} ->
Val;
false ->
Default
end;
_ ->
false
end.
get_group_users(Host, Group) ->
case catch mnesia:dirty_index_read(
sr_user, {Group, Host}, #sr_user.group_host) of
Rs when is_list(Rs) ->
[R#sr_user.us || R <- Rs];
_ ->
[]
end.
get_group_name(Host, Group) ->
get_group_opt(Host, Group, name, Group).
get_user_displayed_groups(US) ->
Host = element(2, US),
DisplayedGroups1 =
lists:usort(
lists:flatmap(
fun(Group) ->
case is_group_enabled(Host, Group) of
true ->
get_group_opt(Host, Group, displayed_groups, []);
false ->
[]
end
end, get_user_groups(US))),
[Group || Group <- DisplayedGroups1, is_group_enabled(Host, Group)].
add_user_to_group(Host, US, Group) ->
R = #sr_user{us = US, group_host = {Group, Host}},
F = fun() ->
mnesia:write(R)
end,
mnesia:transaction(F).
remove_user_from_group(Host, US, Group) ->
R = #sr_user{us = US, group_host = {Group, Host}},
F = fun() ->
mnesia:delete_object(R)
end,
mnesia:transaction(F).

View File

@ -38,7 +38,7 @@ process_local_iq(From, To, #iq{id = ID, type = Type,
Node = string:tokens(xml:get_tag_attr_s("node", SubEl), "/"),
Names = get_names(Els, []),
case get_local_stats(Node, Names) of
case get_local_stats(To#jid.server, Node, Names) of
{result, Res} ->
IQ#iq{type = result,
sub_el = [{xmlelement, "query",
@ -66,16 +66,20 @@ get_names([_ | Els], Res) ->
-define(STAT(Name), {xmlelement, "stat", [{"name", Name}], []}).
get_local_stats([], []) ->
get_local_stats(_Server, [], []) ->
{result,
[?STAT("users/online"),
?STAT("users/total")
?STAT("users/total"),
?STAT("users/all-hosts/online"),
?STAT("users/all-hosts/total")
]};
get_local_stats([], Names) ->
{result, lists:map(fun(Name) -> get_local_stat([], Name) end, Names)};
get_local_stats(Server, [], Names) ->
{result, lists:map(fun(Name) ->
get_local_stat(Server, [], Name)
end, Names)};
get_local_stats(["running nodes", _], []) ->
get_local_stats(_Server, ["running nodes", _], []) ->
{result,
[?STAT("time/uptime"),
?STAT("time/cputime"),
@ -86,7 +90,7 @@ get_local_stats(["running nodes", _], []) ->
?STAT("transactions/logged")
]};
get_local_stats(["running nodes", ENode], Names) ->
get_local_stats(_Server, ["running nodes", ENode], Names) ->
case search_running_node(ENode) of
false ->
{error, ?ERR_ITEM_NOT_FOUND};
@ -95,7 +99,7 @@ get_local_stats(["running nodes", ENode], Names) ->
lists:map(fun(Name) -> get_node_stat(Node, Name) end, Names)}
end;
get_local_stats(_, _) ->
get_local_stats(_Server, _, _) ->
{error, ?ERR_FEATURE_NOT_IMPLEMENTED}.
@ -115,27 +119,40 @@ get_local_stats(_, _) ->
[{xmlcdata, Desc}]}]}).
%get_local_stat([], Name) when Name == "time/uptime" ->
% ?STATVAL(io_lib:format("~.3f", [element(1, statistics(wall_clock))/1000]),
% "seconds");
%get_local_stat([], Name) when Name == "time/cputime" ->
% ?STATVAL(io_lib:format("~.3f", [element(1, statistics(runtime))/1000]),
% "seconds");
get_local_stat([], Name) when Name == "users/online" ->
get_local_stat(Server, [], Name) when Name == "users/online" ->
case catch ejabberd_sm:get_vh_session_list(Server) of
{'EXIT', Reason} ->
?STATERR("500", "Internal Server Error");
Users ->
?STATVAL(integer_to_list(length(Users)), "users")
end;
get_local_stat(Server, [], Name) when Name == "users/total" ->
LServer = jlib:nameprep(Server),
case catch ejabberd_auth:get_vh_registered_users(Server) of
{'EXIT', Reason} ->
?STATERR("500", "Internal Server Error");
Users ->
?STATVAL(integer_to_list(length(Users)), "users")
end;
get_local_stat(_Server, [], Name) when Name == "users/all-hosts/online" ->
case catch mnesia:table_info(session, size) of
{'EXIT', Reason} ->
?STATERR("500", "Internal Server Error");
Users ->
?STATVAL(integer_to_list(Users), "users")
end;
get_local_stat([], Name) when Name == "users/total" ->
get_local_stat(_Server, [], Name) when Name == "users/all-hosts/total" ->
case catch mnesia:table_info(passwd, size) of
{'EXIT', Reason} ->
?STATERR("500", "Internal Server Error");
Users ->
?STATVAL(integer_to_list(Users), "users")
end;
get_local_stat(_, Name) ->
get_local_stat(_Server, _, Name) ->
?STATERR("404", "Not Found").

View File

@ -16,16 +16,16 @@
process_local_iq/3,
process_sm_iq/3,
reindex_vcards/0,
remove_user/1]).
remove_user/2]).
-include("ejabberd.hrl").
-include("jlib.hrl").
%-define(JUD_ALLOW_RETURN_ALL, true).
-define(JUD_MATCHES, 30).
-record(vcard_search, {user, luser,
-record(vcard_search, {us,
user, luser,
fn, lfn,
family, lfamily,
given, lgiven,
@ -38,7 +38,7 @@
orgname, lorgname,
orgunit, lorgunit
}).
-record(vcard, {user, vcard}).
-record(vcard, {us, vcard}).
start(Opts) ->
@ -47,6 +47,7 @@ start(Opts) ->
mnesia:create_table(vcard_search,
[{disc_copies, [node()]},
{attributes, record_info(fields, vcard_search)}]),
update_tables(),
mnesia:add_table_index(vcard_search, luser),
mnesia:add_table_index(vcard_search, lfn),
mnesia:add_table_index(vcard_search, lfamily),
@ -68,21 +69,21 @@ start(Opts) ->
gen_iq_handler:add_iq_handler(ejabberd_sm, ?NS_VCARD,
?MODULE, process_sm_iq, IQDisc),
catch mod_disco:register_sm_feature(?NS_VCARD),
Host = gen_mod:get_opt(host, Opts, "vjud." ++ ?MYNAME),
Hosts = gen_mod:get_hosts(Opts, "vjud."),
Search = gen_mod:get_opt(search, Opts, true),
register(ejabberd_mod_vcard, spawn(?MODULE, init, [Host, Search])).
register(ejabberd_mod_vcard, spawn(?MODULE, init, [Hosts, Search])).
init(Host, Search) ->
init(Hosts, Search) ->
case Search of
false ->
loop(Host);
loop(Hosts);
_ ->
ejabberd_router:register_route(Host),
loop(Host)
ejabberd_router:register_routes(Hosts),
loop(Hosts)
end.
loop(Host) ->
loop(Hosts) ->
receive
{route, From, To, Packet} ->
case catch do_route(From, To, Packet) of
@ -91,12 +92,12 @@ loop(Host) ->
_ ->
ok
end,
loop(Host);
loop(Hosts);
stop ->
catch ejabberd_router:unregister_route(Host),
catch ejabberd_router:unregister_routes(Hosts),
ok;
_ ->
loop(Host)
loop(Hosts)
end.
stop() ->
@ -137,17 +138,18 @@ process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
case Type of
set ->
#jid{user = User, lserver = LServer, luser = LUser} = From,
case ?MYNAME of
LServer ->
set_vcard(User, SubEl),
case lists:member(LServer, ?MYHOSTS) of
true ->
set_vcard(User, LServer, SubEl),
IQ#iq{type = result, sub_el = []};
_ ->
false ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
end;
get ->
#jid{luser = LUser} = To,
#jid{luser = LUser, lserver = LServer} = To,
US = {LUser, LServer},
F = fun() ->
mnesia:read({vcard, LUser})
mnesia:read({vcard, US})
end,
Els = case mnesia:transaction(F) of
{atomic, Rs} ->
@ -160,7 +162,7 @@ process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
IQ#iq{type = result, sub_el = Els}
end.
set_vcard(User, VCARD) ->
set_vcard(User, LServer, VCARD) ->
FN = xml:get_path_s(VCARD, [{elem, "FN"}, cdata]),
Family = xml:get_path_s(VCARD, [{elem, "N"}, {elem, "FAMILY"}, cdata]),
Given = xml:get_path_s(VCARD, [{elem, "N"}, {elem, "GIVEN"}, cdata]),
@ -193,6 +195,8 @@ set_vcard(User, VCARD) ->
LOrgName = stringprep:tolower(OrgName),
LOrgUnit = stringprep:tolower(OrgUnit),
US = {LUser, LServer},
if
(LUser == error) or
(LFN == error) or
@ -209,9 +213,11 @@ set_vcard(User, VCARD) ->
{error, badarg};
true ->
F = fun() ->
mnesia:write(#vcard{user = LUser, vcard = VCARD}),
mnesia:write(#vcard{us = US, vcard = VCARD}),
mnesia:write(
#vcard_search{user = User, luser = LUser,
#vcard_search{us = US,
user = {User, LServer},
luser = LUser,
fn = FN, lfn = LFN,
family = Family, lfamily = LFamily,
given = Given, lgiven = LGiven,
@ -428,7 +434,7 @@ search_result(Lang, JID, Data) ->
?LFIELD("email", "email"),
?LFIELD("Organization Name", "orgname"),
?LFIELD("Organization Unit", "orgunit")
]}] ++ lists:map(fun record_to_item/1, search(Data)).
]}] ++ lists:map(fun record_to_item/1, search(JID#jid.lserver, Data)).
-define(FIELD(Var, Val),
{xmlelement, "field", [{"var", Var}],
@ -436,9 +442,10 @@ search_result(Lang, JID, Data) ->
[{xmlcdata, Val}]}]}).
record_to_item(R) ->
{User, Server} = R#vcard_search.user,
{xmlelement, "item", [],
[
?FIELD("jid", R#vcard_search.user ++ "@" ++ ?MYNAME),
?FIELD("jid", User ++ "@" ++ Server),
?FIELD("fn", R#vcard_search.fn),
?FIELD("family", R#vcard_search.family),
?FIELD("given", R#vcard_search.given),
@ -453,34 +460,12 @@ record_to_item(R) ->
]
}.
-ifdef(JUD_ALLOW_RETURN_ALL).
search(Data) ->
MatchSpec = make_matchspec(Data),
case catch mnesia:dirty_select(vcard_search, [{MatchSpec, [], ['$_']}]) of
{'EXIT', Reason} ->
?ERROR_MSG("~p", [Reason]),
[];
Rs ->
case gen_mod:get_module_opt(?MODULE, matches, 30) of
infinity ->
Rs;
Val when is_integer(Val) and Val > 0 ->
lists:sublist(Rs, Val);
Val ->
?ERROR_MSG("Illegal option value ~p. "
"Default value ~p substituted.",
[{matches, Val}, ?JUD_MATCHES]),
lists:sublist(Rs, ?JUD_MATCHES)
end
end.
-else.
search(Data) ->
MatchSpec = make_matchspec(Data),
search(LServer, Data) ->
MatchSpec = make_matchspec(LServer, Data),
AllowReturnAll = gen_mod:get_module_opt(?MODULE, allow_return_all, false),
if
MatchSpec == #vcard_search{_ = '_'} ->
(MatchSpec == #vcard_search{_ = '_'}) and (not AllowReturnAll) ->
[];
true ->
case catch mnesia:dirty_select(vcard_search,
@ -503,33 +488,27 @@ search(Data) ->
end
end.
-endif.
% TODO: remove
% F = fun() ->
% mnesia:select(vcard_search, [{MatchSpec, [], ['$_']}])
% end,
% case mnesia:transaction(F) of
% {atomic, Rs} ->
% Rs;
% _ ->
% []
% end.
make_matchspec(Data) ->
make_matchspec(LServer, Data) ->
GlobMatch = #vcard_search{_ = '_'},
Match = filter_fields(Data, GlobMatch),
Match = filter_fields(Data, GlobMatch, LServer),
Match.
filter_fields([], Match) ->
filter_fields([], Match, _LServer) ->
Match;
filter_fields([{SVar, [Val]} | Ds], Match)
filter_fields([{SVar, [Val]} | Ds], Match, LServer)
when is_list(Val) and (Val /= "") ->
LVal = stringprep:tolower(Val),
NewMatch = case SVar of
"user" -> Match#vcard_search{luser = make_val(LVal)};
"user" ->
case gen_mod:get_module_opt(
?MODULE, search_all_hosts, true) of
true ->
Match#vcard_search{luser = make_val(LVal)};
false ->
Host = find_my_host(LServer),
Match#vcard_search{us = {make_val(LVal), Host}}
end;
"fn" -> Match#vcard_search{lfn = make_val(LVal)};
"family" -> Match#vcard_search{lfamily = make_val(LVal)};
"given" -> Match#vcard_search{lgiven = make_val(LVal)};
@ -543,9 +522,9 @@ filter_fields([{SVar, [Val]} | Ds], Match)
"orgunit" -> Match#vcard_search{lorgunit = make_val(LVal)};
_ -> Match
end,
filter_fields(Ds, NewMatch);
filter_fields([_ | Ds], Match) ->
filter_fields(Ds, Match).
filter_fields(Ds, NewMatch, LServer);
filter_fields([_ | Ds], Match, LServer) ->
filter_fields(Ds, Match, LServer).
make_val(Val) ->
case lists:suffix("*", Val) of
@ -555,10 +534,32 @@ make_val(Val) ->
Val
end.
find_my_host(LServer) ->
Parts = string:tokens(LServer, "."),
find_my_host(Parts, ?MYHOSTS).
find_my_host([], _Hosts) ->
?MYNAME;
find_my_host([_ | Tail] = Parts, Hosts) ->
Domain = parts_to_string(Parts),
case lists:member(Domain, Hosts) of
true ->
Domain;
false ->
find_my_host(Tail, Hosts)
end.
parts_to_string(Parts) ->
string:strip(lists:flatten(lists:map(fun(S) -> [S, $.] end, Parts)),
right, $.).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
set_vcard_t(R, _) ->
User = R#vcard.user,
US = R#vcard.us,
User = US,
VCARD = R#vcard.vcard,
FN = xml:get_path_s(VCARD, [{elem, "FN"}, cdata]),
@ -573,7 +574,7 @@ set_vcard_t(R, _) ->
OrgName = xml:get_path_s(VCARD, [{elem, "ORG"}, {elem, "ORGNAME"}, cdata]),
OrgUnit = xml:get_path_s(VCARD, [{elem, "ORG"}, {elem, "ORGUNIT"}, cdata]),
LUser = jlib:nodeprep(User),
{LUser, _LServer} = US,
LFN = stringprep:tolower(FN),
LFamily = stringprep:tolower(Family),
LGiven = stringprep:tolower(Given),
@ -602,7 +603,8 @@ set_vcard_t(R, _) ->
{error, badarg};
true ->
mnesia:write(
#vcard_search{user = User, luser = LUser,
#vcard_search{us = US,
user = User, luser = LUser,
fn = FN, lfn = LFN,
family = Family, lfamily = LFamily,
given = Given, lgiven = LGiven,
@ -625,16 +627,159 @@ reindex_vcards() ->
mnesia:transaction(F).
remove_user(User) ->
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
mnesia:delete({vcard, LUser}),
lists:foreach(fun(R) ->
mnesia:delete_object(R)
end,
mnesia:index_read(vcard_search,
LUser,
#vcard_search.luser))
mnesia:delete({vcard, US}),
mnesia:delete({vcard_search, US})
end,
mnesia:transaction(F).
update_tables() ->
update_vcard_table(),
update_vcard_search_table().
update_vcard_table() ->
Fields = record_info(fields, vcard),
case mnesia:table_info(vcard, attributes) of
Fields ->
ok;
[user, vcard] ->
?INFO_MSG("Converting vcard table from "
"{user, vcard} format", []),
Host = ?MYNAME,
{atomic, ok} = mnesia:create_table(
mod_vcard_tmp_table,
[{disc_only_copies, [node()]},
{type, bag},
{local_content, true},
{record_name, vcard},
{attributes, record_info(fields, vcard)}]),
mnesia:transform_table(vcard, ignore, Fields),
F1 = fun() ->
mnesia:write_lock_table(mod_vcard_tmp_table),
mnesia:foldl(
fun(#vcard{us = U} = R, _) ->
mnesia:dirty_write(
mod_vcard_tmp_table,
R#vcard{us = {U, Host}})
end, ok, vcard)
end,
mnesia:transaction(F1),
mnesia:clear_table(vcard),
F2 = fun() ->
mnesia:write_lock_table(vcard),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, mod_vcard_tmp_table)
end,
mnesia:transaction(F2),
mnesia:delete_table(mod_vcard_tmp_table);
_ ->
?INFO_MSG("Recreating vcard table", []),
mnesia:transform_table(vcard, ignore, Fields)
end.
update_vcard_search_table() ->
Fields = record_info(fields, vcard_search),
case mnesia:table_info(vcard_search, attributes) of
Fields ->
ok;
[user, luser,
fn, lfn,
family, lfamily,
given, lgiven,
middle, lmiddle,
nickname, lnickname,
bday, lbday,
ctry, lctry,
locality, llocality,
email, lemail,
orgname, lorgname,
orgunit, lorgunit] ->
?INFO_MSG("Converting vcard_search table from "
"{user, luser, fn, lfn, family, lfamily, given, lgiven, middle, lmiddle, nickname, lnickname, bday, lbday, ctry, lctry, locality, llocality, email, lemail, orgname, lorgname, orgunit, lorgunit} format", []),
Host = ?MYNAME,
{atomic, ok} = mnesia:create_table(
mod_vcard_tmp_table,
[{disc_only_copies, [node()]},
{type, bag},
{local_content, true},
{record_name, vcard_search},
{attributes, record_info(fields, vcard_search)}]),
F1 = fun() ->
mnesia:write_lock_table(mod_vcard_tmp_table),
mnesia:foldl(
fun({vcard_search,
User, LUser,
FN, LFN,
Family, LFamily,
Given, LGiven,
Middle, LMiddle,
Nickname, LNickname,
BDay, LBDay,
CTRY, LCTRY,
Locality, LLocality,
EMail, LEMail,
OrgName, LOrgName,
OrgUnit, LOrgUnit
}, _) ->
mnesia:dirty_write(
mod_vcard_tmp_table,
#vcard_search{
us = {LUser, Host},
user = {User, Host},
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
})
end, ok, vcard_search)
end,
mnesia:transaction(F1),
lists:foreach(fun(I) ->
mnesia:del_table_index(
vcard_search,
element(I, {vcard_search,
user, luser,
fn, lfn,
family, lfamily,
given, lgiven,
middle, lmiddle,
nickname, lnickname,
bday, lbday,
ctry, lctry,
locality, llocality,
email, lemail,
orgname, lorgname,
orgunit, lorgunit}))
end, mnesia:table_info(vcard_search, index)),
mnesia:clear_table(vcard_search),
mnesia:transform_table(vcard_search, ignore, Fields),
F2 = fun() ->
mnesia:write_lock_table(vcard_search),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, mod_vcard_tmp_table)
end,
mnesia:transaction(F2),
mnesia:delete_table(mod_vcard_tmp_table);
_ ->
?INFO_MSG("Recreating vcard_search table", []),
mnesia:transform_table(vcard_search, ignore, Fields)
end.

View File

@ -195,18 +195,23 @@ process_request(#state{request_method = 'GET',
request_lang = Lang,
use_http_poll = UseHTTPPoll,
use_web_admin = UseWebAdmin}) ->
User = case Auth of
{U, P} ->
case ejabberd_auth:check_password(U, P) of
US = case Auth of
{SJID, P} ->
case jlib:string_to_jid(SJID) of
error ->
unauthorized;
#jid{user = U, server = S} ->
case ejabberd_auth:check_password(U, S, P) of
true ->
U;
{U, S};
false ->
unauthorized
end
end;
_ ->
undefined
end,
case User of
case US of
unauthorized ->
make_xhtml_output(
401,
@ -228,7 +233,7 @@ process_request(#state{request_method = 'GET',
Request = #request{method = 'GET',
path = LPath,
q = LQuery,
user = User,
us = US,
lang = Lang},
case ejabberd_web:process_get({UseHTTPPoll, UseWebAdmin},
Request) of
@ -256,18 +261,23 @@ process_request(#state{request_method = 'POST',
use_http_poll = UseHTTPPoll,
use_web_admin = UseWebAdmin} = State)
when is_integer(Len) ->
User = case Auth of
{U, P} ->
case ejabberd_auth:check_password(U, P) of
US = case Auth of
{SJID, P} ->
case jlib:string_to_jid(SJID) of
error ->
unauthorized;
#jid{user = U, server = S} ->
case ejabberd_auth:check_password(U, S, P) of
true ->
U;
{U, S};
false ->
unauthorized
end
end;
_ ->
undefined
end,
case User of
case US of
unauthorized ->
make_xhtml_output(
401,
@ -297,7 +307,7 @@ process_request(#state{request_method = 'POST',
Request = #request{method = 'POST',
path = LPath,
q = LQuery,
user = User,
us = US,
data = Data,
lang = Lang},
case ejabberd_web:process_get({UseHTTPPoll, UseWebAdmin},

View File

@ -9,7 +9,7 @@
-record(request, {method,
path,
q = [],
user,
us,
lang = "",
data = ""
}).

404
src/web/ejabberd_http1.erl Normal file
View File

@ -0,0 +1,404 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_http.erl
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose :
%%% Created : 27 Feb 2004 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$
%%%----------------------------------------------------------------------
-module(ejabberd_http).
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-behaviour(gen_fsm).
%% External exports
-export([start/2,
start_link/2]).
%% gen_fsm callbacks
-export([init/1,
wait_for_headers/2,
handle_event/3,
handle_sync_event/4,
code_change/4,
handle_info/3,
terminate/3]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-define(DICT, dict).
-record(state, {socket,
request_method,
request_path,
request_auth
}).
-define(DBGFSM, true).
-ifdef(DBGFSM).
-define(FSMOPTS, [{debug, [trace]}]).
-else.
-define(FSMOPTS, []).
-endif.
-define(XHTML_DOCTYPE,
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" "
"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n").
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(SockData, Opts) ->
supervisor:start_child(ejabberd_http_sup, [SockData, Opts]).
start_link(SockData, Opts) ->
gen_fsm:start_link(ejabberd_http, [SockData, Opts], ?FSMOPTS).
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok, StateName, StateData} |
%% {ok, StateName, StateData, Timeout} |
%% ignore |
%% {stop, StopReason}
%%----------------------------------------------------------------------
init([{SockMod, Socket}, Opts]) ->
?INFO_MSG("started: ~p", [{SockMod, Socket}]),
case SockMod of
gen_tcp ->
inet:setopts(Socket, [{packet, http}, {active, true}, {recbuf, 0}]);
ssl ->
ssl:setopts(Socket, [{packet, http}, {active, true}])
end,
{ok, wait_for_headers, #state{socket = Socket}}.
%%----------------------------------------------------------------------
%% Func: StateName/2
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
wait_for_headers(_, StateData) ->
{next_state, wait_for_headers, StateData}.
%%----------------------------------------------------------------------
%% Func: StateName/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {reply, Reply, NextStateName, NextStateData} |
%% {reply, Reply, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
%state_name(Event, From, StateData) ->
% Reply = ok,
% {reply, Reply, state_name, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_event/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_sync_event/4
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {reply, Reply, NextStateName, NextStateData} |
%% {reply, Reply, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
handle_sync_event(_Event, _From, StateName, StateData) ->
Reply = ok,
{reply, Reply, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_info/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_info({http_request, _Socket, Method, Path, _Version},
StateName, StateData) ->
{next_state, StateName, StateData#state{request_method = Method,
request_path = Path}};
handle_info({http_header, _Socket, _, 'Authorization', _, Auth},
StateName, StateData) ->
{next_state, StateName,
StateData#state{request_auth = parse_auth(Auth)}};
handle_info({http_eoh, _Socket}, StateName, StateData) ->
process_request(StateData),
{stop, normal, StateData};
handle_info({tcp_closed, _Socket}, StateName, StateData) ->
{stop, normal, StateData};
handle_info({tcp_error, _Socket, _Reason}, StateName, StateData) ->
{stop, normal, StateData};
handle_info(_, StateName, StateData) ->
{next_state, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: terminate/3
%% Purpose: Shutdown the fsm
%% Returns: any
%%----------------------------------------------------------------------
terminate(Reason, _StateName, StateData) ->
?INFO_MSG("terminated: ~p", [Reason]),
gen_tcp:close(StateData#state.socket),
ok.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
send_text(State, Text) ->
gen_tcp:send(State#state.socket, Text).
process_request(#state{request_method = 'GET',
request_path = {abs_path, Path},
request_auth = undefined} = State) ->
Out = make_xhtml_output(
401,
[{"WWW-Authenticate", "basic realm=\"ejabberd\""}],
make_xhtml([{xmlelement, "h1", [],
[{xmlcdata, "403 Unauthorized"}]}])),
send_text(State, Out),
ok;
process_request(#state{request_method = 'GET',
request_path = {abs_path, Path},
request_auth = {User, Pass}} = State) ->
Out = make_xhtml_output(
200,
[],
make_xhtml([{xmlelement, "h1", [],
[{xmlcdata, "Welcome " ++ User}]}])),
send_text(State, Out),
ok;
process_request(State) ->
todo.
make_xhtml(Els) ->
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
{"xml:lang", "en"},
{"lang", "en"}],
[{xmlelement, "head", [],
[{xmlelement, "meta", [{"http-equiv", "Content-Type"},
{"content", "text/html; charset=utf-8"}], []}]},
{xmlelement, "body", [], Els}
]}.
make_xhtml_output(Status, Headers, XHTML) ->
Data = list_to_binary([?XHTML_DOCTYPE, xml:element_to_string(XHTML)]),
Headers1 = [{"Content-Type", "text/html; charset=utf-8"},
{"Content-Length", integer_to_list(size(Data))} | Headers],
H = lists:map(fun({Attr, Val}) ->
[Attr, ": ", Val, "\r\n"]
end, Headers1),
SL = ["HTTP/1.1 ", integer_to_list(Status), " ",
code_to_phrase(Status), "\r\n"],
[SL, H, "\r\n", Data].
% Code below is taken (with some modifications) from the yaws webserver, which
% is distributed under the folowing license:
%
%This software (the yaws webserver) is free software.
%Parts of this software is Copyright (c) Claes Wikstrom <klacke@hyber.org>
%Any use or misuse of the source code is hereby freely allowed.
%
%1. Redistributions of source code must retain the above copyright
% notice as well as this list of conditions.
%
%2. Redistributions in binary form must reproduce the above copyright
% notice as well as this list of conditions.
%% url decode the path and return {Path, QueryPart}
url_decode_q_split(Path) ->
url_decode_q_split(Path, []).
url_decode_q_split([$%, $C, $2, $%, Hi, Lo | Tail], Ack) ->
Hex = hex_to_integer([Hi, Lo]),
url_decode_q_split(Tail, [Hex|Ack]);
url_decode_q_split([$%, $C, $3, $%, Hi, Lo | Tail], Ack) when Hi > $9 ->
Hex = hex_to_integer([Hi+4, Lo]),
url_decode_q_split(Tail, [Hex|Ack]);
url_decode_q_split([$%, $C, $3, $%, Hi, Lo | Tail], Ack) when Hi < $A ->
Hex = hex_to_integer([Hi+4+7, Lo]),
url_decode_q_split(Tail, [Hex|Ack]);
url_decode_q_split([$%, Hi, Lo | Tail], Ack) ->
Hex = hex_to_integer([Hi, Lo]),
url_decode_q_split(Tail, [Hex|Ack]);
url_decode_q_split([$?|T], Ack) ->
%% Don't decode the query string here, that is parsed separately.
{path_norm_reverse(Ack), T};
url_decode_q_split([H|T], Ack) ->
url_decode_q_split(T, [H|Ack]);
url_decode_q_split([], Ack) ->
{path_norm_reverse(Ack), []}.
path_norm_reverse("/" ++ T) -> start_dir(0, "/", T);
path_norm_reverse( T) -> start_dir(0, "", T).
start_dir(N, Path, ".." ) -> rest_dir(N, Path, "");
start_dir(N, Path, "/" ++ T ) -> start_dir(N , Path, T);
start_dir(N, Path, "./" ++ T ) -> start_dir(N , Path, T);
start_dir(N, Path, "../" ++ T ) -> start_dir(N + 1, Path, T);
start_dir(N, Path, T ) -> rest_dir (N , Path, T).
rest_dir (_N, Path, [] ) -> case Path of
[] -> "/";
_ -> Path
end;
rest_dir (0, Path, [ $/ | T ] ) -> start_dir(0 , [ $/ | Path ], T);
rest_dir (N, Path, [ $/ | T ] ) -> start_dir(N - 1, Path , T);
rest_dir (0, Path, [ H | T ] ) -> rest_dir (0 , [ H | Path ], T);
rest_dir (N, Path, [ _H | T ] ) -> rest_dir (N , Path , T).
%% hex_to_integer
hex_to_integer(Hex) ->
case catch erlang:list_to_integer(Hex, 16) of
{'EXIT', _} ->
old_hex_to_integer(Hex);
X ->
X
end.
old_hex_to_integer(Hex) ->
DEHEX = fun (H) when H >= $a, H =< $f -> H - $a + 10;
(H) when H >= $A, H =< $F -> H - $A + 10;
(H) when H >= $0, H =< $9 -> H - $0
end,
lists:foldl(fun(E, Acc) -> Acc*16+DEHEX(E) end, 0, Hex).
code_to_phrase(100) -> "Continue";
code_to_phrase(101) -> "Switching Protocols ";
code_to_phrase(200) -> "OK";
code_to_phrase(201) -> "Created";
code_to_phrase(202) -> "Accepted";
code_to_phrase(203) -> "Non-Authoritative Information";
code_to_phrase(204) -> "No Content";
code_to_phrase(205) -> "Reset Content";
code_to_phrase(206) -> "Partial Content";
code_to_phrase(300) -> "Multiple Choices";
code_to_phrase(301) -> "Moved Permanently";
code_to_phrase(302) -> "Found";
code_to_phrase(303) -> "See Other";
code_to_phrase(304) -> "Not Modified";
code_to_phrase(305) -> "Use Proxy";
code_to_phrase(306) -> "(Unused)";
code_to_phrase(307) -> "Temporary Redirect";
code_to_phrase(400) -> "Bad Request";
code_to_phrase(401) -> "Unauthorized";
code_to_phrase(402) -> "Payment Required";
code_to_phrase(403) -> "Forbidden";
code_to_phrase(404) -> "Not Found";
code_to_phrase(405) -> "Method Not Allowed";
code_to_phrase(406) -> "Not Acceptable";
code_to_phrase(407) -> "Proxy Authentication Required";
code_to_phrase(408) -> "Request Timeout";
code_to_phrase(409) -> "Conflict";
code_to_phrase(410) -> "Gone";
code_to_phrase(411) -> "Length Required";
code_to_phrase(412) -> "Precondition Failed";
code_to_phrase(413) -> "Request Entity Too Large";
code_to_phrase(414) -> "Request-URI Too Long";
code_to_phrase(415) -> "Unsupported Media Type";
code_to_phrase(416) -> "Requested Range Not Satisfiable";
code_to_phrase(417) -> "Expectation Failed";
code_to_phrase(500) -> "Internal Server Error";
code_to_phrase(501) -> "Not Implemented";
code_to_phrase(502) -> "Bad Gateway";
code_to_phrase(503) -> "Service Unavailable";
code_to_phrase(504) -> "Gateway Timeout";
code_to_phrase(505) -> "HTTP Version Not Supported".
parse_auth(Orig = "Basic " ++ Auth64) ->
case decode_base64(Auth64) of
{error, _Err} ->
undefined;
Auth ->
case string:tokens(Auth, ":") of
[User, Pass] ->
{User, Pass};
_ ->
undefined
end
end;
parse_auth(_) ->
undefined.
decode_base64([]) ->
[];
decode_base64([Sextet1,Sextet2,$=,$=|Rest]) ->
Bits2x6=
(d(Sextet1) bsl 18) bor
(d(Sextet2) bsl 12),
Octet1=Bits2x6 bsr 16,
[Octet1|decode_base64(Rest)];
decode_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|decode_base64(Rest)];
decode_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|decode_base64(Rest)];
decode_base64(_CatchAll) ->
{error, bad_base64}.
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.

328
src/web/ejabberd_wcs.erl Normal file
View File

@ -0,0 +1,328 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_wcs.erl
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose : Web Client Service
%%% Created : 13 Jul 2004 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$
%%%----------------------------------------------------------------------
-module(ejabberd_wcs).
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-behaviour(gen_fsm).
%% External exports
-export([start_link/2,
init/1,
handle_event/3,
handle_sync_event/4,
code_change/4,
handle_info/3,
terminate/3,
send/2,
recv/3,
close/1,
process_request/1]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("ejabberd_http.hrl").
-record(http_poll, {id, pid}).
-record(state, {id,
key,
output = "",
input = "",
waiting_input = false,
timer}).
%-define(DBGFSM, true).
-ifdef(DBGFSM).
-define(FSMOPTS, [{debug, [trace]}]).
-else.
-define(FSMOPTS, []).
-endif.
-define(HTTP_POLL_TIMEOUT, 300000).
-define(CT, {"Content-Type", "text/xml; charset=utf-8"}).
-define(BAD_REQUEST, [?CT, {"Set-Cookie", "ID=-3:0; expires=-1"}]).
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(ID, Key) ->
mnesia:create_table(http_poll,
[{ram_copies, [node()]},
{attributes, record_info(fields, http_poll)}]),
supervisor:start_child(ejabberd_http_poll_sup, [ID, Key]).
start_link(ID, Key) ->
gen_fsm:start_link(?MODULE, [ID, Key], ?FSMOPTS).
send({http_poll, FsmRef}, Packet) ->
gen_fsm:sync_send_all_state_event(FsmRef, {send, Packet}).
recv({http_poll, FsmRef}, _Length, Timeout) ->
gen_fsm:sync_send_all_state_event(FsmRef, recv, Timeout).
close({http_poll, FsmRef}) ->
catch gen_fsm:sync_send_all_state_event(FsmRef, close).
process_request(#request{path = [],
data = Data} = Request) ->
case catch parse_request(Data) of
{ok, ID1, Key, NewKey, Packet} ->
ID = if
(ID1 == "0") or (ID1 == "mobile") ->
NewID = sha:sha(term_to_binary({now(), make_ref()})),
{ok, Pid} = start(NewID, ""),
mnesia:transaction(
fun() ->
mnesia:write(#http_poll{id = NewID,
pid = Pid})
end),
NewID;
true ->
ID1
end,
case http_put(ID, Key, NewKey, Packet) of
{error, not_exists} ->
{200, ?BAD_REQUEST, ""};
{error, bad_key} ->
{200, ?BAD_REQUEST, ""};
ok ->
receive
after 100 -> ok
end,
case http_get(ID) of
{error, not_exists} ->
{200, [?BAD_REQUEST], ""};
{ok, OutPacket} ->
if
ID == ID1 ->
{200, [?CT], OutPacket};
ID1 == "mobile" ->
{200, [?CT], [ID, $\n, OutPacket]};
true ->
Cookie = "ID=" ++ ID ++ "; expires=-1",
{200, [?CT, {"Set-Cookie", Cookie}],
OutPacket}
end
end
end;
_ ->
{200, [?CT, {"Set-Cookie", "ID=-2:0; expires=-1"}], ""}
end.
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok, StateName, StateData} |
%% {ok, StateName, StateData, Timeout} |
%% ignore |
%% {stop, StopReason}
%%----------------------------------------------------------------------
init([ID, Key]) ->
?INFO_MSG("started: ~p", [{ID, Key}]),
Opts = [], % TODO
ejabberd_c2s:start({?MODULE, {http_poll, self()}}, Opts),
Timer = erlang:start_timer(?HTTP_POLL_TIMEOUT, self(), []),
{ok, loop, #state{id = ID,
key = Key,
timer = Timer}}.
%%----------------------------------------------------------------------
%% Func: StateName/2
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
%% Func: StateName/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {reply, Reply, NextStateName, NextStateData} |
%% {reply, Reply, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
%state_name(Event, From, StateData) ->
% Reply = ok,
% {reply, Reply, state_name, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_event/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_event(Event, StateName, StateData) ->
{next_state, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_sync_event/4
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {reply, Reply, NextStateName, NextStateData} |
%% {reply, Reply, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
handle_sync_event({send, Packet}, From, StateName, StateData) ->
Output = [StateData#state.output | Packet],
Reply = ok,
{reply, Reply, StateName, StateData#state{output = Output}};
handle_sync_event(recv, From, StateName, StateData) ->
case StateData#state.input of
"" ->
{next_state, StateName, StateData#state{waiting_input = From}};
Input ->
Reply = {ok, list_to_binary(Input)},
{reply, Reply, StateName, StateData#state{input = "",
waiting_input = false}}
end;
handle_sync_event(stop, From, StateName, StateData) ->
Reply = ok,
{stop, normal, Reply, StateData};
handle_sync_event({http_put, Key, NewKey, Packet},
From, StateName, StateData) ->
Allow = case StateData#state.key of
"" ->
true;
OldKey ->
NextKey = jlib:encode_base64(
binary_to_list(crypto:sha(Key))),
if
OldKey == NextKey ->
true;
true ->
false
end
end,
if
Allow ->
case StateData#state.waiting_input of
false ->
Input = [StateData#state.input | Packet],
Reply = ok,
{reply, Reply, StateName, StateData#state{input = Input,
key = NewKey}};
Receiver ->
gen_fsm:reply(Receiver, {ok, list_to_binary(Packet)}),
cancel_timer(StateData#state.timer),
Timer = erlang:start_timer(?HTTP_POLL_TIMEOUT, self(), []),
Reply = ok,
{reply, Reply, StateName,
StateData#state{waiting_input = false,
key = NewKey,
timer = Timer}}
end;
true ->
Reply = {error, bad_key},
{reply, Reply, StateName, StateData}
end;
handle_sync_event(http_get, From, StateName, StateData) ->
Reply = {ok, StateData#state.output},
{reply, Reply, StateName, StateData#state{output = ""}};
handle_sync_event(Event, From, StateName, StateData) ->
Reply = ok,
{reply, Reply, StateName, StateData}.
code_change(OldVsn, StateName, StateData, Extra) ->
{ok, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_info/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
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
%% Returns: any
%%----------------------------------------------------------------------
terminate(Reason, StateName, StateData) ->
mnesia:transaction(
fun() ->
mnesia:delete({http_poll, StateData#state.id})
end),
case StateData#state.waiting_input of
false ->
ok;
Receiver ->
gen_fsm:reply(Receiver, {error, closed})
end,
ok.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
http_put(ID, Key, NewKey, Packet) ->
case mnesia:dirty_read({http_poll, ID}) of
[] ->
{error, not_exists};
[#http_poll{pid = FsmRef}] ->
gen_fsm:sync_send_all_state_event(
FsmRef, {http_put, Key, NewKey, Packet})
end.
http_get(ID) ->
case mnesia:dirty_read({http_poll, ID}) of
[] ->
{error, not_exists};
[#http_poll{pid = FsmRef}] ->
gen_fsm:sync_send_all_state_event(FsmRef, http_get)
end.
parse_request(Data) ->
Comma = string:chr(Data, $,),
Header = lists:sublist(Data, Comma - 1),
Packet = lists:nthtail(Comma, Data),
{ID, Key, NewKey} =
case string:tokens(Header, ";") of
[ID1] ->
{ID1, "", ""};
[ID1, Key1] ->
{ID1, Key1, Key1};
[ID1, Key1, NewKey1] ->
{ID1, Key1, NewKey1}
end,
{ok, ID, Key, NewKey, Packet}.
cancel_timer(Timer) ->
erlang:cancel_timer(Timer),
receive
{timeout, Timer, _} ->
ok
after 0 ->
ok
end.

View File

@ -50,20 +50,20 @@ make_xhtml(Els) ->
process_get({_, true},
#request{user = User,
#request{us = US,
path = ["admin" | RPath],
q = Query,
lang = Lang} = Request) ->
if
User /= undefined ->
case acl:match_rule(configure, jlib:make_jid(User, ?MYNAME, "")) of
case US of
{User, Server} ->
case acl:match_rule(configure, jlib:make_jid(User, Server, "")) of
deny ->
{401, [], make_xhtml([?XC("h1", "Not Allowed")])};
allow ->
ejabberd_web_admin:process_admin(
Request#request{path = RPath})
end;
true ->
undefined ->
{401,
[{"WWW-Authenticate", "basic realm=\"ejabberd\""}],
ejabberd_web:make_xhtml([{xmlelement, "h1", [],
@ -71,10 +71,10 @@ process_get({_, true},
end;
process_get({true, _},
#request{user = User,
#request{us = _US,
path = ["http-poll" | RPath],
q = Query,
lang = Lang} = Request) ->
q = _Query,
lang = _Lang} = Request) ->
ejabberd_http_poll:process_request(Request#request{path = RPath});
process_get(_, _Request) ->

View File

@ -5,8 +5,8 @@
%%% Created : 9 Apr 2004 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$
%%%----------------------------------------------------------------------
%%% Copyright (c) 2004 Alexey Shchepin
%%% Copyright (c) 2004 Process One
%%% Copyright (c) 2004-2005 Alexey Shchepin
%%% Copyright (c) 2004-2005 Process One
%%%----------------------------------------------------------------------
-module(ejabberd_web_admin).
@ -119,7 +119,7 @@ make_xhtml(Els, Lang) ->
[?XE("tbody",
[?XE("tr",
[?XCT("td",
"ejabberd (c) 2002-2005 Alexey Shchepin, 2004 Process One")
"ejabberd (c) 2002-2005 Alexey Shchepin, 2004-2005 Process One")
])])
])])])])])])
]}}.
@ -304,13 +304,13 @@ input[type=submit] {
}
textarea {
border: 1px solid #93a6c7;
color: #556655;
background-color: #ffffff;
border: 1px solid #d6760e;
color: #723202;
background-color: #fff2e8;
vertical-align: middle;
margin-top: 7px;
margin-left: 7px;
margin-right: 7px;
/*margin-left: 7px;
margin-right: 7px;*/
margin-bottom: 5px;
padding: 0.1em;
}
@ -494,7 +494,7 @@ empty() ->
jlib:decode_base64(
"R0lGODlhAQABAIAAAP///////yH+FUNyZWF0ZWQgd2l0aCBUaGUgR0lNUAAh+QQBCgABACwAAAAAAQABAAACAkwBADs=").
process_admin(#request{user = User,
process_admin(#request{us = US,
path = [],
q = Query,
lang = Lang} = Request) ->
@ -511,31 +511,31 @@ process_admin(#request{user = User,
])
], Lang);
process_admin(#request{user = User,
process_admin(#request{us = US,
path = ["style.css"],
q = Query,
lang = Lang} = Request) ->
{200, [{"Content-Type", "text/css"}], css()};
process_admin(#request{user = User,
process_admin(#request{us = US,
path = ["logo.png"],
q = Query,
lang = Lang} = Request) ->
{200, [{"Content-Type", "image/png"}], logo()};
process_admin(#request{user = User,
process_admin(#request{us = US,
path = ["logo-fill.png"],
q = Query,
lang = Lang} = Request) ->
{200, [{"Content-Type", "image/png"}], logo_fill()};
process_admin(#request{user = User,
process_admin(#request{us = US,
path = ["1x1tr.gif"],
q = Query,
lang = Lang} = Request) ->
{200, [{"Content-Type", "image/gif"}], empty()};
process_admin(#request{user = User,
process_admin(#request{us = US,
path = ["acls-raw"],
q = Query,
lang = Lang} = Request) ->
@ -578,7 +578,7 @@ process_admin(#request{user = User,
], Lang);
process_admin(#request{method = Method,
user = User,
us = US,
path = ["acls"],
q = Query,
lang = Lang} = Request) ->
@ -618,7 +618,7 @@ process_admin(#request{method = Method,
])
], Lang);
process_admin(#request{user = User,
process_admin(#request{us = US,
path = ["access-raw"],
q = Query,
lang = Lang} = Request) ->
@ -686,7 +686,7 @@ process_admin(#request{user = User,
], Lang);
process_admin(#request{method = Method,
user = User,
us = US,
path = ["access"],
q = Query,
lang = Lang} = Request) ->
@ -722,7 +722,7 @@ process_admin(#request{method = Method,
], Lang);
process_admin(#request{method = Method,
user = User,
us = US,
path = ["access", SName],
q = Query,
lang = Lang} = Request) ->
@ -761,63 +761,63 @@ process_admin(#request{method = Method,
])
], Lang);
process_admin(#request{user = User,
process_admin(#request{us = US,
path = ["users"],
q = Query,
lang = Lang} = Request) ->
Res = list_users(Query, Lang),
make_xhtml([?XCT("h1", "ejabberd users")] ++ Res, Lang);
process_admin(#request{user = User,
process_admin(#request{us = US,
path = ["users", Diap],
q = Query,
lang = Lang} = Request) ->
Res = list_users_in_diapason(Diap, Lang),
make_xhtml([?XCT("h1", "ejabberd users")] ++ Res, Lang);
process_admin(#request{user = User,
process_admin(#request{us = US,
path = ["online-users"],
q = Query,
lang = Lang} = Request) ->
Res = list_online_users(Lang),
make_xhtml([?XCT("h1", "ejabberd users")] ++ Res, Lang);
process_admin(#request{user = User,
process_admin(#request{us = US,
path = ["stats"],
q = Query,
lang = Lang} = Request) ->
Res = get_stats(Lang),
make_xhtml([?XCT("h1", "ejabberd stats")] ++ Res, Lang);
process_admin(#request{user = User,
process_admin(#request{us = US,
path = ["user", U],
q = Query,
lang = Lang} = Request) ->
Res = user_info(U, Query, Lang),
make_xhtml(Res, Lang);
process_admin(#request{user = User,
process_admin(#request{us = US,
path = ["user", U, "queue"],
q = Query,
lang = Lang} = Request) ->
Res = user_queue(U, Query, Lang),
make_xhtml(Res, Lang);
process_admin(#request{user = User,
process_admin(#request{us = US,
path = ["user", U, "roster"],
q = Query,
lang = Lang} = Request) ->
Res = user_roster(U, Query, Lang, true),
make_xhtml(Res, Lang);
process_admin(#request{user = User,
process_admin(#request{us = US,
path = ["nodes"],
q = Query,
lang = Lang} = Request) ->
Res = get_nodes(Lang),
make_xhtml(Res, Lang);
process_admin(#request{user = User,
process_admin(#request{us = US,
path = ["node", SNode | NPath],
q = Query,
lang = Lang} = Request) ->
@ -829,6 +829,20 @@ process_admin(#request{user = User,
make_xhtml(Res, Lang)
end;
process_admin(#request{us = US,
path = ["shared-roster"],
q = Query,
lang = Lang} = Request) ->
Res = list_shared_roster_groups(Query, Lang),
make_xhtml(Res, Lang);
process_admin(#request{us = US,
path = ["shared-roster", Group],
q = Query,
lang = Lang} = Request) ->
Res = shared_roster_group(Group, Query, Lang),
make_xhtml(Res, Lang);
process_admin(#request{lang = Lang}) ->
setelement(1, make_xhtml([?XC("h1", "Not found")], Lang), 404).
@ -1065,7 +1079,7 @@ parse_access_rule(Text) ->
list_users(Query, Lang) ->
Res = list_users_parse_query(Query),
Users = ejabberd_auth:dirty_get_registered_users(),
SUsers = lists:sort(Users),
SUsers = lists:sort([{S, U} || {U, S} <- Users]),
FUsers =
case length(SUsers) of
N when N =< 100 ->
@ -1077,11 +1091,12 @@ list_users(Query, Lang) ->
fun(K) ->
L = K + M - 1,
Node = integer_to_list(K) ++ "-" ++ integer_to_list(L),
Last = if L < N -> lists:nth(L, SUsers);
true -> lists:last(SUsers)
Last = if L < N -> su_to_list(lists:nth(L, SUsers));
true -> su_to_list(lists:last(SUsers))
end,
Name =
lists:nth(K, SUsers) ++ [$\s, 226, 128, 148, $\s] ++
su_to_list(lists:nth(K, SUsers)) ++
[$\s, 226, 128, 148, $\s] ++
Last,
[?AC(Node ++ "/", Name), ?BR]
end, lists:seq(1, N, M))
@ -1131,7 +1146,7 @@ list_users_parse_query(Query) ->
list_users_in_diapason(Diap, Lang) ->
Users = ejabberd_auth:dirty_get_registered_users(),
SUsers = lists:sort(Users),
SUsers = lists:sort([{S, U} || {U, S} <- Users]),
{ok, [S1, S2]} = regexp:split(Diap, "-"),
N1 = list_to_integer(S1),
N2 = list_to_integer(S2),
@ -1147,14 +1162,15 @@ list_given_users(Users, Prefix, Lang) ->
?XCT("td", "Last Activity")])]),
?XE("tbody",
lists:map(
fun(User) ->
QueueLen = length(mnesia:dirty_read({offline_msg, User})),
fun(SU = {Server, User}) ->
US = {User, Server},
QueueLen = length(mnesia:dirty_read({offline_msg, US})),
FQueueLen = [?AC(Prefix ++ "user/" ++ User ++ "/queue/",
integer_to_list(QueueLen))],
FLast =
case ejabberd_sm:get_user_resources(User) of
case ejabberd_sm:get_user_resources(User, Server) of
[] ->
case mnesia:dirty_read({last_activity, User}) of
case mnesia:dirty_read({last_activity, US}) of
[] ->
?T("Never");
[E] ->
@ -1173,13 +1189,20 @@ list_given_users(Users, Prefix, Lang) ->
?T("Online")
end,
?XE("tr",
[?XE("td", [?AC(Prefix ++ "user/" ++ User ++ "/",
User)]),
[?XE("td", [?AC(Prefix ++ "user/" ++
us_to_list(US) ++ "/",
us_to_list(US))]),
?XE("td", FQueueLen),
?XC("td", FLast)])
end, Users)
)]).
us_to_list({User, Server}) ->
jlib:jid_to_string({User, Server, ""}).
su_to_list({Server, User}) ->
jlib:jid_to_string({User, Server, ""}).
get_stats(Lang) ->
OnlineUsers = mnesia:table_info(presence, size),
@ -1206,17 +1229,20 @@ get_stats(Lang) ->
list_online_users(_Lang) ->
Users = lists:map(fun({U, R}) -> U end,
ejabberd_sm:dirty_get_sessions_list()),
Users = [{S, U} || {U, S, R} <- ejabberd_sm:dirty_get_sessions_list()],
SUsers = lists:usort(Users),
lists:flatmap(
fun(U) ->
[?AC("../user/" ++ U ++ "/", U), ?BR]
fun(SU) ->
[?AC("../user/" ++ su_to_list(SU) ++ "/", su_to_list(SU)), ?BR]
end, SUsers).
user_info(User, Query, Lang) ->
Res = user_parse_query(User, Query),
Resources = ejabberd_sm:get_user_resources(User),
user_info(SUser, Query, Lang) ->
UJID = jlib:string_to_jid(SUser),
User = UJID#jid.user,
Server = UJID#jid.server,
US = {UJID#jid.luser, UJID#jid.lserver},
Res = user_parse_query(User, Server, Query),
Resources = ejabberd_sm:get_user_resources(User, Server),
FResources =
case Resources of
[] ->
@ -1227,13 +1253,13 @@ user_info(User, Query, Lang) ->
?LI([?C(R)])
end, lists:sort(Resources)))]
end,
Password = ejabberd_auth:get_password_s(User),
Password = ejabberd_auth:get_password_s(User, Server),
FPassword = [?INPUT("password", "password", Password), ?C(" "),
?INPUTT("submit", "chpassword", "Change Password")],
QueueLen = length(mnesia:dirty_read({offline_msg, User})),
QueueLen = length(mnesia:dirty_read({offline_msg, US})),
FQueueLen = [?AC("queue/",
integer_to_list(QueueLen))],
[?XC("h1", ?T("User ") ++ User)] ++
[?XC("h1", ?T("User ") ++ us_to_list(US))] ++
case Res of
ok -> [?CT("submitted"), ?P];
error -> [?CT("bad format"), ?P];
@ -1247,14 +1273,14 @@ user_info(User, Query, Lang) ->
[?BR, ?INPUTT("submit", "removeuser", "Remove User")])].
user_parse_query(User, Query) ->
user_parse_query(User, Server, Query) ->
case lists:keysearch("chpassword", 1, Query) of
{value, _} ->
case lists:keysearch("password", 1, Query) of
{value, {_, undefined}} ->
error;
{value, {_, Password}} ->
ejabberd_auth:set_password(User, Password),
ejabberd_auth:set_password(User, Server, Password),
ok;
_ ->
error
@ -1262,7 +1288,7 @@ user_parse_query(User, Query) ->
_ ->
case lists:keysearch("removeuser", 1, Query) of
{value, _} ->
ejabberd_auth:remove_user(User),
ejabberd_auth:remove_user(User, Server),
ok;
false ->
nothing
@ -1270,12 +1296,16 @@ user_parse_query(User, Query) ->
end.
user_queue(User, Query, Lang) ->
Res = user_queue_parse_query(User, Query),
Msgs = lists:keysort(3, mnesia:dirty_read({offline_msg, User})),
user_queue(SUser, Query, Lang) ->
UJID = jlib:string_to_jid(SUser),
User = UJID#jid.user,
Server = UJID#jid.server,
US = {UJID#jid.luser, UJID#jid.lserver},
Res = user_queue_parse_query(US, Query),
Msgs = lists:keysort(3, mnesia:dirty_read({offline_msg, US})),
FMsgs =
lists:map(
fun({offline_msg, _User, TimeStamp, _Expire, From, To,
fun({offline_msg, _US, TimeStamp, _Expire, From, To,
{xmlelement, Name, Attrs, Els}} = Msg) ->
ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg))),
{{Year, Month, Day}, {Hour, Minute, Second}} =
@ -1298,7 +1328,8 @@ user_queue(User, Query, Lang) ->
?XAE("td", [{"class", "valign"}], [?XC("pre", FPacket)])]
)
end, Msgs),
[?XC("h1", io_lib:format(?T("~s offline messages queue"), [User]))] ++
[?XC("h1", io_lib:format(?T("~s offline messages queue"),
[us_to_list(US)]))] ++
case Res of
ok -> [?CT("submitted"), ?P];
error -> [?CT("bad format"), ?P];
@ -1319,10 +1350,10 @@ user_queue(User, Query, Lang) ->
?INPUTT("submit", "delete", "Delete Selected")
])].
user_queue_parse_query(User, Query) ->
user_queue_parse_query(US, Query) ->
case lists:keysearch("delete", 1, Query) of
{value, _} ->
Msgs = lists:keysort(3, mnesia:dirty_read({offline_msg, User})),
Msgs = lists:keysort(3, mnesia:dirty_read({offline_msg, US})),
F = fun() ->
lists:foreach(
fun(Msg) ->
@ -1344,8 +1375,8 @@ user_queue_parse_query(User, Query) ->
-record(roster, {uj,
user,
-record(roster, {usj,
us,
jid,
name = "",
subscription = none,
@ -1359,11 +1390,14 @@ ask_to_pending(unsubscribe) -> none;
ask_to_pending(Ask) -> Ask.
user_roster(User, Query, Lang, Admin) ->
LUser = jlib:nameprep(User),
Items1 = mnesia:dirty_index_read(roster, LUser, #roster.user),
Res = user_roster_parse_query(User, Items1, Query, Admin),
Items = mnesia:dirty_index_read(roster, LUser, #roster.user),
user_roster(SUser, Query, Lang, Admin) ->
UJID = jlib:string_to_jid(SUser),
User = UJID#jid.user,
Server = UJID#jid.server,
US = {UJID#jid.luser, UJID#jid.lserver},
Items1 = mnesia:dirty_index_read(roster, US, #roster.us),
Res = user_roster_parse_query(User, Server, Items1, Query, Admin),
Items = mnesia:dirty_index_read(roster, US, #roster.us),
SItems = lists:sort(Items),
FItems =
case SItems of
@ -1415,7 +1449,7 @@ user_roster(User, Query, Lang, Admin) ->
"Remove")])])
end, SItems))])]
end,
[?XC("h1", ?T("Roster of ") ++ User)] ++
[?XC("h1", ?T("Roster of ") ++ us_to_list(US))] ++
case Res of
ok -> [?CT("submitted"), ?P];
error -> [?CT("bad format"), ?P];
@ -1428,7 +1462,7 @@ user_roster(User, Query, Lang, Admin) ->
?INPUTT("submit", "addjid", "Add JID")
])].
user_roster_parse_query(User, Items, Query, Admin) ->
user_roster_parse_query(User, Server, Items, Query, Admin) ->
case lists:keysearch("addjid", 1, Query) of
{value, _} ->
case lists:keysearch("newjid", 1, Query) of
@ -1437,7 +1471,7 @@ user_roster_parse_query(User, Items, Query, Admin) ->
{value, {_, SJID}} ->
case jlib:string_to_jid(SJID) of
JID when is_record(JID, jid) ->
user_roster_subscribe_jid(User, JID),
user_roster_subscribe_jid(User, Server, JID),
ok;
error ->
error
@ -1445,32 +1479,9 @@ user_roster_parse_query(User, Items, Query, Admin) ->
false ->
error
end;
false ->
case lists:keysearch("adduser", 1, Query) of
{value, _} ->
case lists:keysearch("newuser", 1, Query) of
{value, {_, undefined}} ->
error;
{value, {_, U}} ->
if
Admin ->
user_roster_subscribe_users(User, U);
true ->
case jlib:make_jid(U, ?MYNAME, "") of
JID when is_record(JID, jid) ->
user_roster_subscribe_jid(
User, JID),
ok;
false ->
error
end
end;
false ->
error
end;
false ->
case catch user_roster_item_parse_query(
User, Items, Query) of
User, Server, Items, Query) of
submitted ->
ok;
{'EXIT', _Reason} ->
@ -1478,37 +1489,16 @@ user_roster_parse_query(User, Items, Query, Admin) ->
_ ->
nothing
end
end
end.
user_roster_subscribe_users(User1, User2) ->
case jlib:make_jid(User1, ?MYNAME, "") of
JID1 when is_record(JID1, jid) ->
case jlib:make_jid(User2, ?MYNAME, "") of
JID2 when is_record(JID2, jid) ->
mod_roster:out_subscription(User1, JID2, subscribe),
mod_roster:in_subscription(User2, JID1, subscribe),
mod_roster:out_subscription(User2, JID1, subscribe),
mod_roster:in_subscription(User1, JID2, subscribe),
mod_roster:out_subscription(User1, JID2, subscribed),
mod_roster:in_subscription(User2, JID1, subscribed),
mod_roster:out_subscription(User2, JID1, subscribed),
mod_roster:in_subscription(User1, JID2, subscribed),
ok;
false ->
error
end;
false ->
error
end.
user_roster_subscribe_jid(User, JID) ->
mod_roster:out_subscription(User, JID, subscribe),
UJID = jlib:make_jid(User, ?MYNAME, ""),
user_roster_subscribe_jid(User, Server, JID) ->
mod_roster:out_subscription(User, Server, JID, subscribe),
UJID = jlib:make_jid(User, Server, ""),
ejabberd_router:route(
UJID, JID, {xmlelement, "presence", [{"type", "subscribe"}], []}).
user_roster_item_parse_query(User, Items, Query) ->
user_roster_item_parse_query(User, Server, Items, Query) ->
lists:foreach(
fun(R) ->
JID = R#roster.jid,
@ -1516,8 +1506,9 @@ user_roster_item_parse_query(User, Items, Query) ->
"validate" ++ term_to_id(JID), 1, Query) of
{value, _} ->
JID1 = jlib:make_jid(JID),
mod_roster:out_subscription(User, JID1, subscribed),
UJID = jlib:make_jid(User, ?MYNAME, ""),
mod_roster:out_subscription(
User, Server, JID1, subscribed),
UJID = jlib:make_jid(User, Server, ""),
ejabberd_router:route(
UJID, JID1, {xmlelement, "presence",
[{"type", "subscribed"}], []}),
@ -1526,7 +1517,7 @@ user_roster_item_parse_query(User, Items, Query) ->
case lists:keysearch(
"remove" ++ term_to_id(JID), 1, Query) of
{value, _} ->
UJID = jlib:make_jid(User, ?MYNAME, ""),
UJID = jlib:make_jid(User, Server, ""),
mod_roster:process_iq(
UJID, UJID,
#iq{type = set,
@ -2038,3 +2029,200 @@ pretty_print({xmlelement, Name, Attrs, Els}, Prefix) ->
end
end].
list_shared_roster_groups(Query, Lang) ->
Res = list_sr_groups_parse_query(Query),
SRGroups = mod_shared_roster:list_groups(?MYNAME),
FGroups =
?XAE("table", [],
[?XE("tbody",
lists:map(
fun(Group) ->
?XE("tr",
[?XE("td", [?INPUT("checkbox", "selected",
Group)]),
?XE("td", [?AC(Group ++ "/", Group)])
]
)
end, lists:sort(SRGroups)) ++
[?XE("tr",
[?X("td"),
?XE("td", [?INPUT("text", "namenew", "")]),
?XE("td", [?INPUTT("submit", "addnew", "Add New")])
]
)]
)]),
[?XC("h1", ?T("Shared roster groups"))] ++
case Res of
ok -> [?CT("submitted"), ?P];
error -> [?CT("bad format"), ?P];
nothing -> []
end ++
[?XAE("form", [{"method", "post"}],
[FGroups,
?BR,
?INPUTT("submit", "delete", "Delete Selected")
])
].
list_sr_groups_parse_query(Query) ->
case lists:keysearch("addnew", 1, Query) of
{value, _} ->
list_sr_groups_parse_addnew(Query);
_ ->
case lists:keysearch("delete", 1, Query) of
{value, _} ->
list_sr_groups_parse_delete(Query);
_ ->
nothing
end
end.
list_sr_groups_parse_addnew(Query) ->
case lists:keysearch("namenew", 1, Query) of
{value, {_, Group}} when Group /= "" ->
mod_shared_roster:create_group(?MYNAME, Group),
ok;
_ ->
error
end.
list_sr_groups_parse_delete(Query) ->
SRGroups = mod_shared_roster:list_groups(?MYNAME),
lists:foreach(
fun(Group) ->
case lists:member({"selected", Group}, Query) of
true ->
mod_shared_roster:delete_group(?MYNAME, Group);
_ ->
ok
end
end, SRGroups),
ok.
shared_roster_group(Group, Query, Lang) ->
Res = shared_roster_group_parse_query(?MYNAME, Group, Query),
GroupOpts = mod_shared_roster:get_group_opts(?MYNAME, Group),
Name = get_opt(GroupOpts, name, ""),
Description = get_opt(GroupOpts, description, ""),
Members = mod_shared_roster:get_group_users(?MYNAME, Group),
Disabled = false,
DisplayedGroups = get_opt(GroupOpts, displayed_groups, []),
FMembers = [[us_to_list(Member), $\n] || Member <- Members],
FDisplayedGroups = [[DG, $\n] || DG <- DisplayedGroups],
FGroup =
?XAE("table", [],
[?XE("tbody",
[?XE("tr",
[?XCT("td", "Name:"),
?XE("td", [?INPUT("text", "name", Name)])
]
),
?XE("tr",
[?XCT("td", "Description:"),
?XE("td", [?XAC("textarea", [{"name", "description"},
{"rows", "3"},
{"cols", "20"}],
Description)])
]
),
?XE("tr",
[?XCT("td", "Members:"),
?XE("td", [?XAC("textarea", [{"name", "members"},
{"rows", "3"},
{"cols", "20"}],
FMembers)])
]
),
?XE("tr",
[?XCT("td", "Displayed Groups:"),
?XE("td", [?XAC("textarea", [{"name", "dispgroups"},
{"rows", "3"},
{"cols", "20"}],
FDisplayedGroups)])
]
)]
)]),
[?XC("h1", ?T("Shared roster group " ++ Group))] ++
case Res of
ok -> [?CT("submitted"), ?P];
error -> [?CT("bad format"), ?P];
nothing -> []
end ++
[?XAE("form", [{"method", "post"}],
[FGroup,
?BR,
?INPUTT("submit", "submit", "Submit")
])
].
shared_roster_group_parse_query(Host, Group, Query) ->
case lists:keysearch("submit", 1, Query) of
{value, _} ->
{value, {_, Name}} = lists:keysearch("name", 1, Query),
{value, {_, Description}} = lists:keysearch("description", 1, Query),
{value, {_, SMembers}} = lists:keysearch("members", 1, Query),
{value, {_, SDispGroups}} = lists:keysearch("dispgroups", 1, Query),
NameOpt =
if
Name == "" -> [];
true -> [{name, Name}]
end,
DescriptionOpt =
if
Description == "" -> [];
true -> [{description, Description}]
end,
DispGroups = string:tokens(SDispGroups, "\r\n"),
DispGroupsOpt =
if
DispGroups == [] -> [];
true -> [{displayed_groups, DispGroups}]
end,
mod_shared_roster:set_group_opts(
?MYNAME, Group, NameOpt ++ DispGroupsOpt ++ DescriptionOpt),
OldMembers = mod_shared_roster:get_group_users(?MYNAME, Group),
NewMembers =
lists:foldl(
fun(_SJID, error) -> error;
(SJID, USs) ->
case jlib:string_to_jid(SJID) of
JID when is_record(JID, jid) ->
[{JID#jid.luser, JID#jid.lserver} | USs];
error ->
error
end
end, [], string:tokens(SMembers, "\r\n")),
if
NewMembers == error -> error;
true ->
AddedMembers = NewMembers -- OldMembers,
RemovedMembers = OldMembers -- NewMembers,
lists:foreach(
fun(US) ->
mod_shared_roster:remove_user_from_group(
Host, US, Group)
end, RemovedMembers),
lists:foreach(
fun(US) ->
mod_shared_roster:add_user_to_group(
Host, US, Group)
end, AddedMembers),
ok
end;
_ ->
nothing
end.
get_opt(Opts, Opt, Default) ->
case lists:keysearch(Opt, 1, Opts) of
{value, {_, Val}} ->
Val;
false ->
Default
end.