diff --git a/doc/guide.tex b/doc/guide.tex index 468faf009..5d5bf2fdc 100644 --- a/doc/guide.tex +++ b/doc/guide.tex @@ -890,6 +890,12 @@ The available modules, their purpose and the options allowed by each one are: Handles incoming HTTP connections.\\ Options: \texttt{captcha}, \texttt{certfile}, \texttt{default\_host}, \texttt{http\_bind}, \texttt{http\_poll}, \texttt{request\_handlers}, \texttt{tls}, \texttt{tls\_compression}, \texttt{trusted\_proxies}, \texttt{web\_admin}\\ + \titem{\texttt{ejabberd\_xmlrpc}} + Handles XML-RPC requests to execute ejabberd commands (\ref{eja-commands}).\\ + Options: \texttt{access\_commands}, \texttt{maxsessions}, \texttt{timeout}.\\ + You can find option explanations, example configuration in old and new format, + and example calls in several languages in the old + \footahref{https://raw.github.com/processone/ejabberd-contrib/master/ejabberd\_xmlrpc/README.txt}{ejabberd\_xmlrpc README.txt} \end{description} @@ -4046,15 +4052,28 @@ Options: not add/remove/modify contacts, or subscribe/unsubscribe presence. By default there aren't restrictions. + \titem{managers} \ind{options!managers} + List of remote entities that can manage users rosters using Remote Roster Management + (\xepref{0321}). + The protocol sections implemented are: + \term{4.2. The remote entity requests current user's roster}. + \term{4.3. The user updates roster}. + \term{4.4. The remote entity updates the user's roster}. + A remote entity cab only get or modify roster items that have the same domain as the entity. + Default value is: \term{[]}. \end{description} -This example configuration enables Roster Versioning with storage of current id: +This example configuration enables Roster Versioning with storage of current id. +The ICQ and MSN transports can get ICQ and MSN contacts, add them, or remove them for any local account: \begin{verbatim} modules: ... mod_roster: versioning: true store_current_id: true + managers: + - "icq.example.org" + - "msn.example.org" ... \end{verbatim} @@ -5130,9 +5149,9 @@ with a defined number and type of calling arguments and type of result that is registered in the \term{ejabberd\_commands} service. Those commands can be defined in any Erlang module and executed using any valid frontend. -\ejabberd{} includes a frontend to execute \term{ejabberd commands}: the script \term{ejabberdctl}. +\ejabberd{} includes two frontends to execute \term{ejabberd commands}: the script \term{ejabberdctl} (\ref{ejabberdctl}) +and the \term{ejabberd\_xmlrpc} listener (\ref{listened-module}). Other known frontends that can be installed to execute ejabberd commands in different ways are: -\term{ejabberd\_xmlrpc} (XML-RPC service), \term{mod\_rest} (HTTP POST service), \term{mod\_shcommands} (ejabberd WebAdmin page). diff --git a/ejabberd.yml.example b/ejabberd.yml.example index 307b2d311..28f61fdbe 100644 --- a/ejabberd.yml.example +++ b/ejabberd.yml.example @@ -599,12 +599,11 @@ modules: ## ## Enable modules with custom options in a specific virtual host ## -## host_config: +## append_host_config: ## "localhost": -## add: -## modules: -## mod_echo: -## host: "mirror.localhost" +## modules: +## mod_echo: +## host: "mirror.localhost" ### Local Variables: ### mode: yaml diff --git a/rebar.config.script b/rebar.config.script index f342a6c90..7a5e332c4 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -16,7 +16,7 @@ Cfg = case file:consult("vars.config") of Macros = lists:flatmap( fun({roster_gateway_workaround, true}) -> [{d, 'ROSTER_GATEWAY_WORKAROUND'}]; - ({transient_supervisors, true}) -> + ({transient_supervisors, false}) -> [{d, 'NO_TRANSIENT_SUPERVISORS'}]; ({nif, true}) -> [{d, 'NIF'}]; diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index e5304044a..ae5fc97b8 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -94,7 +94,7 @@ tls_options = [], authenticated = false, jid, - user = "", server = ?MYNAME, resource = <<"">>, + user = "", server = <<"">>, resource = <<"">>, sid, pres_t = ?SETS:new(), pres_f = ?SETS:new(), @@ -291,7 +291,12 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> DefaultLang = ?MYLANG, case xml:get_attr_s(<<"xmlns:stream">>, Attrs) of ?NS_STREAM -> - Server = jlib:nameprep(xml:get_attr_s(<<"to">>, Attrs)), + Server = + case StateData#state.server of + <<"">> -> + jlib:nameprep(xml:get_attr_s(<<"to">>, Attrs)); + S -> S + end, case lists:member(Server, ?MYHOSTS) of true -> Lang = case xml:get_attr_s(<<"xml:lang">>, Attrs) of @@ -647,6 +652,7 @@ wait_for_feature_request({xmlstreamelement, El}, StateData#state{streamid = new_id(), authenticated = true, auth_module = AuthModule, + sasl_state = undefined, user = U}); {continue, ServerOut, NewSASLState} -> send_element(StateData, @@ -804,6 +810,7 @@ wait_for_sasl_response({xmlstreamelement, El}, StateData#state{streamid = new_id(), authenticated = true, auth_module = AuthModule, + sasl_state = undefined, user = U}); {ok, Props, ServerOut} -> (StateData#state.sockmod):reset_stream(StateData#state.socket), @@ -824,6 +831,7 @@ wait_for_sasl_response({xmlstreamelement, El}, StateData#state{streamid = new_id(), authenticated = true, auth_module = AuthModule, + sasl_state = undefined, user = U}); {continue, ServerOut, NewSASLState} -> send_element(StateData, diff --git a/src/ejabberd_odbc.erl b/src/ejabberd_odbc.erl index d3d8a2a84..554322c23 100644 --- a/src/ejabberd_odbc.erl +++ b/src/ejabberd_odbc.erl @@ -484,7 +484,7 @@ abort_on_driver_error(Reply, From) -> %% Open an ODBC database connection odbc_connect(SQLServer) -> ejabberd:start_app(odbc), - odbc:connect(SQLServer, [{scrollable_cursors, off}]). + odbc:connect(binary_to_list(SQLServer), [{scrollable_cursors, off}]). %% == Native PostgreSQL code diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index 2823cde62..4789f8652 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -88,7 +88,7 @@ -define(SUPERVISOR_START, p1_fsm:start(ejabberd_s2s_in, [SockData, Opts], - ?FSMOPTS ++ fsm_limit_opts(Opts)). + ?FSMOPTS ++ fsm_limit_opts(Opts))). -else. diff --git a/src/ejabberd_web_admin.erl b/src/ejabberd_web_admin.erl index a1c4b25a9..7a676f674 100644 --- a/src/ejabberd_web_admin.erl +++ b/src/ejabberd_web_admin.erl @@ -1570,9 +1570,7 @@ user_info(User, Server, Query, Lang) -> end, lists:sort(Resources))))] end, - Password = ejabberd_auth:get_password_s(User, Server), - FPassword = [?INPUT(<<"password">>, <<"password">>, - Password), + FPassword = [?INPUT(<<"text">>, <<"password">>, <<"">>), ?C(<<" ">>), ?INPUTT(<<"submit">>, <<"chpassword">>, <<"Change Password">>)], diff --git a/src/ejabberd_xmlrpc.erl b/src/ejabberd_xmlrpc.erl index 40223f77e..acda0c7f9 100644 --- a/src/ejabberd_xmlrpc.erl +++ b/src/ejabberd_xmlrpc.erl @@ -345,8 +345,10 @@ build_fault_response(Code, ParseString, ParseArgs) -> do_command(AccessCommands, Auth, Command, AttrL, ArgsF, ResultF) -> ArgsFormatted = format_args(AttrL, ArgsF), + {UserT, ServerT, PasswordT} = Auth, + AuthBin = {list_to_binary(UserT), list_to_binary(ServerT), list_to_binary(PasswordT)}, Result = - ejabberd_commands:execute_command(AccessCommands, Auth, + ejabberd_commands:execute_command(AccessCommands, AuthBin, Command, ArgsFormatted), ResultFormatted = format_result(Result, ResultF), {command_result, ResultFormatted}. diff --git a/src/mod_carboncopy.erl b/src/mod_carboncopy.erl index 087d090c8..cc5cb83b0 100644 --- a/src/mod_carboncopy.erl +++ b/src/mod_carboncopy.erl @@ -149,7 +149,7 @@ check_and_forward(JID, #xmlel{name = <<"message">>, attrs = Attrs} = Packet, Dir <<"chat">> -> case xml:get_subtag(Packet, <<"private">>) of false -> - case xml:get_subtag(Packet,<<"forwarded">>) of + case xml:get_subtag(Packet,<<"received">>) of false -> send_copies(JID, Packet, Direction); _ -> diff --git a/src/mod_muc.erl b/src/mod_muc.erl index b8d6876b0..7934e6f95 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -657,7 +657,7 @@ load_permanent_rooms(Host, ServerHost, Access, HistorySize, RoomShaper) -> start_new_room(Host, ServerHost, Access, Room, HistorySize, RoomShaper, From, Nick, DefRoomOpts) -> - case restore_room(ServerHost, Room, Host) of + case restore_room(ServerHost, Host, Room) of error -> ?DEBUG("MUC: open new room '~s'~n", [Room]), mod_muc_room:start(Host, ServerHost, Access, diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index b1c5c92ff..1ef90b1eb 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -2334,6 +2334,14 @@ send_nick_changing(JID, OldNick, StateData, [{<<"affiliation">>, SAffiliation}, {<<"role">>, SRole}] end, + Status110 = case JID == Info#user.jid of + true -> + [#xmlel{name = <<"status">>, + attrs = [{<<"code">>, <<"110">>}] + }]; + false -> + [] + end, Packet1 = #xmlel{name = <<"presence">>, attrs = [{<<"type">>, @@ -2356,7 +2364,7 @@ send_nick_changing(JID, OldNick, StateData, [{<<"code">>, <<"303">>}], children = - []}]}]}, + []}|Status110]}]}, Packet2 = xml:append_subtags(Presence, [#xmlel{name = <<"x">>, attrs = @@ -2371,7 +2379,7 @@ send_nick_changing(JID, OldNick, StateData, ItemAttrs2, children = - []}]}]), + []}|Status110]}]), if SendOldUnavailable -> ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, OldNick), diff --git a/src/mod_roster.erl b/src/mod_roster.erl index 7415aa3de..4851b8fb5 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -130,6 +130,9 @@ stop(Host) -> gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_ROSTER). +process_iq(From, To, IQ) when ((From#jid.luser == <<"">>) andalso (From#jid.resource == <<"">>)) -> + process_iq_manager(From, To, IQ); + process_iq(From, To, IQ) -> #iq{sub_el = SubEl} = IQ, #jid{lserver = LServer} = From, @@ -465,15 +468,16 @@ try_process_iq_set(From, To, #iq{sub_el = SubEl} = IQ) -> process_iq_set(From, To, IQ) end. -process_iq_set(From, To, #iq{sub_el = SubEl} = IQ) -> +process_iq_set(From, To, #iq{sub_el = SubEl, id = Id} = IQ) -> #xmlel{children = Els} = SubEl, - lists:foreach(fun (El) -> process_item_set(From, To, El) + Managed = is_managed_from_id(Id), + lists:foreach(fun (El) -> process_item_set(From, To, El, Managed) end, Els), IQ#iq{type = result, sub_el = []}. process_item_set(From, To, - #xmlel{attrs = Attrs, children = Els}) -> + #xmlel{attrs = Attrs, children = Els}, Managed) -> JID1 = jlib:string_to_jid(xml:get_attr_s(<<"jid">>, Attrs)), #jid{user = User, luser = LUser, lserver = LServer} = @@ -484,12 +488,13 @@ process_item_set(From, To, LJID = jlib:jid_tolower(JID1), F = fun () -> Item = get_roster_by_jid_t(LUser, LServer, LJID), - Item1 = process_item_attrs(Item, Attrs), + Item1 = process_item_attrs_managed(Item, Attrs, Managed), Item2 = process_item_els(Item1, Els), case Item2#roster.subscription of remove -> del_roster_t(LUser, LServer, LJID); _ -> update_roster_t(LUser, LServer, LJID, Item2) end, + send_itemset_to_managers(From, Item2, Managed), Item3 = ejabberd_hooks:run_fold(roster_process_item, LServer, Item2, [LServer]), @@ -511,7 +516,7 @@ process_item_set(From, To, ?DEBUG("ROSTER: roster item set error: ~p~n", [E]), ok end end; -process_item_set(_From, _To, _) -> ok. +process_item_set(_From, _To, _, _Managed) -> ok. process_item_attrs(Item, [{Attr, Val} | Attrs]) -> case Attr of @@ -1554,6 +1559,91 @@ webadmin_user(Acc, _User, _Server, Lang) -> Acc ++ [?XE(<<"h3">>, [?ACT(<<"roster/">>, <<"Roster">>)])]. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Implement XEP-0321 Remote Roster Management + +process_iq_manager(From, To, IQ) -> + %% Check what access is allowed for From to To + MatchDomain = From#jid.lserver, + case is_domain_managed(MatchDomain, To#jid.lserver) of + true -> + process_iq_manager2(MatchDomain, To, IQ); + false -> + #iq{sub_el = SubEl} = IQ, + IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]} + end. + +process_iq_manager2(MatchDomain, To, IQ) -> + %% If IQ is SET, filter the input IQ + IQFiltered = maybe_filter_request(MatchDomain, IQ), + %% Call the standard function with reversed JIDs + IdInitial = IQFiltered#iq.id, + ResIQ = process_iq(To, To, IQFiltered#iq{id = <<"roster-remotely-managed">>}), + %% Filter the output IQ + filter_stanza(MatchDomain, ResIQ#iq{id = IdInitial}). + +is_domain_managed(ContactHost, UserHost) -> + Managers = gen_mod:get_module_opt(UserHost, ?MODULE, managers, + fun(B) when is_list(B) -> B end, + []), + lists:member(ContactHost, Managers). + +maybe_filter_request(MatchDomain, IQ) when IQ#iq.type == set -> + filter_stanza(MatchDomain, IQ); +maybe_filter_request(_MatchDomain, IQ) -> + IQ. + +filter_stanza(_MatchDomain, #iq{sub_el = []} = IQ) -> + IQ; +filter_stanza(MatchDomain, #iq{sub_el = [SubEl | _]} = IQ) -> + #iq{sub_el = SubElFiltered} = IQRes = + filter_stanza(MatchDomain, IQ#iq{sub_el = SubEl}), + IQRes#iq{sub_el = [SubElFiltered]}; +filter_stanza(MatchDomain, #iq{sub_el = SubEl} = IQ) -> + #xmlel{name = Type, attrs = Attrs, children = Items} = SubEl, + ItemsFiltered = lists:filter( + fun(Item) -> + is_item_of_domain(MatchDomain, Item) end, Items), + SubElFiltered = #xmlel{name=Type, attrs = Attrs, children = ItemsFiltered}, + IQ#iq{sub_el = SubElFiltered}. + +is_item_of_domain(MatchDomain, #xmlel{} = El) -> + lists:any(fun(Attr) -> is_jid_of_domain(MatchDomain, Attr) end, El#xmlel.attrs); +is_item_of_domain(_MatchDomain, {xmlcdata, _}) -> + false. + +is_jid_of_domain(MatchDomain, {<<"jid">>, JIDString}) -> + case jlib:string_to_jid(JIDString) of + JID when JID#jid.lserver == MatchDomain -> true; + _ -> false + end; +is_jid_of_domain(_, _) -> + false. + +process_item_attrs_managed(Item, Attrs, true) -> + process_item_attrs_ws(Item, Attrs); +process_item_attrs_managed(Item, _Attrs, false) -> + process_item_attrs(Item, _Attrs). + +send_itemset_to_managers(_From, _Item, true) -> + ok; +send_itemset_to_managers(From, Item, false) -> + {_, UserHost} = Item#roster.us, + {_ContactUser, ContactHost, _ContactResource} = Item#roster.jid, + %% Check if the component is an allowed manager + IsManager = is_domain_managed(ContactHost, UserHost), + case IsManager of + true -> push_item(<<"">>, ContactHost, <<"">>, From, Item); + false -> ok + end. + +is_managed_from_id(<<"roster-remotely-managed">>) -> + true; +is_managed_from_id(_Id) -> + false. + + export(_Server) -> [{roster, fun(Host, #roster{usj = {LUser, LServer, LJID}} = R)