diff --git a/ChangeLog b/ChangeLog index e1dea738f..bb509b737 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,11 +1,22 @@ -2009-01-15 Pablo Polvorin +2009-01-16 Jean-Sébastien Pédron + + 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 * src/mod_muc/mod_muc.erl, src/mod_muc/mod_muc_room.erl: Store registered nicknames, rooms and domains as binary(). Use document_to_iolist/1 and iolist_size/1 instead of document_to_list/1. -2009-01-11 Pablo Polvorin +2009-01-11 Pablo Polvorin * src/mod_pubsub/mod_pubsub.erl: Fix typo, initial update to new hooks API (using binaries). mod_pubsub is still @@ -21,42 +32,42 @@ permissions (thanks to Andy Skelton)(EJAB-840) * src/mod_pubsub/node_default.erl: Likewise -2009-01-10 Pablo Polvorin +2009-01-10 Pablo Polvorin * src/mod_vcard_odbc.erl: Fix bug in user search. * 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 * src/mod_pubsub/node_default.erl: fix unsubscription of full jid subscribed node (thanks to Andy Skelton)(EJAB-839) -2009-01-09 Pablo Polvorin +2009-01-09 Pablo Polvorin * 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 * src/mod_pubsub/mod_pubsub.erl: completely support subscription using @@ -77,35 +88,39 @@ * src/mod_pubsub/node_default.erl: Likewise * src/mod_pubsub/node_club.erl: Likewise -2009-01-08 Pablo Polvorin +2009-01-08 Pablo Polvorin + * 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, - src/mod_irc/mod_irc.erl, src/web/ejabberd_http_poll.erl, + src/mod_irc/mod_irc.erl, src/web/ejabberd_http_poll.erl, src/mod_privacy_odbc.erl, src/ejabberd_c2s.erl, src/mod_announce.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_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 + * src/mod_roster.erl: Fix typo. -2009-01-03 Pablo Polvorin - * src/mod_pubsub_node_default.erl: Fix typo +2009-01-03 Pablo Polvorin - * 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/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/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 @@ -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 + + * 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 * 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 + + * 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 * src/mod_pubsub/mod_pubsub.erl: Fix send_last_published_item issue @@ -158,7 +220,7 @@ * src/mod_pubsub/mod_pubsub.erl: Check option of the nodetree instead of checking configuration (thanks to Eric Cestari)(EJAB-737) -2008-12-17 Pablo Polvorin +2008-12-17 Pablo Polvorin * src/mod_muc/mod_muc_room.erl: Fix bug in MUC invite. diff --git a/doc/guide.tex b/doc/guide.tex index 0ca65220b..9501c0cc5 100644 --- a/doc/guide.tex +++ b/doc/guide.tex @@ -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, \}} Matches any member of a Shared Roster Group with name \term{} in the virtual host. Example: +\begin{verbatim} +{acl, techgroupmembers, {shared_group, "techteam"}}. +\end{verbatim} +\titem{\{shared\_group, , \}} Matches any member of a Shared Roster Group with name \term{} in the virtual host \term{}. Example: +\begin{verbatim} +{acl, techgroupmembers, {shared_group, "techteam", "example.org"}}. +\end{verbatim} \titem{\{user\_regexp, \}} Matches any local user with a name that matches \term{} on local virtual hosts. Example: \begin{verbatim} diff --git a/src/acl.erl b/src/acl.erl index c21e62488..e3fd76020 100644 --- a/src/acl.erl +++ b/src/acl.erl @@ -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); diff --git a/src/cyrsasl_digest.erl b/src/cyrsasl_digest.erl index c269a74bf..533fc4265 100644 --- a/src/cyrsasl_digest.erl +++ b/src/cyrsasl_digest.erl @@ -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,27 +43,35 @@ 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), - AuthzId = xml:get_attr_s("authzid", KeyVals), - case (State#state.get_password)(UserName) of - {false, _} -> + 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}; - {Passwd, AuthModule} -> - Response = response(KeyVals, UserName, Passwd, - Nonce, AuthzId, "AUTHENTICATE"), - case xml:get_attr_s("response", KeyVals) of - Response -> - RspAuth = response(KeyVals, - UserName, Passwd, - Nonce, AuthzId, ""), - {continue, - "rspauth=" ++ RspAuth, - State#state{step = 5, - auth_module = AuthModule, - username = UserName, - authzid = AuthzId}}; - _ -> - {error, 'not-authorized', UserName} + true -> + AuthzId = xml:get_attr_s("authzid", KeyVals), + case (State#state.get_password)(UserName) of + {false, _} -> + {error, 'not-authorized', UserName}; + {Passwd, AuthModule} -> + Response = response(KeyVals, UserName, Passwd, + Nonce, AuthzId, "AUTHENTICATE"), + case xml:get_attr_s("response", KeyVals) of + Response -> + RspAuth = response(KeyVals, + UserName, Passwd, + Nonce, AuthzId, ""), + {continue, + "rspauth=" ++ RspAuth, + State#state{step = 5, + auth_module = AuthModule, + username = UserName, + authzid = AuthzId}}; + _ -> + {error, 'not-authorized', UserName} + end end end end; @@ -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. diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index 2cefb6eab..6462b6edd 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -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. %%%---------------------------------------------------------------------- diff --git a/src/ejabberd_auth_internal.erl b/src/ejabberd_auth_internal.erl index 04efc52c8..ae4ef4989 100644 --- a/src/ejabberd_auth_internal.erl +++ b/src/ejabberd_auth_internal.erl @@ -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; diff --git a/src/ejabberd_auth_ldap.erl b/src/ejabberd_auth_ldap.erl index b4128a9d1..7fd8487ad 100644 --- a/src/ejabberd_auth_ldap.erl +++ b/src/ejabberd_auth_ldap.erl @@ -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}. diff --git a/src/ejabberd_auth_odbc.erl b/src/ejabberd_auth_odbc.erl index 79debf2ad..1f0a97b0c 100644 --- a/src/ejabberd_auth_odbc.erl +++ b/src/ejabberd_auth_odbc.erl @@ -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; diff --git a/src/ejabberd_auth_pam.erl b/src/ejabberd_auth_pam.erl index f26296d33..5f67bedba 100644 --- a/src/ejabberd_auth_pam.erl +++ b/src/ejabberd_auth_pam.erl @@ -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. diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index 51b228c4f..63c67cb6d 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -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), diff --git a/src/jlib.erl b/src/jlib.erl index e6cdceb0d..d76946fb0 100644 --- a/src/jlib.erl +++ b/src/jlib.erl @@ -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( diff --git a/src/jlib.hrl b/src/jlib.hrl index 39731b14a..93d2a5dce 100644 --- a/src/jlib.hrl +++ b/src/jlib.hrl @@ -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}). diff --git a/src/mod_last.erl b/src/mod_last.erl index f1f8e7bda..4ddb716f3 100644 --- a/src/mod_last.erl +++ b/src/mod_last.erl @@ -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) @@ -147,8 +167,9 @@ 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} -> diff --git a/src/mod_last_odbc.erl b/src/mod_last_odbc.erl index 4eab4ffea..4d8ea152d 100644 --- a/src/mod_last_odbc.erl +++ b/src/mod_last_odbc.erl @@ -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 diff --git a/src/mod_muc/mod_muc.erl b/src/mod_muc/mod_muc.erl index 55cf0008c..daa953371 100644 --- a/src/mod_muc/mod_muc.erl +++ b/src/mod_muc/mod_muc.erl @@ -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 diff --git a/src/mod_privacy.erl b/src/mod_privacy.erl index 7d035994f..b159c3983 100644 --- a/src/mod_privacy.erl +++ b/src/mod_privacy.erl @@ -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 diff --git a/src/mod_privacy_odbc.erl b/src/mod_privacy_odbc.erl index 953a2dc2c..1147c51db 100644 --- a/src/mod_privacy_odbc.erl +++ b/src/mod_privacy_odbc.erl @@ -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, "';"]). diff --git a/src/mod_pubsub/node.template b/src/mod_pubsub/node.template index 447c919ad..7b7fb175c 100644 --- a/src/mod_pubsub/node.template +++ b/src/mod_pubsub/node.template @@ -26,7 +26,6 @@ -author(__TO_BE_DEFINED__). -include("pubsub.hrl"). --include("jlib.hrl"). -behaviour(gen_pubsub_node). diff --git a/src/mod_register.erl b/src/mod_register.erl index 3b66aeea8..a51408920 100644 --- a/src/mod_register.erl +++ b/src/mod_register.erl @@ -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; diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl index e0522510f..6ed4fbb0c 100644 --- a/src/mod_shared_roster.erl +++ b/src/mod_shared_roster.erl @@ -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([], diff --git a/src/odbc/odbc_queries.erl b/src/odbc/odbc_queries.erl index c9f6819be..a2fb0ae41 100644 --- a/src/odbc/odbc_queries.erl +++ b/src/odbc/odbc_queries.erl @@ -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, "';"]).