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

Merge from trunk (r1734 to r1752).

Note: this merge doesn't include the following revisions because it was
made by previous commits:
r1737, r1740, r1745, r1747, r1748.

Additionnaly, any deprecated content was removed from jlib.hrl. This
leaves only the new RSM records.

Warning: Ejabberd may be broken until the merge is completly finished.

PR:		EJABP-1

SVN Revision: 1824
This commit is contained in:
Jean-Sébastien Pédron 2009-01-19 10:14:04 +00:00
parent 04f6a2be37
commit 2cc2039ad8
21 changed files with 551 additions and 407 deletions

122
ChangeLog
View File

@ -1,3 +1,14 @@
2009-01-16 Jean-Sébastien Pédron <js.pedron@meetic-corp.com>
Merge from trunk (r1734 to r1752).
Note: this merge doesn't include the following revisions because it
was made by previous commits:
r1737, r1740, r1745, r1747, r1748.
* src/jlib.hrl: Any deprecated content was removed from jlib.hrl. This
leaves only the new RSM records.
2009-01-15 Pablo Polvorin <pablo.polvorin@process-one.net>
* src/mod_muc/mod_muc.erl, src/mod_muc/mod_muc_room.erl:
@ -28,12 +39,13 @@
* src/mod_vcard_ldap.erl, src/mod_vcard.erl, src/mod_configure.erl,
src/ejabberd_sm.erl, src/mod_privacy_odbc.erl, src/ejabberd_c2s.erl,
src/ejabberd_local.erl, src/mod_privacy.erl, src/mod_adhoc.erl,
src/mod_pubsub/mod_pubsub.erl, src/mod_vcard_odbc.erl, src/mod_stats.erl,
src/mod_last.erl, src/mod_private.erl, src/mod_roster.erl,
src/mod_disco.erl, src/mod_private_odbc.erl, src/mod_configure2.erl,
src/mod_roster_odbc.erl, src/mod_register.erl, src/mod_version.erl,
src/mod_caps.erl, src/mod_last_odbc.erl, src/mod_time.erl: Update
gen_iq_handler API, require the 'Host' argument to be in binary() format.
src/mod_pubsub/mod_pubsub.erl, src/mod_vcard_odbc.erl,
src/mod_stats.erl, src/mod_last.erl, src/mod_private.erl,
src/mod_roster.erl, src/mod_disco.erl, src/mod_private_odbc.erl,
src/mod_configure2.erl, src/mod_roster_odbc.erl, src/mod_register.erl,
src/mod_version.erl, src/mod_caps.erl, src/mod_last_odbc.erl,
src/mod_time.erl: Update gen_iq_handler API, require the 'Host'
argument to be in binary() format.
2009-01-10 Christophe Romain <christophe.romain@process-one.net>
@ -45,18 +57,17 @@
* src/mod_muc/mod_muc_room.erl, src/mod_muc/mod_muc.erl
src/mod_offline_odbc.erl, src/mod_irc/mod_irc_connection.erl,
src/mod_irc/mod_irc.erl, src/ejabberd_c2s.erl, src/ejabberd_local.erl,
src/mod_pubsub/mod_pubsub.erl, src/ejabberd_s2s.erl, src/mod_roster.erl,
src/mod_roster_odbc.erl, src/ejabberd_s2s_out.erl, src/mod_offline.erl,
src/translate.erl: Adapt to new exmpp API where get_id/1, get_lang/1,
get_initiating_entity/1, get_receiving_entity/1 and get_type/1
returns binary().
src/mod_pubsub/mod_pubsub.erl, src/ejabberd_s2s.erl,
src/mod_roster.erl, src/mod_roster_odbc.erl, src/ejabberd_s2s_out.erl,
src/mod_offline.erl, src/translate.erl: Adapt to new exmpp API where
get_id/1, get_lang/1, get_initiating_entity/1, get_receiving_entity/1
and get_type/1 return binary().
* src/mod_pubsub/node_default.erl: Fix typo in variable name.
* src/ejabberd_c2s.erl: Fix bug in handle_info/3 when dealing with
VCARD requests: convert to IQ struct before invoking gen_iq_handler.
2009-01-08 Christophe Romain <christophe.romain@process-one.net>
* src/mod_pubsub/mod_pubsub.erl: completely support subscription using
@ -78,6 +89,7 @@
* src/mod_pubsub/node_club.erl: Likewise
2009-01-08 Pablo Polvorin <pablo.polvorin@process-one.net>
* src/mod_vcard_ldap.erl, src/mod_muc/mod_muc.erl, src/mod_roster.hrl,
src/mod_offline_odbc.erl, src/ejabberd_s2s_in.erl, src/adhoc.erl,
src/mod_configure.erl, src/mod_irc/mod_irc_connection.erl,
@ -86,26 +98,29 @@
src/mod_privacy.erl, src/mod_adhoc.erl, src/mod_pubsub/mod_pubsub.erl,
src/mod_vcard_odbc.erl, src/mod_stats.erl, src/mod_last.erl,
src/mod_roster.erl, src/ejabberd_service.erl, src/mod_disco.erl,
src/mod_configure2.erl, src/mod_roster_odbc.erl, src/ejabberd_s2s_out.erl,
src/mod_last_odbc.erl: XML attributes as binary(). Change Node argument
to binary in the following hooks: disco_local_items, disco_local_features,
disco_local_identity, disco_sm_items and disco_sm_identity.
src/mod_configure2.erl, src/mod_roster_odbc.erl,
src/ejabberd_s2s_out.erl, src/mod_last_odbc.erl: XML attributes as
binary(). Change Node argument to binary in the following hooks:
disco_local_items, disco_local_features, disco_local_identity,
disco_sm_items and disco_sm_identity.
2009-01-05 Pablo Polvorin <pablo.polvorin@process-one.net>
* src/mod_roster.erl: Fix typo.
2009-01-03 Pablo Polvorin <pablo.polvorin@process-one.net>
* src/mod_pubsub_node_default.erl: Fix typo
* src/mod_vcard.erl, src/mod_vcard_ldap.erl,src/ ejabberd_hooks.erl,
mod_muc/mod_muc_room.erl, src/mod_muc/mod_muc.erl,
* src/mod_pubsub_node_default.erl: Fix typo.
* src/mod_vcard.erl, src/mod_vcard_ldap.erl, src/ejabberd_hooks.erl,
src/mod_muc/mod_muc_room.erl, src/mod_muc/mod_muc.erl,
src/mod_muc/mod_muc_log.erl, src/mod_shared_roster.erl,
src/ejabberd_auth_odbc.erl, src/mod_offline_odbc.erl,
src/ejabberd_system_monitor.erl, src/ejabberd_s2s_in.erl,
src/mod_configure.erl, src/ejabberd_receiver.erl, src/mod_irc/mod_irc.erl,
src/ejabberd_sm.erl, src/mod_privacy_odbc.erl, src/ejabberd_c2s.erl,
src/mod_announce.erl, src/ejabberd_local.erl, src/mod_privacy.erl,
src/mod_configure.erl, src/ejabberd_receiver.erl,
src/mod_irc/mod_irc.erl, src/ejabberd_sm.erl,
src/mod_privacy_odbc.erl, src/ejabberd_c2s.erl, src/mod_announce.erl,
src/ejabberd_local.erl, src/mod_privacy.erl,
src/ejabberd_auth_internal.erl, src/mod_adhoc.erl, src/mod_echo.erl,
src/jlib.erl, src/mod_vcard_odbc.erl, src/ejabberd_s2s.erl,
src/mod_stats.erl, src/ejabberd_router.erl, src/mod_last.erl,
@ -113,13 +128,11 @@
src/mod_disco.erl, src/mod_private_odbc.erl, src/mod_service_log.erl,
src/mod_configure2.erl, src/mod_roster_odbc.erl, src/mod_offline.erl,
src/mod_register.erl, src/mod_version.erl, src/mod_caps.erl,
src/mod_last_odbc.erl: Use exmpp API to access JID fields. Keep
#jid fields in binary format when possible. Change all 'user' and
'server' arguments in all hooks to binary. Change internal tables of
ejabberd_sm, ejabberd_router, ejabberd_hooks, mod_last, mod_roster
to use binary() storage.
src/mod_last_odbc.erl: Use exmpp API to access JID fields. Keep #jid
fields in binary format when possible. Change all 'user' and 'server'
arguments in all hooks to binary. Change internal tables of
ejabberd_sm, ejabberd_router, ejabberd_hooks, mod_last, mod_roster to
use binary() storage.
2009-01-03 Christophe Romain <christophe.romain@process-one.net>
@ -142,12 +155,61 @@
* src/mod_pubsub/mod_pubsub.erl: Added "access-whitelist" and
"member-affiliation" features (thanks to Andy Skelton)(EJAB-780)
2008-12-23 Badlop <badlop@process-one.net>
* src/acl.erl: New ACL: shared_group (thanks to Maxim Ryazanov)
* doc/guide.tex: Likewise
* src/mod_shared_roster.erl: Push new group members when
registered or manually added to group: EJAB-730 EJAB-731 EJAB-732
EJAB-767 EJAB-794. When user is added to group, push it to other
members, and other members to it. When user is removed from group,
push deletion to other members, and other members to it. When user
is registered, push him to members of group @all@. When user is
deleted, push deletion to members of group @all@. Document several
functions in mod_shared_roster.
* src/ejabberd_auth.erl: Rename hook user_registered to
register_user, for name consistency with the widely used hook
remove_user. Run hook register_user in ejabberd_auth, so it's run
when account is created with any method. Run hook remove_user in
ejabberd_auth, so it's run when account is deleted with any
method.
* src/ejabberd_auth_internal.erl: Likewise
* src/ejabberd_auth_ldap.erl: Likewise
* src/ejabberd_auth_odbc.erl: Likewise
* src/ejabberd_auth_pam.erl: Likewise
* src/mod_register.erl: Likewise
* src/jlib.erl: Implementation of XEP-0059 Result Set
Management (thanks to Eric Cestari)(EJAB-807)
* src/jlib.hrl: Likewise
* src/mod_muc/mod_muc.erl: Likewise
2008-12-23 Christophe Romain <christophe.romain@process-one.net>
* src/mod_pubsub/mod_pubsub.erl: Improve handling of PEP sent to
external contacts (EJAB-825)
* src/mod_caps.erl: Likewise
2008-12-23 Badlop <badlop@process-one.net>
* src/mod_last.erl: Implement workaround for uptime statistic in
32 bit machines, so it can show uptime greater than 50
days (EJAB-610)
* src/mod_last_odbc.erl: Likewise
* src/ejabberd_config.erl: Store start time in local_config table
* src/cyrsasl_digest.erl: Check digest-uri in SASL digest
authentication (thanks to Paul Guyot)(EJAB-569)
* src/odbc/odbc_queries.erl: Fix removal of private_storage of an
account when the account is removed
* src/mod_privacy.erl: Remove privacy lists of an account when the
account is removed (EJAB-720)
* src/mod_privacy_odbc.erl: Likewise
2008-12-19 Christophe Romain <christophe.romain@process-one.net>
* src/mod_pubsub/mod_pubsub.erl: Fix send_last_published_item issue

View File

@ -1239,6 +1239,14 @@ declarations of ACLs in the configuration file have the following syntax:
\begin{verbatim}
{acl, mucklres, {resource, "muckl"}}.
\end{verbatim}
\titem{\{shared\_group, <groupname>\}} Matches any member of a Shared Roster Group with name \term{<groupname>} in the virtual host. Example:
\begin{verbatim}
{acl, techgroupmembers, {shared_group, "techteam"}}.
\end{verbatim}
\titem{\{shared\_group, <groupname>, <server>\}} Matches any member of a Shared Roster Group with name \term{<groupname>} in the virtual host \term{<server>}. Example:
\begin{verbatim}
{acl, techgroupmembers, {shared_group, "techteam", "example.org"}}.
\end{verbatim}
\titem{\{user\_regexp, <regexp>\}} Matches any local user with a name that
matches \term{<regexp>} on local virtual hosts. Example:
\begin{verbatim}

View File

@ -180,6 +180,10 @@ match_acl(ACL, JID, Host) ->
((Host == global) andalso
lists:member(Server, ?MYHOSTS)))
andalso is_regexp_match(User, UR);
{shared_group, G} ->
mod_shared_roster:is_user_in_group({User, Server}, G, Host);
{shared_group, G, H} ->
mod_shared_roster:is_user_in_group({User, Server}, G, H);
{user_regexp, UR, S} ->
(S == Server) andalso
is_regexp_match(User, UR);

View File

@ -18,7 +18,8 @@
-behaviour(cyrsasl).
-record(state, {step, nonce, username, authzid, get_password, auth_module}).
-record(state, {step, nonce, username, authzid, get_password, auth_module,
host}).
start(_Opts) ->
cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE, true).
@ -26,9 +27,10 @@ start(_Opts) ->
stop() ->
ok.
mech_new(_Host, GetPassword, _CheckPassword) ->
mech_new(Host, GetPassword, _CheckPassword) ->
{ok, #state{step = 1,
nonce = randoms:get_string(),
host = Host,
get_password = GetPassword}}.
mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
@ -41,7 +43,14 @@ mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
bad ->
{error, 'bad-protocol'};
KeyVals ->
DigestURI = xml:get_attr_s("digest-uri", KeyVals),
UserName = xml:get_attr_s("username", KeyVals),
case is_digesturi_valid(DigestURI, State#state.host) of
false ->
?DEBUG("User login not authorized because digest-uri "
"seems invalid: ~p", [DigestURI]),
{error, 'not-authorized', UserName};
true ->
AuthzId = xml:get_attr_s("authzid", KeyVals),
case (State#state.get_password)(UserName) of
{false, _} ->
@ -64,6 +73,7 @@ mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
{error, 'not-authorized', UserName}
end
end
end
end;
mech_step(#state{step = 5,
auth_module = AuthModule,
@ -75,7 +85,6 @@ mech_step(A, B) ->
?DEBUG("SASL DIGEST: A ~p B ~p", [A,B]),
{error, 'bad-protocol'}.
parse(S) ->
parse1(S, "", []).
@ -118,6 +127,23 @@ parse4([], Key, Val, Ts) ->
parse1([], "", [{Key, lists:reverse(Val)} | Ts]).
%% @doc Check if the digest-uri is valid.
%% RFC-2831 allows to provide the IP address in Host,
%% however ejabberd doesn't allow that.
%% If the service (for example jabber.example.org)
%% is provided by several hosts (being one of them server3.example.org),
%% then digest-uri can be like xmpp/server3.example.org/jabber.example.org
%% In that case, ejabberd only checks the service name, not the host.
is_digesturi_valid(DigestURICase, JabberHost) ->
DigestURI = stringprep:tolower(DigestURICase),
case catch string:tokens(DigestURI, "/") of
["xmpp", Host] when Host == JabberHost ->
true;
["xmpp", _Host, ServName] when ServName == JabberHost ->
true;
_ ->
false
end.

View File

@ -139,6 +139,7 @@ set_password(User, Server, Password) ->
Res
end, {error, not_allowed}, auth_modules(Server)).
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, not_allowed}
try_register(_User, _Server, "") ->
%% We do not allow empty password
{error, not_allowed};
@ -149,12 +150,19 @@ try_register(User, Server, Password) ->
false ->
case lists:member(exmpp_stringprep:nameprep(Server), ?MYHOSTS) of
true ->
lists:foldl(
Res = lists:foldl(
fun(_M, {atomic, ok} = Res) ->
Res;
(M, _) ->
M:try_register(User, Server, Password)
end, {error, not_allowed}, auth_modules(Server));
end, {error, not_allowed}, auth_modules(Server)),
case Res of
{atomic, ok} ->
ejabberd_hooks:run(register_user, Server,
[User, Server]),
{atomic, ok};
_ -> Res
end;
false ->
{error, not_allowed}
end
@ -251,17 +259,37 @@ is_user_exists_in_other_modules(Module, User, Server) ->
M:is_user_exists(User, Server)
end, auth_modules(Server)--[Module]).
%% @spec (User, Server) -> ok | error | {error, not_allowed}
%% Remove user.
%% Note: it may return ok even if there was some problem removing the user.
remove_user(User, Server) ->
lists:foreach(
R = lists:foreach(
fun(M) ->
M:remove_user(User, Server)
end, auth_modules(Server)).
end, auth_modules(Server)),
case R of
ok -> ejabberd_hooks:run(remove_user, jlib:nameprep(Server), [User, Server]);
_ -> none
end,
R.
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request | error
%% Try to remove user if the provided password is correct.
%% The removal is attempted in each auth method provided:
%% when one returns 'ok' the loop stops;
%% if no method returns 'ok' then it returns the error message indicated by the last method attempted.
remove_user(User, Server, Password) ->
lists:foreach(
fun(M) ->
R = lists:foldl(
fun(_M, ok = Res) ->
Res;
(M, _) ->
M:remove_user(User, Server, Password)
end, auth_modules(Server)).
end, error, auth_modules(Server)),
case R of
ok -> ejabberd_hooks:run(remove_user, jlib:nameprep(Server), [User, Server]);
_ -> none
end,
R.
%%%----------------------------------------------------------------------

View File

@ -112,6 +112,7 @@ set_password(User, Server, Password) ->
ok
end.
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} | {aborted, Reason}
try_register(User, Server, Password) ->
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
@ -252,6 +253,9 @@ is_user_exists(User, Server) ->
false
end.
%% @spec (User, Server) -> ok
%% Remove user.
%% Note: it returns ok even if there was some problem removing the user.
remove_user(User, Server) ->
try
LUser = exmpp_stringprep:nodeprep(User),
@ -261,14 +265,14 @@ remove_user(User, Server) ->
mnesia:delete({passwd, US})
end,
mnesia:transaction(F),
ejabberd_hooks:run(remove_user,
list_to_binary(LServer),
[list_to_binary(User), list_to_binary(Server)])
ok
catch
_ ->
ok
end.
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request
%% Remove user if the provided password is correct.
remove_user(User, Server, Password) ->
try
LUser = exmpp_stringprep:nodeprep(User),
@ -287,9 +291,6 @@ remove_user(User, Server, Password) ->
end,
case mnesia:transaction(F) of
{atomic, ok} ->
ejabberd_hooks:run(remove_user,
list_to_binary(LServer),
[list_to_binary(User), list_to_binary(Server)]),
ok;
{atomic, Res} ->
Res;

View File

@ -153,6 +153,7 @@ check_password(User, Server, Password, _StreamID, _Digest) ->
set_password(_User, _Server, _Password) ->
{error, not_allowed}.
%% @spec (User, Server, Password) -> {error, not_allowed}
try_register(_User, _Server, _Password) ->
{error, not_allowed}.

View File

@ -117,6 +117,7 @@ set_password(User, Server, Password) ->
end.
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid}
try_register(User, Server, Password) ->
try
LUser = exmpp_stringprep:nodeprep(User),
@ -225,19 +226,23 @@ is_user_exists(User, Server) ->
false
end.
%% @spec (User, Server) -> ok | error
%% Remove user.
%% Note: it may return ok even if there was some problem removing the user.
remove_user(User, Server) ->
try
LUser = exmpp_stringprep:nodeprep(User),
Username = ejabberd_odbc:escape(LUser),
LServer = exmpp_stringprep:nameprep(Server),
catch odbc_queries:del_user(LServer, Username),
ejabberd_hooks:run(remove_user, list_to_binary(LServer),
[list_to_binary(User), list_to_binary(Server)])
ok
catch
_ ->
error
end.
%% @spec (User, Server, Password) -> ok | error | not_exists | not_allowed
%% Remove user if the provided password is correct.
remove_user(User, Server, Password) ->
try
LUser = exmpp_stringprep:nodeprep(User),
@ -249,8 +254,6 @@ remove_user(User, Server, Password) ->
LServer, Username, Pass),
case Result of
{selected, ["password"], [{Password}]} ->
ejabberd_hooks:run(remove_user, list_to_binary(LServer),
[list_to_binary(User), list_to_binary(Server)]),
ok;
{selected, ["password"], []} ->
not_exists;

View File

@ -91,7 +91,7 @@ remove_user(_User, _Server) ->
{error, not_allowed}.
remove_user(_User, _Server, _Password) ->
{error, not_allowed}.
not_allowed.
plain_password_required() ->
true.

View File

@ -55,7 +55,10 @@ start() ->
{attributes, record_info(fields, local_config)}]),
mnesia:add_table_copy(local_config, node(), ram_copies),
Config = get_ejabberd_config_path(),
load_file(Config).
load_file(Config),
%% This start time is used by mod_last:
add_local_option(node_start, now()),
ok.
%% @doc Get the filename of the ejabberd configuration file.
%% The filename can be specified with: erl -config "/path/to/ejabberd.cfg".
@ -76,7 +79,7 @@ get_ejabberd_config_path() ->
%% @doc Load the ejabberd configuration file.
%% It also includes additional configuration files and replaces macros.
%% @spec (File::string()) -> [term()]
%% @spec (File::string()) -> ok
load_file(File) ->
Terms = get_plain_terms_file(File),
State = lists:foldl(fun search_hosts/2, #state{}, Terms),

View File

@ -36,6 +36,9 @@
decode_base64/1,
encode_base64/1,
ip_to_list/1,
rsm_encode/1,
rsm_encode/2,
rsm_decode/1,
from_old_jid/1,
short_jid/1,
short_bare_jid/1,
@ -44,6 +47,8 @@
-include_lib("exmpp/include/exmpp.hrl").
-include("jlib.hrl").
parse_xdata_submit(#xmlel{attrs = Attrs, children = Els}) ->
case exmpp_xml:get_attribute_from_list(Attrs, 'type', "") of
@ -75,6 +80,70 @@ parse_xdata_values([#xmlel{name = 'value', children = SubEls} | Els], Res) ->
parse_xdata_values([_ | Els], Res) ->
parse_xdata_values(Els, Res).
rsm_decode(#iq{payload=SubEl})->
rsm_decode(SubEl);
rsm_decode(#xmlel{}=SubEl)->
case exmpp_xml:get_element(SubEl, 'set') of
undefined ->
none;
#xmlelement{name = 'set', children = SubEls}->
lists:foldl(fun rsm_parse_element/2, #rsm_in{}, SubEls)
end.
rsm_parse_element(#xmlel{name = 'max'}=Elem, RsmIn)->
CountStr = exmpp_xml:get_cdata_as_list(Elem),
{Count, _} = string:to_integer(CountStr),
RsmIn#rsm_in{max=Count};
rsm_parse_element(#xmlel{name = 'before'}=Elem, RsmIn)->
UID = exmpp_xml:get_cdata_as_list(Elem),
RsmIn#rsm_in{direction=before, id=UID};
rsm_parse_element(#xmlel{name = 'after'}=Elem, RsmIn)->
UID = exmpp_xml:get_cdata_as_list(Elem),
RsmIn#rsm_in{direction=aft, id=UID};
rsm_parse_element(#xmlel{name = 'index'}=Elem, RsmIn)->
IndexStr = exmpp_xml:get_cdata_as_list(Elem),
{Index, _} = string:to_integer(IndexStr),
RsmIn#rsm_in{index=Index};
rsm_parse_element(_, RsmIn)->
RsmIn.
rsm_encode(#iq{payload=SubEl}=IQ_Rec,RsmOut)->
Set = #xmlel{ns = ?NS_RSM, name = 'set', children =
lists:reverse(rsm_encode_out(RsmOut))},
New = exmpp_xml:prepend_child(SubEl, Set),
IQ_Rec#iq{payload=New}.
rsm_encode(none)->
[];
rsm_encode(RsmOut)->
[#xmlel{ns = ?NS_RSM, name = 'set', children = lists:reverse(rsm_encode_out(RsmOut))}].
rsm_encode_out(#rsm_out{count=Count, index=Index, first=First, last=Last})->
El = rsm_encode_first(First, Index, []),
El2 = rsm_encode_last(Last,El),
rsm_encode_count(Count, El2).
rsm_encode_first(undefined, undefined, Arr) ->
Arr;
rsm_encode_first(First, undefined, Arr) ->
[#xmlel{ns = ?NS_RSM, name = 'first', children = [#xmlcdata{cdata = list_to_binary(First)}]}|Arr];
rsm_encode_first(First, Index, Arr) ->
[#xmlel{ns = ?NS_RSM, name = 'first', attrs = [#xmlattr{name = 'index', value = i2b(Index)}], children = [#xmlcdata{cdata = list_to_binary(First)}]}|Arr].
rsm_encode_last(undefined, Arr) -> Arr;
rsm_encode_last(Last, Arr) ->
[#xmlel{ns = ?NS_RSM, name = 'last', children = [#xmlcdata{cdata = list_to_binary(Last)}]}|Arr].
rsm_encode_count(undefined, Arr)-> Arr;
rsm_encode_count(Count, Arr)->
[#xmlel{ns = ?NS_RSM, name = 'count', children = [#xmlcdata{cdata = i2b(Count)}]} | Arr].
i2b(I) when is_integer(I) -> list_to_binary(integer_to_list(I));
i2b(L) when is_list(L) -> list_to_binary(L).
timestamp_to_iso({{Year, Month, Day}, {Hour, Minute, Second}}) ->
lists:flatten(

View File

@ -19,290 +19,5 @@
%%%
%%%----------------------------------------------------------------------
-define(NS_DISCO_ITEMS, "http://jabber.org/protocol/disco#items").
-define(NS_DISCO_INFO, "http://jabber.org/protocol/disco#info").
-define(NS_VCARD, "vcard-temp").
-define(NS_AUTH, "jabber:iq:auth").
-define(NS_AUTH_ERROR, "jabber:iq:auth:error").
-define(NS_REGISTER, "jabber:iq:register").
-define(NS_SEARCH, "jabber:iq:search").
-define(NS_ROSTER, "jabber:iq:roster").
-define(NS_PRIVACY, "jabber:iq:privacy").
-define(NS_PRIVATE, "jabber:iq:private").
-define(NS_VERSION, "jabber:iq:version").
-define(NS_TIME, "jabber:iq:time").
-define(NS_LAST, "jabber:iq:last").
-define(NS_XDATA, "jabber:x:data").
-define(NS_IQDATA, "jabber:iq:data").
-define(NS_DELAY, "jabber:x:delay").
-define(NS_EXPIRE, "jabber:x:expire").
-define(NS_EVENT, "jabber:x:event").
-define(NS_XCONFERENCE, "jabber:x:conference").
-define(NS_STATS, "http://jabber.org/protocol/stats").
-define(NS_MUC, "http://jabber.org/protocol/muc").
-define(NS_MUC_USER, "http://jabber.org/protocol/muc#user").
-define(NS_MUC_ADMIN, "http://jabber.org/protocol/muc#admin").
-define(NS_MUC_OWNER, "http://jabber.org/protocol/muc#owner").
-define(NS_PUBSUB, "http://jabber.org/protocol/pubsub").
-define(NS_PUBSUB_EVENT, "http://jabber.org/protocol/pubsub#event").
-define(NS_PUBSUB_OWNER, "http://jabber.org/protocol/pubsub#owner").
-define(NS_PUBSUB_NMI, "http://jabber.org/protocol/pubsub#node-meta-info").
-define(NS_PUBSUB_ERRORS,"http://jabber.org/protocol/pubsub#errors").
-define(NS_PUBSUB_NODE_CONFIG, "http://jabber.org/protocol/pubsub#node_config").
-define(NS_PUBSUB_SUB_AUTH, "http://jabber.org/protocol/pubsub#subscribe_authorization").
-define(NS_COMMANDS, "http://jabber.org/protocol/commands").
-define(NS_BYTESTREAMS, "http://jabber.org/protocol/bytestreams").
-define(NS_ADMIN, "http://jabber.org/protocol/admin").
-define(NS_EJABBERD_CONFIG, "ejabberd:config").
-define(NS_STREAM, "http://etherx.jabber.org/streams").
-define(NS_STANZAS, "urn:ietf:params:xml:ns:xmpp-stanzas").
-define(NS_STREAMS, "urn:ietf:params:xml:ns:xmpp-streams").
-define(NS_TLS, "urn:ietf:params:xml:ns:xmpp-tls").
-define(NS_SASL, "urn:ietf:params:xml:ns:xmpp-sasl").
-define(NS_SESSION, "urn:ietf:params:xml:ns:xmpp-session").
-define(NS_BIND, "urn:ietf:params:xml:ns:xmpp-bind").
-define(NS_FEATURE_IQAUTH, "http://jabber.org/features/iq-auth").
-define(NS_FEATURE_IQREGISTER, "http://jabber.org/features/iq-register").
-define(NS_FEATURE_COMPRESS, "http://jabber.org/features/compress").
-define(NS_COMPRESS, "http://jabber.org/protocol/compress").
-define(NS_CAPS, "http://jabber.org/protocol/caps").
% TODO: remove "code" attribute (currently it used for backward-compatibility)
-define(STANZA_ERROR(Code, Type, Condition),
{xmlelement, "error",
[{"code", Code}, {"type", Type}],
[{xmlelement, Condition, [{"xmlns", ?NS_STANZAS}], []}]}).
-define(ERR_BAD_REQUEST,
?STANZA_ERROR("400", "modify", "bad-request")).
-define(ERR_CONFLICT,
?STANZA_ERROR("409", "cancel", "conflict")).
-define(ERR_FEATURE_NOT_IMPLEMENTED,
?STANZA_ERROR("501", "cancel", "feature-not-implemented")).
-define(ERR_FORBIDDEN,
?STANZA_ERROR("403", "auth", "forbidden")).
-define(ERR_GONE,
?STANZA_ERROR("302", "modify", "gone")).
-define(ERR_INTERNAL_SERVER_ERROR,
?STANZA_ERROR("500", "wait", "internal-server-error")).
-define(ERR_ITEM_NOT_FOUND,
?STANZA_ERROR("404", "cancel", "item-not-found")).
-define(ERR_JID_MALFORMED,
?STANZA_ERROR("400", "modify", "jid-malformed")).
-define(ERR_NOT_ACCEPTABLE,
?STANZA_ERROR("406", "modify", "not-acceptable")).
-define(ERR_NOT_ALLOWED,
?STANZA_ERROR("405", "cancel", "not-allowed")).
-define(ERR_NOT_AUTHORIZED,
?STANZA_ERROR("401", "auth", "not-authorized")).
-define(ERR_PAYMENT_REQUIRED,
?STANZA_ERROR("402", "auth", "payment-required")).
-define(ERR_RECIPIENT_UNAVAILABLE,
?STANZA_ERROR("404", "wait", "recipient-unavailable")).
-define(ERR_REDIRECT,
?STANZA_ERROR("302", "modify", "redirect")).
-define(ERR_REGISTRATION_REQUIRED,
?STANZA_ERROR("407", "auth", "registration-required")).
-define(ERR_REMOTE_SERVER_NOT_FOUND,
?STANZA_ERROR("404", "cancel", "remote-server-not-found")).
-define(ERR_REMOTE_SERVER_TIMEOUT,
?STANZA_ERROR("504", "wait", "remote-server-timeout")).
-define(ERR_RESOURCE_CONSTRAINT,
?STANZA_ERROR("500", "wait", "resource-constraint")).
-define(ERR_SERVICE_UNAVAILABLE,
?STANZA_ERROR("503", "cancel", "service-unavailable")).
-define(ERR_SUBSCRIPTION_REQUIRED,
?STANZA_ERROR("407", "auth", "subscription-required")).
-define(ERR_UNEXPECTED_REQUEST,
?STANZA_ERROR("400", "wait", "unexpected-request")).
%-define(ERR_,
% ?STANZA_ERROR("", "", "")).
-define(STANZA_ERRORT(Code, Type, Condition, Lang, Text),
{xmlelement, "error",
[{"code", Code}, {"type", Type}],
[{xmlelement, Condition, [{"xmlns", ?NS_STANZAS}], []},
{xmlelement, "text", [{"xmlns", ?NS_STANZAS}],
[{xmlcdata, translate:translate(Lang, Text)}]}]}).
-define(ERRT_BAD_REQUEST(Lang, Text),
?STANZA_ERRORT("400", "modify", "bad-request", Lang, Text)).
-define(ERRT_CONFLICT(Lang, Text),
?STANZA_ERRORT("409", "cancel", "conflict", Lang, Text)).
-define(ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Text),
?STANZA_ERRORT("501", "cancel", "feature-not-implemented", Lang, Text)).
-define(ERRT_FORBIDDEN(Lang, Text),
?STANZA_ERRORT("403", "auth", "forbidden", Lang, Text)).
-define(ERRT_GONE(Lang, Text),
?STANZA_ERRORT("302", "modify", "gone", Lang, Text)).
-define(ERRT_INTERNAL_SERVER_ERROR(Lang, Text),
?STANZA_ERRORT("500", "wait", "internal-server-error", Lang, Text)).
-define(ERRT_ITEM_NOT_FOUND(Lang, Text),
?STANZA_ERRORT("404", "cancel", "item-not-found", Lang, Text)).
-define(ERRT_JID_MALFORMED(Lang, Text),
?STANZA_ERRORT("400", "modify", "jid-malformed", Lang, Text)).
-define(ERRT_NOT_ACCEPTABLE(Lang, Text),
?STANZA_ERRORT("406", "modify", "not-acceptable", Lang, Text)).
-define(ERRT_NOT_ALLOWED(Lang, Text),
?STANZA_ERRORT("405", "cancel", "not-allowed", Lang, Text)).
-define(ERRT_NOT_AUTHORIZED(Lang, Text),
?STANZA_ERRORT("401", "auth", "not-authorized", Lang, Text)).
-define(ERRT_PAYMENT_REQUIRED(Lang, Text),
?STANZA_ERRORT("402", "auth", "payment-required", Lang, Text)).
-define(ERRT_RECIPIENT_UNAVAILABLE(Lang, Text),
?STANZA_ERRORT("404", "wait", "recipient-unavailable", Lang, Text)).
-define(ERRT_REDIRECT(Lang, Text),
?STANZA_ERRORT("302", "modify", "redirect", Lang, Text)).
-define(ERRT_REGISTRATION_REQUIRED(Lang, Text),
?STANZA_ERRORT("407", "auth", "registration-required", Lang, Text)).
-define(ERRT_REMOTE_SERVER_NOT_FOUND(Lang, Text),
?STANZA_ERRORT("404", "cancel", "remote-server-not-found", Lang, Text)).
-define(ERRT_REMOTE_SERVER_TIMEOUT(Lang, Text),
?STANZA_ERRORT("504", "wait", "remote-server-timeout", Lang, Text)).
-define(ERRT_RESOURCE_CONSTRAINT(Lang, Text),
?STANZA_ERRORT("500", "wait", "resource-constraint", Lang, Text)).
-define(ERRT_SERVICE_UNAVAILABLE(Lang, Text),
?STANZA_ERRORT("503", "cancel", "service-unavailable", Lang, Text)).
-define(ERRT_SUBSCRIPTION_REQUIRED(Lang, Text),
?STANZA_ERRORT("407", "auth", "subscription-required", Lang, Text)).
-define(ERRT_UNEXPECTED_REQUEST(Lang, Text),
?STANZA_ERRORT("400", "wait", "unexpected-request", Lang, Text)).
% Auth stanza errors
-define(ERR_AUTH_NO_RESOURCE_PROVIDED(Lang),
?ERRT_NOT_ACCEPTABLE(Lang, "No resource provided")).
-define(ERR_AUTH_BAD_RESOURCE_FORMAT(Lang),
?ERRT_NOT_ACCEPTABLE(Lang, "Illegal resource format")).
-define(ERR_AUTH_RESOURCE_CONFLICT(Lang),
?ERRT_CONFLICT(Lang, "Resource conflict")).
-define(STREAM_ERROR(Condition),
{xmlelement, "stream:error",
[],
[{xmlelement, Condition, [{"xmlns", ?NS_STREAMS}], []}]}).
-define(SERR_BAD_FORMAT,
?STREAM_ERROR("bad-format")).
-define(SERR_BAD_NAMESPACE_PREFIX,
?STREAM_ERROR("bad-namespace-prefix")).
-define(SERR_CONFLICT,
?STREAM_ERROR("conflict")).
-define(SERR_CONNECTION_TIMEOUT,
?STREAM_ERROR("connection-timeout")).
-define(SERR_HOST_GONE,
?STREAM_ERROR("host-gone")).
-define(SERR_HOST_UNKNOWN,
?STREAM_ERROR("host-unknown")).
-define(SERR_IMPROPER_ADDRESSING,
?STREAM_ERROR("improper-addressing")).
-define(SERR_INTERNAL_SERVER_ERROR,
?STREAM_ERROR("internal-server-error")).
-define(SERR_INVALID_FROM,
?STREAM_ERROR("invalid-from")).
-define(SERR_INVALID_ID,
?STREAM_ERROR("invalid-id")).
-define(SERR_INVALID_NAMESPACE,
?STREAM_ERROR("invalid-namespace")).
-define(SERR_INVALID_XML,
?STREAM_ERROR("invalid-xml")).
-define(SERR_NOT_AUTHORIZED,
?STREAM_ERROR("not-authorized")).
-define(SERR_POLICY_VIOLATION,
?STREAM_ERROR("policy-violation")).
-define(SERR_REMOTE_CONNECTION_FAILED,
?STREAM_ERROR("remote-connection-failed")).
-define(SERR_RESOURSE_CONSTRAINT,
?STREAM_ERROR("resource-constraint")).
-define(SERR_RESTRICTED_XML,
?STREAM_ERROR("restricted-xml")).
% TODO: include hostname or IP
-define(SERR_SEE_OTHER_HOST,
?STREAM_ERROR("see-other-host")).
-define(SERR_SYSTEM_SHUTDOWN,
?STREAM_ERROR("system-shutdown")).
-define(SERR_UNSUPPORTED_ENCODING,
?STREAM_ERROR("unsupported-encoding")).
-define(SERR_UNSUPPORTED_STANZA_TYPE,
?STREAM_ERROR("unsupported-stanza-type")).
-define(SERR_UNSUPPORTED_VERSION,
?STREAM_ERROR("unsupported-version")).
-define(SERR_XML_NOT_WELL_FORMED,
?STREAM_ERROR("xml-not-well-formed")).
%-define(SERR_,
% ?STREAM_ERROR("")).
-define(STREAM_ERRORT(Condition, Lang, Text),
{xmlelement, "stream:error",
[],
[{xmlelement, Condition, [{"xmlns", ?NS_STREAMS}], []},
{xmlelement, "text", [{"xml:lang", Lang}, {"xmlns", ?NS_STREAMS}],
[{xmlcdata, translate:translate(Lang, Text)}]}]}).
-define(SERRT_BAD_FORMAT(Lang, Text),
?STREAM_ERRORT("bad-format", Lang, Text)).
-define(SERRT_BAD_NAMESPACE_PREFIX(Lang, Text),
?STREAM_ERRORT("bad-namespace-prefix", Lang, Text)).
-define(SERRT_CONFLICT(Lang, Text),
?STREAM_ERRORT("conflict", Lang, Text)).
-define(SERRT_CONNECTION_TIMEOUT(Lang, Text),
?STREAM_ERRORT("connection-timeout", Lang, Text)).
-define(SERRT_HOST_GONE(Lang, Text),
?STREAM_ERRORT("host-gone", Lang, Text)).
-define(SERRT_HOST_UNKNOWN(Lang, Text),
?STREAM_ERRORT("host-unknown", Lang, Text)).
-define(SERRT_IMPROPER_ADDRESSING(Lang, Text),
?STREAM_ERRORT("improper-addressing", Lang, Text)).
-define(SERRT_INTERNAL_SERVER_ERROR(Lang, Text),
?STREAM_ERRORT("internal-server-error", Lang, Text)).
-define(SERRT_INVALID_FROM(Lang, Text),
?STREAM_ERRORT("invalid-from", Lang, Text)).
-define(SERRT_INVALID_ID(Lang, Text),
?STREAM_ERRORT("invalid-id", Lang, Text)).
-define(SERRT_INVALID_NAMESPACE(Lang, Text),
?STREAM_ERRORT("invalid-namespace", Lang, Text)).
-define(SERRT_INVALID_XML(Lang, Text),
?STREAM_ERRORT("invalid-xml", Lang, Text)).
-define(SERRT_NOT_AUTHORIZED(Lang, Text),
?STREAM_ERRORT("not-authorized", Lang, Text)).
-define(SERRT_POLICY_VIOLATION(Lang, Text),
?STREAM_ERRORT("policy-violation", Lang, Text)).
-define(SERRT_REMOTE_CONNECTION_FAILED(Lang, Text),
?STREAM_ERRORT("remote-connection-failed", Lang, Text)).
-define(SERRT_RESOURSE_CONSTRAINT(Lang, Text),
?STREAM_ERRORT("resource-constraint", Lang, Text)).
-define(SERRT_RESTRICTED_XML(Lang, Text),
?STREAM_ERRORT("restricted-xml", Lang, Text)).
% TODO: include hostname or IP
-define(SERRT_SEE_OTHER_HOST(Lang, Text),
?STREAM_ERRORT("see-other-host", Lang, Text)).
-define(SERRT_SYSTEM_SHUTDOWN(Lang, Text),
?STREAM_ERRORT("system-shutdown", Lang, Text)).
-define(SERRT_UNSUPPORTED_ENCODING(Lang, Text),
?STREAM_ERRORT("unsupported-encoding", Lang, Text)).
-define(SERRT_UNSUPPORTED_STANZA_TYPE(Lang, Text),
?STREAM_ERRORT("unsupported-stanza-type", Lang, Text)).
-define(SERRT_UNSUPPORTED_VERSION(Lang, Text),
?STREAM_ERRORT("unsupported-version", Lang, Text)).
-define(SERRT_XML_NOT_WELL_FORMED(Lang, Text),
?STREAM_ERRORT("xml-not-well-formed", Lang, Text)).
%-define(SERRT_(Lang, Text),
% ?STREAM_ERRORT("", Lang, Text)).
-record(jid, {user, server, resource,
luser, lserver, lresource}).
-record(iq, {id = "",
type,
xmlns = "",
lang = "",
sub_el}).
-record(rsm_in, {max, direction, id, index}).
-record(rsm_out, {count, index, first, last}).

View File

@ -71,14 +71,36 @@ stop(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_local, HostB, ?NS_LAST_ACTIVITY),
gen_iq_handler:remove_iq_handler(ejabberd_sm, HostB, ?NS_LAST_ACTIVITY).
%%%
%%% Uptime of ejabberd node
%%%
process_local_iq(_From, _To, #iq{type = get} = IQ_Rec) ->
Sec = trunc(element(1, erlang:statistics(wall_clock))/1000),
Sec = get_node_uptime(),
Response = #xmlel{ns = ?NS_LAST_ACTIVITY, name = 'query', attrs =
[#xmlattr{name = 'seconds', value = list_to_binary(integer_to_list(Sec))}]},
exmpp_iq:result(IQ_Rec, Response);
process_local_iq(_From, _To, #iq{type = set} = IQ_Rec) ->
exmpp_iq:error(IQ_Rec, 'not-allowed').
%% @spec () -> integer()
%% @doc Get the uptime of the ejabberd node, expressed in seconds.
%% When ejabberd is starting, ejabberd_config:start/0 stores the datetime.
get_node_uptime() ->
case ejabberd_config:get_local_option(node_start) of
{_, _, _} = StartNow ->
now_to_seconds(now()) - now_to_seconds(StartNow);
_undefined ->
trunc(element(1, erlang:statistics(wall_clock))/1000)
end.
now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
MegaSecs * 1000000 + Secs.
%%%
%%% Serve queries about user last online
%%%
process_sm_iq(From, To, #iq{type = get} = IQ_Rec) ->
{Subscription, _Groups} =
@ -117,8 +139,7 @@ get_last(IQ_Rec, LUser, LServer) ->
[] ->
exmpp_iq:error(IQ_Rec, 'service-unavailable');
[#last_activity{timestamp = TimeStamp, status = Status}] ->
{MegaSecs, Secs, _MicroSecs} = now(),
TimeStamp2 = MegaSecs * 1000000 + Secs,
TimeStamp2 = now_to_seconds(now()),
Sec = TimeStamp2 - TimeStamp,
Response = #xmlel{ns = ?NS_LAST_ACTIVITY, name = 'query',
attrs = [#xmlattr{name = 'seconds', value = list_to_binary(integer_to_list(Sec))}],
@ -129,8 +150,7 @@ get_last(IQ_Rec, LUser, LServer) ->
on_presence_update(User, Server, _Resource, Status) ->
{MegaSecs, Secs, _MicroSecs} = now(),
TimeStamp = MegaSecs * 1000000 + Secs,
TimeStamp = now_to_seconds(now()),
store_last_info(User, Server, TimeStamp, Status).
store_last_info(User, Server, TimeStamp, Status)
@ -148,7 +168,8 @@ store_last_info(User, Server, TimeStamp, Status)
ok
end.
%% Returns: {ok, Timestamp, Status} | not_found
%% @spec (LUser::string(), LServer::string() ->
%% {ok, Timestamp::integer(), Status::string()} | not_found
get_last_info(LUser, LServer) when is_binary(LUser), is_binary(LServer) ->
case catch mnesia:dirty_read(last_activity, {LUser, LServer}) of
{'EXIT', _Reason} ->

View File

@ -64,14 +64,36 @@ stop(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_local, HostB, ?NS_LAST_ACTIVITY),
gen_iq_handler:remove_iq_handler(ejabberd_sm, HostB, ?NS_LAST_ACTIVITY).
%%%
%%% Uptime of ejabberd node
%%%
process_local_iq(_From, _To, #iq{type = get} = IQ_Rec) ->
Sec = trunc(element(1, erlang:statistics(wall_clock))/1000),
Sec = get_node_uptime(),
Response = #xmlel{ns = ?NS_LAST_ACTIVITY, name = 'query', attrs =
[#xmlattr{name = 'seconds', value = list_to_binary(integer_to_list(Sec))}]},
exmpp_iq:result(IQ_Rec, Response);
process_local_iq(_From, _To, #iq{type = set} = IQ_Rec) ->
exmpp_iq:error(IQ_Rec, 'not-allowed').
%% @spec () -> integer()
%% @doc Get the uptime of the ejabberd node, expressed in seconds.
%% When ejabberd is starting, ejabberd_config:start/0 stores the datetime.
get_node_uptime() ->
case ejabberd_config:get_local_option(node_start) of
{_, _, _} = StartNow ->
now_to_seconds(now()) - now_to_seconds(StartNow);
_undefined ->
trunc(element(1, erlang:statistics(wall_clock))/1000)
end.
now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
MegaSecs * 1000000 + Secs.
%%%
%%% Serve queries about user last online
%%%
process_sm_iq(From, To, #iq{type = get} = IQ_Rec) ->
User = exmpp_jid:lnode_as_list(To),
Server = exmpp_jid:ldomain_as_list(To),
@ -112,8 +134,7 @@ get_last(IQ_Rec, LUser, LServer) ->
{selected, ["seconds","state"], [{STimeStamp, Status}]} ->
case catch list_to_integer(STimeStamp) of
TimeStamp when is_integer(TimeStamp) ->
{MegaSecs, Secs, _MicroSecs} = now(),
TimeStamp2 = MegaSecs * 1000000 + Secs,
TimeStamp2 = now_to_seconds(now()),
Sec = TimeStamp2 - TimeStamp,
Response = #xmlel{ns = ?NS_LAST_ACTIVITY, name = 'query',
attrs = [#xmlattr{name = 'seconds', value = list_to_binary(integer_to_list(Sec))}],
@ -127,8 +148,7 @@ get_last(IQ_Rec, LUser, LServer) ->
end.
on_presence_update(User, Server, _Resource, Status) ->
{MegaSecs, Secs, _MicroSecs} = now(),
TimeStamp = MegaSecs * 1000000 + Secs,
TimeStamp = now_to_seconds(now()),
store_last_info(User, Server, TimeStamp, Status).
store_last_info(User, Server, TimeStamp, Status)
@ -148,7 +168,8 @@ store_last_info(User, Server, TimeStamp, Status)
ok
end.
%% Returns: {ok, Timestamp, Status} | not_found
%% @spec (LUser::string(), LServer::string() ->
%% {ok, Timestamp::integer(), Status::string()} | not_found
get_last_info(LUser, LServer) ->
Username = ejabberd_odbc:escape(LUser),
case catch odbc_queries:get_last(LServer, Username) of

View File

@ -48,6 +48,7 @@
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(muc_room, {name_host, opts}).
@ -125,11 +126,11 @@ forget_room(Host, Name) when is_binary(Host), is_binary(Name) ->
end,
mnesia:transaction(F).
process_iq_disco_items(Host, From, To, #iq{} = IQ) when is_binary(Host) ->
Lang = exmpp_stanza:get_lang(IQ),
process_iq_disco_items(Host, From, To, #iq{lang = Lang} = IQ) when is_binary(Host) ->
Rsm = jlib:rsm_decode(IQ),
Res = exmpp_iq:result(IQ, #xmlel{ns = ?NS_DISCO_ITEMS,
name = 'query',
children = iq_disco_items(Host, From, Lang)}),
children = iq_disco_items(Host, From, Lang, Rsm)}),
ejabberd_router:route(To,
From,
exmpp_iq:iq_to_xmlel(Res)).
@ -513,13 +514,15 @@ iq_disco_info(Lang) ->
#xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs =
[#xmlattr{name = 'var',
value = ?NS_INBAND_REGISTER_s}]},
#xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs =
[#xmlattr{name = 'var',
value = ?NS_RSM_s}]},
#xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs =
[#xmlattr{name = 'var',
value = ?NS_VCARD_s}]}].
iq_disco_items(Host, From, Lang) when is_binary(Host) ->
iq_disco_items(Host, From, Lang, none) when is_binary(Host) ->
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
@ -535,7 +538,72 @@ iq_disco_items(Host, From, Lang) when is_binary(Host) ->
_ ->
false
end
end, get_vh_rooms(Host)).
end, get_vh_rooms(Host));
iq_disco_items(Host, From, Lang, Rsm) ->
{Rooms, RsmO} = get_vh_rooms(Host, Rsm),
RsmOut = jlib:rsm_encode(RsmO),
lists:zf(fun(#muc_online_room{name_host = {Name, _Host}, pid = Pid}) ->
case catch gen_fsm:sync_send_all_state_event(
Pid, {get_disco_item, From, Lang}, 100) of
{item, Desc} ->
flush(),
{true,
{xmlelement, "item",
[{"jid", jlib:jid_to_string({Name, Host, ""})},
{"name", Desc}], []}};
_ ->
false
end
end, Rooms) ++ RsmOut.
get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})->
AllRooms = lists:sort(get_vh_rooms(Host)),
Count = erlang:length(AllRooms),
Guard = case Direction of
_ when Index =/= undefined -> [{'==', {element, 2, '$1'}, Host}];
aft -> [{'==', {element, 2, '$1'}, Host}, {'>=',{element, 1, '$1'} ,I}];
before when I =/= []-> [{'==', {element, 2, '$1'}, Host}, {'=<',{element, 1, '$1'} ,I}];
_ -> [{'==', {element, 2, '$1'}, Host}]
end,
L = lists:sort(
mnesia:dirty_select(muc_online_room,
[{#muc_online_room{name_host = '$1', _ = '_'},
Guard,
['$_']}])),
L2 = if
Index == undefined andalso Direction == before ->
lists:reverse(lists:sublist(lists:reverse(L), 1, M));
Index == undefined ->
lists:sublist(L, 1, M);
Index > Count orelse Index < 0 ->
[];
true ->
lists:sublist(L, Index+1, M)
end,
if
L2 == [] ->
{L2, #rsm_out{count=Count}};
true ->
H = hd(L2),
NewIndex = get_room_pos(H, AllRooms),
T=lists:last(L2),
{F, _}=H#muc_online_room.name_host,
{Last, _}=T#muc_online_room.name_host,
{L2, #rsm_out{first=F, last=Last, count=Count, index=NewIndex}}
end.
%% @doc Return the position of desired room in the list of rooms.
%% The room must exist in the list. The count starts in 0.
%% @spec (Desired::muc_online_room(), Rooms::[muc_online_room()]) -> integer()
get_room_pos(Desired, Rooms) ->
get_room_pos(Desired, Rooms, 0).
get_room_pos(Desired, [HeadRoom | _], HeadPosition)
when (Desired#muc_online_room.name_host ==
HeadRoom#muc_online_room.name_host) ->
HeadPosition;
get_room_pos(Desired, [_ | Rooms], HeadPosition) ->
get_room_pos(Desired, Rooms, HeadPosition + 1).
flush() ->
receive

View File

@ -35,6 +35,7 @@
process_iq_get/5,
get_user_list/3,
check_packet/6,
remove_user/2,
updated_list/3]).
-include_lib("exmpp/include/exmpp.hrl").
@ -59,6 +60,8 @@ start(Host, Opts) ->
?MODULE, check_packet, 50),
ejabberd_hooks:add(privacy_updated_list, HostB,
?MODULE, updated_list, 50),
ejabberd_hooks:add(remove_user, HostB,
?MODULE, remove_user, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, HostB, ?NS_PRIVACY,
?MODULE, process_iq, IQDisc).
@ -74,6 +77,8 @@ stop(Host) ->
?MODULE, check_packet, 50),
ejabberd_hooks:delete(privacy_updated_list, HostB,
?MODULE, updated_list, 50),
ejabberd_hooks:delete(remove_user, HostB,
?MODULE, remove_user, 50),
gen_iq_handler:remove_iq_handler(ejabberd_sm, HostB, ?NS_PRIVACY).
process_iq(_From, _To, IQ_Rec) ->
@ -667,6 +672,16 @@ is_type_match(Type, Value, JID, Subscription, Groups) ->
end.
remove_user(User, Server) ->
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
F = fun() ->
mnesia:delete({privacy,
{LUser, LServer}})
end,
mnesia:transaction(F).
updated_list(_,
#userlist{name = OldName} = Old,
#userlist{name = NewName} = New) ->
@ -678,7 +693,6 @@ updated_list(_,
end.
update_table() ->
Fields = record_info(fields, privacy),
case mnesia:table_info(privacy, attributes) of

View File

@ -35,6 +35,7 @@
process_iq_get/5,
get_user_list/3,
check_packet/6,
remove_user/2,
updated_list/3]).
-include_lib("exmpp/include/exmpp.hrl").
@ -55,6 +56,8 @@ start(Host, Opts) ->
?MODULE, check_packet, 50),
ejabberd_hooks:add(privacy_updated_list, HostB,
?MODULE, updated_list, 50),
ejabberd_hooks:add(remove_user, HostB,
?MODULE, remove_user, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, HostB, ?NS_PRIVACY,
?MODULE, process_iq, IQDisc).
@ -70,6 +73,8 @@ stop(Host) ->
?MODULE, check_packet, 50),
ejabberd_hooks:delete(privacy_updated_list, HostB,
?MODULE, updated_list, 50),
ejabberd_hooks:delete(remove_user, HostB,
?MODULE, remove_user, 50),
gen_iq_handler:remove_iq_handler(ejabberd_sm, HostB, ?NS_PRIVACY).
process_iq(_From, _To, IQ_Rec) ->
@ -666,6 +671,12 @@ is_type_match(Type, Value, JID, Subscription, Groups) ->
end.
remove_user(User, Server) ->
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
sql_del_privacy_lists(LUser, LServer).
updated_list(_,
#userlist{name = OldName} = Old,
#userlist{name = NewName} = New) ->
@ -880,3 +891,16 @@ sql_set_privacy_list(ID, RItems) ->
") "
"values ('", ID, "', ", Items, ");"])
end, RItems).
sql_del_privacy_lists(LUser, LServer) ->
Username = ejabberd_odbc:escape(LUser),
Server = ejabberd_odbc:escape(LServer),
ejabberd_odbc:sql_query(
LServer,
["delete from privacy_list where username='", Username, "';"]),
ejabberd_odbc:sql_query(
LServer,
["delete from privacy_list_data where value='", Username++"@"++Server, "';"]),
ejabberd_odbc:sql_query(
LServer,
["delete from privacy_default_list where username='", Username, "';"]).

View File

@ -26,7 +26,6 @@
-author(__TO_BE_DEFINED__).
-include("pubsub.hrl").
-include("jlib.hrl").
-behaviour(gen_pubsub_node).

View File

@ -215,8 +215,6 @@ try_register(User, Server, Password, Source, Lang) ->
true ->
case ejabberd_auth:try_register(User, Server, Password) of
{atomic, ok} ->
ejabberd_hooks:run(user_registered, exmpp_jid:domain(JID),
[exmpp_jid:node(JID), exmpp_jid:domain(JID)]),
send_welcome_message(JID),
send_registration_notifications(JID),
ok;

View File

@ -37,7 +37,8 @@
process_item/2,
in_subscription/6,
out_subscription/4,
user_registered/2,
register_user/2,
remove_user/2,
list_groups/1,
create_group/2,
create_group/3,
@ -46,6 +47,7 @@
set_group_opts/3,
get_group_users/2,
get_group_explicit_users/2,
is_user_in_group/3,
add_user_to_group/3,
remove_user_from_group/3]).
@ -85,8 +87,10 @@ start(Host, _Opts) ->
?MODULE, get_jid_info, 70),
ejabberd_hooks:add(roster_process_item, HostB,
?MODULE, process_item, 50),
ejabberd_hooks:add(user_registered, HostB,
?MODULE, user_registered, 50).
ejabberd_hooks:add(register_user, HostB,
?MODULE, register_user, 50),
ejabberd_hooks:add(remove_user, HostB,
?MODULE, remove_user, 50).
%%ejabberd_hooks:add(remove_user, HostB,
%% ?MODULE, remove_user, 50),
@ -108,8 +112,10 @@ stop(Host) ->
?MODULE, get_jid_info, 70),
ejabberd_hooks:delete(roster_process_item, HostB,
?MODULE, process_item, 50),
ejabberd_hooks:delete(user_registered, HostB,
?MODULE, user_registered, 50).
ejabberd_hooks:delete(register_user, HostB,
?MODULE, register_user, 50),
ejabberd_hooks:delete(remove_user, HostB,
?MODULE, remove_user, 50).
%%ejabberd_hooks:delete(remove_user, HostB,
%% ?MODULE, remove_user, 50),
@ -443,6 +449,7 @@ get_group_users(_User, Host, Group, GroupOpts) ->
[]
end ++ get_group_explicit_users(Host, Group).
%% @spec (Host::string(), Group::string()) -> [{User::string(), Server::string()}]
get_group_explicit_users(Host, Group) ->
Read = (catch mnesia:dirty_index_read(
sr_user,
@ -458,6 +465,7 @@ get_group_explicit_users(Host, Group) ->
get_group_name(Host, Group) ->
get_group_opt(Host, Group, name, Group).
%% Get list of names of groups that have @all@ in the memberlist
get_special_users_groups(Host) ->
lists:filter(
fun(Group) ->
@ -465,6 +473,8 @@ get_special_users_groups(Host) ->
end,
list_groups(Host)).
%% Given two lists of groupnames and their options,
%% return the list of displayed groups to the second list
displayed_groups(GroupsOpts, SelectedGroupsOpts) ->
DisplayedGroups =
lists:usort(
@ -476,6 +486,9 @@ displayed_groups(GroupsOpts, SelectedGroupsOpts) ->
[G || G <- DisplayedGroups,
not lists:member(disabled, proplists:get_value(G, GroupsOpts, []))].
%% Given a list of group names with options,
%% for those that have @all@ in memberlist,
%% get the list of groups displayed
get_special_displayed_groups(GroupsOpts) ->
Groups = lists:filter(
fun({_Group, Opts}) ->
@ -483,6 +496,9 @@ get_special_displayed_groups(GroupsOpts) ->
end, GroupsOpts),
displayed_groups(GroupsOpts, Groups).
%% Given a username and server, and a list of group names with options,
%% for the list of groups of that server that user is member
%% get the list of groups displayed
get_user_displayed_groups(LUser, LServer, GroupsOpts) ->
Groups = case catch mnesia:dirty_read(sr_user, {LUser, LServer}) of
Rs when is_list(Rs) ->
@ -493,6 +509,7 @@ get_user_displayed_groups(LUser, LServer, GroupsOpts) ->
end,
displayed_groups(GroupsOpts, Groups).
%% @doc Get the list of groups that are displayed to this user
get_user_displayed_groups(US) ->
Host = element(2, US),
DisplayedGroups1 =
@ -515,22 +532,60 @@ is_user_in_group({_U, S} = US, Group, Host) ->
_ -> true
end.
%% @spec (Host::string(), {User::string(), Server::string()}, Group::string()) -> {atomic, ok}
add_user_to_group(Host, US, Group) ->
{LUser, LServer} = US,
%% Push this new user to members of groups where this group is displayed
push_user_to_displayed(LUser, LServer, Group, both),
%% Push members of groups that are displayed to this group
push_displayed_to_user(LUser, LServer, Group, Host, both),
R = #sr_user{us = US, group_host = {Group, Host}},
F = fun() ->
mnesia:write(R)
end,
mnesia:transaction(F).
push_displayed_to_user(LUser, LServer, Group, Host, Subscription) ->
GroupsOpts = groups_with_opts(LServer),
GroupOpts = proplists:get_value(Group, GroupsOpts, []),
DisplayedGroups = proplists:get_value(displayed_groups, GroupOpts, []),
[push_members_to_user(LUser, LServer, DGroup, Host, Subscription) || DGroup <- DisplayedGroups].
remove_user_from_group(Host, US, Group) ->
GroupHost = {Group, Host},
R = #sr_user{us = US, group_host = GroupHost},
F = fun() ->
mnesia:delete_object(R)
end,
mnesia:transaction(F).
Result = mnesia:transaction(F),
{LUser, LServer} = US,
%% Push removal of the old user to members of groups where the group that this user was members was displayed
push_user_to_displayed(LUser, LServer, Group, remove),
%% Push removal of members of groups that where displayed to the group which this user has left
push_displayed_to_user(LUser, LServer, Group, Host, remove),
Result.
user_registered(User, Server) ->
push_members_to_user(LUser, LServer, Group, Host, Subscription) ->
GroupsOpts = groups_with_opts(LServer),
GroupOpts = proplists:get_value(Group, GroupsOpts, []),
GroupName = proplists:get_value(name, GroupOpts, Group),
Members = get_group_users(Host, Group),
lists:foreach(
fun({U, S}) ->
push_roster_item(LUser, LServer, U, S, GroupName, Subscription)
end, Members).
register_user(User, Server) ->
%% Get list of groups where this user is member
Groups = get_user_groups({User, Server}),
%% Push this user to members of groups where is displayed a group which this user is member
[push_user_to_displayed(User, Server, Group, both) || Group <- Groups].
remove_user(User, Server) ->
push_user_to_members(User, Server, remove).
push_user_to_members(User, Server, Subscription) ->
try
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
@ -543,14 +598,7 @@ user_registered(User, Server) ->
GroupName = proplists:get_value(name, GroupOpts, Group),
lists:foreach(
fun({U, S}) ->
Item = #roster{usj = {U, S, {LUser, LServer, undefined}},
us = {U, S},
jid = {LUser, LServer, undefined},
name = "",
subscription = both,
ask = none,
groups = [GroupName]},
push_item(U, S, exmpp_jid:make_bare_jid(S), Item)
push_roster_item(U, S, LUser, LServer, GroupName, Subscription)
end, get_group_users(LUser, LServer, Group, GroupOpts))
end, lists:usort(SpecialGroups++UserGroups))
catch
@ -558,6 +606,27 @@ user_registered(User, Server) ->
ok
end.
push_user_to_displayed(LUser, LServer, Group, Subscription) ->
GroupsOpts = groups_with_opts(LServer),
GroupOpts = proplists:get_value(Group, GroupsOpts, []),
GroupName = proplists:get_value(name, GroupOpts, Group),
DisplayedToGroupsOpts = displayed_to_groups(Group, LServer),
[push_user_to_group(LUser, LServer, GroupD, GroupName, Subscription) || {GroupD, _Opts} <- DisplayedToGroupsOpts].
push_user_to_group(LUser, LServer, Group, GroupName, Subscription) ->
lists:foreach(
fun({U, S}) ->
push_roster_item(U, S, LUser, LServer, GroupName, Subscription)
end, get_group_users(LServer, Group)).
%% Get list of groups to which this group is displayed
displayed_to_groups(GroupName, LServer) ->
GroupsOpts = groups_with_opts(LServer),
lists:filter(
fun({_Group, Opts}) ->
lists:member(GroupName, proplists:get_value(displayed_groups, Opts, []))
end, GroupsOpts).
push_item(_User, _Server, _From, none) ->
ok;
push_item(User, Server, From, Item) ->
@ -580,6 +649,16 @@ push_item(User, Server, From, Item) ->
ejabberd_router:route(JID, JID, Stanza)
end, ejabberd_sm:get_user_resources(list_to_binary(User), list_to_binary(Server))).
push_roster_item(User, Server, ContactU, ContactS, GroupName, Subscription) ->
Item = #roster{usj = {User, Server, {ContactU, ContactS, ""}},
us = {User, Server},
jid = {ContactU, ContactS, ""},
name = "",
subscription = Subscription,
ask = none,
groups = [GroupName]},
push_item(User, Server, jlib:make_jid("", Server, ""), Item).
item_to_xml(Item) ->
{U, S, R} = Item#roster.jid,
Attrs1 = exmpp_xml:set_attribute_in_list([],

View File

@ -376,7 +376,7 @@ get_private_data(LServer, Username, LXMLNS) ->
"namespace='", LXMLNS, "';"]).
del_user_private_storage(LServer, Username) ->
ejabberd_odbc:sql_transaction(
ejabberd_odbc:sql_query(
LServer,
["delete from private_storage where username='", Username, "';"]).