diff --git a/include/mod_muc_room.hrl b/include/mod_muc_room.hrl index 5fabbb9e8..c90a5c028 100644 --- a/include/mod_muc_room.hrl +++ b/include/mod_muc_room.hrl @@ -61,7 +61,8 @@ max_users = ?MAX_USERS_DEFAULT :: non_neg_integer() | none, logging = false :: boolean(), vcard = <<"">> :: binary(), - captcha_whitelist = (?SETS):empty() :: ?TGB_SET + captcha_whitelist = (?SETS):empty() :: ?TGB_SET, + mam = false :: boolean() }). -type config() :: #config{}. diff --git a/sql/lite.sql b/sql/lite.sql index 461686d1a..89d68eadd 100644 --- a/sql/lite.sql +++ b/sql/lite.sql @@ -279,6 +279,8 @@ CREATE TABLE archive ( xml text NOT NULL, txt text, id INTEGER PRIMARY KEY AUTOINCREMENT, + kind text, + nick text, created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ); diff --git a/sql/mysql.sql b/sql/mysql.sql index fab76b9a2..21e7b9b52 100644 --- a/sql/mysql.sql +++ b/sql/mysql.sql @@ -93,6 +93,8 @@ CREATE TABLE archive ( xml text NOT NULL, txt text, id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, + kind varchar(10), + nick varchar(250), created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB CHARACTER SET utf8; diff --git a/sql/pg.sql b/sql/pg.sql index 2a052d337..099356917 100644 --- a/sql/pg.sql +++ b/sql/pg.sql @@ -93,6 +93,8 @@ CREATE TABLE archive ( xml text NOT NULL, txt text, id SERIAL, + kind text, + nick text, created_at TIMESTAMP NOT NULL DEFAULT now() ); diff --git a/src/mod_mam.erl b/src/mod_mam.erl index d0ac38d54..1d4dd1a50 100644 --- a/src/mod_mam.erl +++ b/src/mod_mam.erl @@ -34,11 +34,12 @@ -export([user_send_packet/4, user_receive_packet/5, process_iq_v0_2/3, process_iq_v0_3/3, remove_user/2, - mod_opt_type/1]). + mod_opt_type/1, muc_process_iq/4, muc_filter_message/5]). -include_lib("stdlib/include/ms_transform.hrl"). -include("jlib.hrl"). -include("logger.hrl"). +-include("mod_muc_room.hrl"). -record(archive_msg, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$2', @@ -46,7 +47,9 @@ timestamp = now() :: erlang:timestamp() | '_' | '$1', peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3', bare_peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3', - packet = #xmlel{} :: xmlel() | '_'}). + packet = #xmlel{} :: xmlel() | '_', + nick = <<"">> :: binary(), + type = chat :: chat | groupchat}). -record(archive_prefs, {us = {<<"">>, <<"">>} :: {binary(), binary()}, @@ -75,19 +78,16 @@ start(Host, Opts) -> user_receive_packet, 500), ejabberd_hooks:add(user_send_packet, Host, ?MODULE, user_send_packet, 500), + ejabberd_hooks:add(muc_filter_message, Host, ?MODULE, + muc_filter_message, 50), + ejabberd_hooks:add(muc_process_iq, Host, ?MODULE, + muc_process_iq, 50), ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50), ejabberd_hooks:add(anonymous_purge_hook, Host, ?MODULE, remove_user, 50), ok. -init_db(odbc, Host) -> - Muchost = gen_mod:get_module_opt_host(Host, mod_muc, - <<"conference.@HOST@">>), - ets:insert(ejabberd_modules, {ejabberd_module, {mod_mam, Muchost}, - [{db_type, odbc}]}), - mnesia:dirty_write({local_config, {modules,Muchost}, - [{mod_mam, [{db_type, odbc}]}]}); init_db(mnesia, _Host) -> mnesia:create_table(archive_msg, [{disc_only_copies, [node()]}, @@ -114,6 +114,10 @@ stop(Host) -> user_send_packet, 500), ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE, user_receive_packet, 500), + ejabberd_hooks:delete(muc_filter_message, Host, ?MODULE, + muc_filter_message, 50), + ejabberd_hooks:delete(muc_process_iq, Host, ?MODULE, + muc_process_iq, 50), gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MAM_TMP), gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MAM_TMP), gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MAM_0), @@ -152,8 +156,7 @@ user_receive_packet(Pkt, C2SState, JID, Peer, _To) -> case should_archive(Pkt) of true -> NewPkt = strip_my_archived_tag(Pkt, LServer), - case store(C2SState, NewPkt, LUser, LServer, - Peer, true, recv) of + case store_msg(C2SState, NewPkt, LUser, LServer, Peer, recv) of {ok, ID} -> Archived = #xmlel{name = <<"archived">>, attrs = [{<<"by">>, LServer}, @@ -164,8 +167,6 @@ user_receive_packet(Pkt, C2SState, JID, Peer, _To) -> _ -> NewPkt end; - muc -> - Pkt; false -> Pkt end. @@ -174,29 +175,25 @@ user_send_packet(Pkt, C2SState, JID, Peer) -> LUser = JID#jid.luser, LServer = JID#jid.lserver, case should_archive(Pkt) of - S when (S==true) -> + true -> NewPkt = strip_my_archived_tag(Pkt, LServer), - store0(C2SState, jlib:replace_from_to(JID, Peer, NewPkt), - LUser, LServer, Peer, S, send), + store_msg(C2SState, jlib:replace_from_to(JID, Peer, NewPkt), + LUser, LServer, Peer, send), NewPkt; - S when (S==muc) -> - NewPkt = strip_my_archived_tag(Pkt, LServer), - case store0(C2SState, jlib:replace_from_to(JID, Peer, NewPkt), - LUser, LServer, Peer, S, send) of - {ok, ID} -> - By = jlib:jid_to_string(Peer), - Archived = #xmlel{name = <<"archived">>, - attrs = [{<<"by">>, By}, {<<"xmlns">>, ?NS_MAM_TMP}, - {<<"id">>, ID}]}, - NewEls = [Archived|NewPkt#xmlel.children], - NewPkt#xmlel{children = NewEls}; - _ -> - NewPkt - end; false -> Pkt end. +muc_filter_message(Pkt, #state{config = Config} = MUCState, + RoomJID, From, FromNick) -> + if Config#config.mam -> + NewPkt = strip_my_archived_tag(Pkt, MUCState#state.server_host), + store_muc(MUCState, NewPkt, RoomJID, From, FromNick), + NewPkt; + true -> + Pkt + end. + % Query archive v0.2 process_iq_v0_2(#jid{lserver = LServer} = From, #jid{lserver = LServer} = To, @@ -217,7 +214,7 @@ process_iq_v0_2(#jid{lserver = LServer} = From, (_) -> [] end, SubEl#xmlel.children), - process_iq(From, To, IQ, SubEl, Fs); + process_iq(LServer, From, To, IQ, SubEl, Fs, chat); process_iq_v0_2(From, To, IQ) -> process_iq(From, To, IQ). @@ -225,7 +222,28 @@ process_iq_v0_2(From, To, IQ) -> process_iq_v0_3(#jid{lserver = LServer} = From, #jid{lserver = LServer} = To, #iq{type = set, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) -> - Fs = case {xml:get_subtag_with_xmlns(SubEl, <<"x">>, ?NS_XDATA), + process_iq(LServer, From, To, IQ, SubEl, get_xdata_fields(SubEl), chat); +process_iq_v0_3(From, To, IQ) -> + process_iq(From, To, IQ). + +muc_process_iq(#iq{type = set, + sub_el = #xmlel{name = <<"query">>, + attrs = Attrs} = SubEl} = IQ, + MUCState, From, To) -> + case xml:get_attr_s(<<"xmlns">>, Attrs) of + ?NS_MAM_0 -> + LServer = MUCState#state.server_host, + Role = mod_muc_room:get_role(From, MUCState), + process_iq(LServer, From, To, IQ, SubEl, + get_xdata_fields(SubEl), {groupchat, Role}); + _ -> + IQ + end; +muc_process_iq(IQ, _MUCState, _From, _To) -> + IQ. + +get_xdata_fields(SubEl) -> + case {xml:get_subtag_with_xmlns(SubEl, <<"x">>, ?NS_XDATA), xml:get_subtag_with_xmlns(SubEl, <<"set">>, ?NS_RSM)} of {#xmlel{} = XData, false} -> jlib:parse_xdata_submit(XData); @@ -235,10 +253,7 @@ process_iq_v0_3(#jid{lserver = LServer} = From, [{<<"set">>, SubEl}]; {false, false} -> [] - end, - process_iq(From, To, IQ, SubEl, Fs); -process_iq_v0_3(From, To, IQ) -> - process_iq(From, To, IQ). + end. %%%=================================================================== %%% Internal functions @@ -276,7 +291,7 @@ process_iq(#jid{luser = LUser, lserver = LServer}, process_iq(_, _, #iq{sub_el = SubEl} = IQ) -> IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}. -process_iq(From, To, IQ, SubEl, Fs) -> +process_iq(LServer, From, To, IQ, SubEl, Fs, MsgType) -> case catch lists:foldl( fun({<<"start">>, [Data|_]}, {_, End, With, RSM}) -> {{_, _, _} = jlib:datetime_string_to_timestamp(Data), @@ -301,7 +316,8 @@ process_iq(From, To, IQ, SubEl, Fs) -> {'EXIT', _} -> IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}; {Start, End, With, RSM} -> - select_and_send(From, To, Start, End, With, RSM, IQ) + select_and_send(LServer, From, To, Start, End, + With, RSM, IQ, MsgType) end. should_archive(#xmlel{name = <<"message">>} = Pkt) -> @@ -310,11 +326,7 @@ should_archive(#xmlel{name = <<"message">>} = Pkt) -> {<<"error">>, _} -> false; {<<"groupchat">>, _} -> - To = xml:get_attr_s(<<"to">>, Pkt#xmlel.attrs), - case (jlib:string_to_jid(To))#jid.resource of - <<"">> -> muc; - _ -> false - end; + false; {_, <<>>} -> %% Empty body false; @@ -370,43 +382,57 @@ should_archive_peer(C2SState, end end. -store0(C2SState, Pkt, LUser, LServer, Peer, Type, Dir) -> - case Type of - muc -> store(C2SState, Pkt, Peer#jid.luser, LServer, - jlib:jid_replace_resource(Peer, LUser), Type, Dir); - true -> store(C2SState, Pkt, LUser, LServer, Peer, Type, Dir) - end. +should_archive_muc(_MUCState, _Peer) -> + %% TODO + true. -store(C2SState, Pkt, LUser, LServer, Peer, Type, Dir) -> +store_msg(C2SState, Pkt, LUser, LServer, Peer, Dir) -> Prefs = get_prefs(LUser, LServer), case should_archive_peer(C2SState, Prefs, Peer) of true -> - do_store(Pkt, LUser, LServer, Peer, Type, Dir, + US = {LUser, LServer}, + store(Pkt, LServer, US, chat, Peer, <<"">>, Dir, gen_mod:db_type(LServer, ?MODULE)); false -> pass end. -do_store(Pkt, LUser, LServer, Peer, Type, _Dir, mnesia) -> +store_muc(MUCState, Pkt, RoomJID, Peer, Nick) -> + case should_archive_muc(MUCState, Peer) of + true -> + LServer = MUCState#state.server_host, + {U, S, _} = jlib:jid_tolower(RoomJID), + store(Pkt, LServer, {U, S}, groupchat, Peer, Nick, recv, + gen_mod:db_type(LServer, ?MODULE)); + false -> + pass + end. + +store(Pkt, _, {LUser, LServer}, Type, Peer, Nick, _Dir, mnesia) -> LPeer = {PUser, PServer, _} = jlib:jid_tolower(Peer), - LServer2 = case Type of muc -> Peer#jid.lserver; _ -> LServer end, TS = now(), ID = jlib:integer_to_binary(now_to_usec(TS)), case mnesia:dirty_write( - #archive_msg{us = {LUser, LServer2}, + #archive_msg{us = {LUser, LServer}, id = ID, timestamp = TS, peer = LPeer, bare_peer = {PUser, PServer, <<>>}, + type = Type, + nick = Nick, packet = Pkt}) of ok -> {ok, ID}; Err -> Err end; -do_store(Pkt, LUser, LServer, Peer, _Type, _Dir, odbc) -> +store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir, odbc) -> TSinteger = now_to_usec(now()), ID = TS = jlib:integer_to_binary(TSinteger), + SUser = case Type of + chat -> LUser; + groupchat -> jlib:jid_to_string({LUser, LHost, <<>>}) + end, BarePeer = jlib:jid_to_string( jlib:jid_tolower( jlib:jid_remove_resource(Peer))), @@ -417,13 +443,15 @@ do_store(Pkt, LUser, LServer, Peer, _Type, _Dir, odbc) -> case ejabberd_odbc:sql_query( LServer, [<<"insert into archive (username, timestamp, " - "peer, bare_peer, xml, txt) values (">>, - <<"'">>, ejabberd_odbc:escape(LUser), <<"', ">>, + "peer, bare_peer, xml, txt, kind, nick) values (">>, + <<"'">>, ejabberd_odbc:escape(SUser), <<"', ">>, <<"'">>, TS, <<"', ">>, <<"'">>, ejabberd_odbc:escape(LPeer), <<"', ">>, <<"'">>, ejabberd_odbc:escape(BarePeer), <<"', ">>, <<"'">>, ejabberd_odbc:escape(XML), <<"', ">>, - <<"'">>, ejabberd_odbc:escape(Body), <<"');">>]) of + <<"'">>, ejabberd_odbc:escape(Body), <<"', ">>, + <<"'">>, jlib:atom_to_binary(Type), <<"', ">>, + <<"'">>, ejabberd_odbc:escape(Nick), <<"');">>]) of {updated, _} -> {ok, ID}; Err -> @@ -507,34 +535,37 @@ get_prefs(LUser, LServer, odbc) -> error end. -select_and_send(#jid{lserver = LServer} = From, - To, Start, End, With, RSM, IQ) -> +select_and_send(LServer, From, To, Start, End, With, RSM, IQ, MsgType) -> DBType = case gen_mod:db_type(LServer, ?MODULE) of odbc -> {odbc, LServer}; DB -> DB end, - select_and_send(From, To, Start, End, With, RSM, IQ, - DBType). + select_and_send(LServer, From, To, Start, End, With, RSM, IQ, + MsgType, DBType). -select_and_send(From, To, Start, End, With, RSM, IQ, DBType) -> - {Msgs, IsComplete, Count} = select_and_start(From, To, Start, End, With, - RSM, DBType), +select_and_send(LServer, From, To, Start, End, With, RSM, IQ, MsgType, DBType) -> + {Msgs, IsComplete, Count} = select_and_start(LServer, From, To, Start, End, + With, RSM, MsgType, DBType), SortedMsgs = lists:keysort(2, Msgs), send(From, To, SortedMsgs, RSM, Count, IsComplete, IQ). -select_and_start(From, _To, StartUser, End, With, RSM, DB) -> - {JidRequestor, Start, With2} = case With of - {room, {LUserRoom, LServerRoom, <<>>} = WithJid} -> - JR = jlib:make_jid(LUserRoom,LServerRoom,<<>>), - St = StartUser, - {JR, St, WithJid}; +select_and_start(LServer, From, To, Start, End, With, RSM, MsgType, DBType) -> + case MsgType of + chat -> + case With of + {room, {_, _, <<"">>} = WithJID} -> + select(LServer, jlib:make_jid(WithJID), Start, End, + WithJID, RSM, MsgType, DBType); _ -> - {From, StartUser, With} - end, - select(JidRequestor, Start, End, With2, RSM, DB). + select(LServer, From, Start, End, + With, RSM, MsgType, DBType) + end; + {groupchat, _Role} -> + select(LServer, To, Start, End, With, RSM, MsgType, DBType) + end. -select(#jid{luser = LUser, lserver = LServer} = JidRequestor, - Start, End, With, RSM, mnesia) -> +select(_LServer, #jid{luser = LUser, lserver = LServer} = JidRequestor, + Start, End, With, RSM, MsgType, mnesia) -> MS = make_matchspec(LUser, LServer, Start, End, With), Msgs = mnesia:dirty_select(archive_msg, MS), {FilteredMsgs, IsComplete} = filter_by_rsm(Msgs, RSM), @@ -543,12 +574,16 @@ select(#jid{luser = LUser, lserver = LServer} = JidRequestor, fun(Msg) -> {Msg#archive_msg.id, jlib:binary_to_integer(Msg#archive_msg.id), - msg_to_el(Msg, JidRequestor)} + msg_to_el(Msg, MsgType, JidRequestor)} end, FilteredMsgs), IsComplete, Count}; -select(#jid{luser = LUser, lserver = LServer} = JidRequestor, - Start, End, With, RSM, {odbc, Host}) -> - {Query, CountQuery} = make_sql_query(LUser, LServer, - Start, End, With, RSM), +select(LServer, #jid{luser = LUser} = JidRequestor, + Start, End, With, RSM, MsgType, {odbc, Host}) -> + User = case MsgType of + chat -> LUser; + {groupchat, _Role} -> jlib:jid_to_string(JidRequestor) + end, + {Query, CountQuery} = make_sql_query(User, LServer, + Start, End, With, RSM), % XXX TODO from XEP-0313: % To conserve resources, a server MAY place a reasonable limit on % how many stanzas may be pushed to a client in one request. If a @@ -573,24 +608,31 @@ select(#jid{luser = LUser, lserver = LServer} = JidRequestor, {Res, true} end, {lists:map( - fun([TS, XML, PeerBin]) -> + fun([TS, XML, PeerBin, Kind, Nick]) -> #xmlel{} = El = xml_stream:parse_element(XML), Now = usec_to_now(jlib:binary_to_integer(TS)), PeerJid = jlib:jid_tolower(jlib:string_to_jid(PeerBin)), + T = if Kind /= <<"">> -> + jlib:binary_to_atom(Kind); + true -> chat + end, {TS, jlib:binary_to_integer(TS), msg_to_el(#archive_msg{timestamp = Now, packet = El, + type = T, + nick = Nick, peer = PeerJid}, + MsgType, JidRequestor)} end, Res1), IsComplete, jlib:binary_to_integer(Count)}; _ -> {[], false, 0} end. -msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, peer = Peer}, - JidRequestor) -> +msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, nick = Nick, peer = Peer}, + MsgType, JidRequestor) -> Delay = jlib:now_to_utc_string(TS), - Pkt = maybe_update_from_to(Pkt1, JidRequestor, Peer), + Pkt = maybe_update_from_to(Pkt1, JidRequestor, Peer, MsgType, Nick), #xmlel{name = <<"forwarded">>, attrs = [{<<"xmlns">>, ?NS_FORWARD}], children = [#xmlel{name = <<"delay">>, @@ -599,9 +641,9 @@ msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, peer = Peer}, xml:replace_tag_attr( <<"xmlns">>, <<"jabber:client">>, Pkt)]}. -maybe_update_from_to(Pkt, _JIDRequestor, undefined) -> +maybe_update_from_to(Pkt, _JIDRequestor, undefined, _Type, _Nick) -> Pkt; -maybe_update_from_to(Pkt, JidRequestor, Peer) -> +maybe_update_from_to(Pkt, JidRequestor, Peer, chat, _Nick) -> case xml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of <<"groupchat">> -> Pkt2 = xml:replace_tag_attr(<<"to">>, @@ -610,7 +652,23 @@ maybe_update_from_to(Pkt, JidRequestor, Peer) -> xml:replace_tag_attr(<<"from">>, jlib:jid_to_string(Peer), Pkt2); _ -> Pkt - end. + end; +maybe_update_from_to(#xmlel{children = Els} = Pkt, JidRequestor, + Peer, {groupchat, Role}, Nick) -> + Items = case Role of + moderator -> + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_MUC_ADMIN}], + children = + [#xmlel{name = <<"item">>, + attrs = [{<<"jid">>, + jlib:jid_to_string(Peer)}]}]}]; + _ -> + [] + end, + Pkt1 = Pkt#xmlel{children = Items ++ Els}, + Pkt2 = jlib:replace_from(jlib:jid_replace_resource(JidRequestor, Nick), Pkt1), + jlib:remove_attr(<<"to">>, Pkt2). send(From, To, Msgs, RSM, Count, IsComplete, #iq{sub_el = SubEl} = IQ) -> QID = xml:get_tag_attr_s(<<"queryid">>, SubEl), @@ -737,7 +795,7 @@ make_matchspec(LUser, LServer, Start, End, none) -> Msg end). -make_sql_query(LUser, _LServer, Start, End, With, RSM) -> +make_sql_query(User, _LServer, Start, End, With, RSM) -> {Max, Direction, ID} = case RSM of #rsm_in{} -> {RSM#rsm_in.max, @@ -795,9 +853,9 @@ make_sql_query(LUser, _LServer, Start, End, With, RSM) -> _ -> [] end, - SUser = ejabberd_odbc:escape(LUser), + SUser = ejabberd_odbc:escape(User), - Query = [<<"SELECT timestamp, xml, peer" + Query = [<<"SELECT timestamp, xml, peer, kind, nick" " FROM archive WHERE username='">>, SUser, <<"'">>, WithClause, StartClause, EndClause, PageClause], @@ -808,7 +866,7 @@ make_sql_query(LUser, _LServer, Start, End, With, RSM) -> % ID can be empty because of % XEP-0059: Result Set Management % 2.5 Requesting the Last Page in a Result Set - [<<"SELECT timestamp, xml, peer FROM (">>, Query, + [<<"SELECT timestamp, xml, peer, kind, nick FROM (">>, Query, <<" ORDER BY timestamp DESC ">>, LimitClause, <<") AS t ORDER BY timestamp ASC;">>]; _ -> diff --git a/src/mod_muc.erl b/src/mod_muc.erl index e798790c5..725051e66 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -345,6 +345,7 @@ init([Host, Opts]) -> persistent -> Bool; public -> Bool; public_list -> Bool; + mam -> Bool; password -> fun iolist_to_binary/1; title -> fun iolist_to_binary/1; allow_private_messages_from_visitors -> @@ -524,7 +525,8 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper, attrs = [{<<"xmlns">>, XMLNS}], children = - iq_disco_info(Lang) ++ + iq_disco_info( + ServerHost, Lang) ++ Info}]}, ejabberd_router:route(To, From, jlib:iq_to_xml(Res)); @@ -767,7 +769,7 @@ register_room(Host, Room, Pid) -> mnesia:transaction(F). -iq_disco_info(Lang) -> +iq_disco_info(ServerHost, Lang) -> [#xmlel{name = <<"identity">>, attrs = [{<<"category">>, <<"conference">>}, @@ -788,7 +790,14 @@ iq_disco_info(Lang) -> #xmlel{name = <<"feature">>, attrs = [{<<"var">>, ?NS_RSM}], children = []}, #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_VCARD}], children = []}]. + attrs = [{<<"var">>, ?NS_VCARD}], children = []}] ++ + case gen_mod:is_loaded(ServerHost, mod_mam) of + true -> + [#xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_MAM_0}]}]; + false -> + [] + end. iq_disco_items(Host, From, Lang, none) -> lists:zf(fun (#muc_online_room{name_host = diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index 7b8d3e991..c1bbbe5c9 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -34,6 +34,7 @@ start_link/7, start/9, start/7, + get_role/2, route/4]). %% gen_fsm callbacks @@ -426,56 +427,67 @@ normal_state({route, From, <<"">>, #xmlel{name = <<"iq">>} = Packet}, StateData) -> case jlib:iq_query_info(Packet) of - #iq{type = Type, xmlns = XMLNS, lang = Lang, - sub_el = #xmlel{name = SubElName} = SubEl} = - IQ - when (XMLNS == (?NS_MUC_ADMIN)) or - (XMLNS == (?NS_MUC_OWNER)) - or (XMLNS == (?NS_DISCO_INFO)) - or (XMLNS == (?NS_DISCO_ITEMS)) - or (XMLNS == (?NS_VCARD)) - or (XMLNS == (?NS_CAPTCHA)) -> - Res1 = case XMLNS of - ?NS_MUC_ADMIN -> - process_iq_admin(From, Type, Lang, SubEl, StateData); - ?NS_MUC_OWNER -> - process_iq_owner(From, Type, Lang, SubEl, StateData); - ?NS_DISCO_INFO -> - process_iq_disco_info(From, Type, Lang, StateData); - ?NS_DISCO_ITEMS -> - process_iq_disco_items(From, Type, Lang, StateData); - ?NS_VCARD -> - process_iq_vcard(From, Type, Lang, SubEl, StateData); - ?NS_CAPTCHA -> - process_iq_captcha(From, Type, Lang, SubEl, StateData) - end, - {IQRes, NewStateData} = case Res1 of - {result, Res, SD} -> - {IQ#iq{type = result, - sub_el = - [#xmlel{name = SubElName, - attrs = - [{<<"xmlns">>, - XMLNS}], - children = Res}]}, - SD}; - {error, Error} -> - {IQ#iq{type = error, - sub_el = [SubEl, Error]}, - StateData} - end, - ejabberd_router:route(StateData#state.jid, From, - jlib:iq_to_xml(IQRes)), - case NewStateData of - stop -> {stop, normal, StateData}; - _ -> {next_state, normal_state, NewStateData} - end; - reply -> {next_state, normal_state, StateData}; - _ -> - Err = jlib:make_error_reply(Packet, - ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(StateData#state.jid, From, Err), - {next_state, normal_state, StateData} + reply -> + {next_state, normal_state, StateData}; + IQ0 -> + case ejabberd_hooks:run_fold( + muc_process_iq, + StateData#state.server_host, + IQ0, [StateData, From, StateData#state.jid]) of + ignore -> + {next_state, normal_state, StateData}; + #iq{type = T} = IQRes when T == error; T == result -> + ejabberd_router:route(StateData#state.jid, From, jlib:iq_to_xml(IQRes)), + {next_state, normal_state, StateData}; + #iq{type = Type, xmlns = XMLNS, lang = Lang, + sub_el = #xmlel{name = SubElName} = SubEl} = IQ + when (XMLNS == (?NS_MUC_ADMIN)) or + (XMLNS == (?NS_MUC_OWNER)) + or (XMLNS == (?NS_DISCO_INFO)) + or (XMLNS == (?NS_DISCO_ITEMS)) + or (XMLNS == (?NS_VCARD)) + or (XMLNS == (?NS_CAPTCHA)) -> + Res1 = case XMLNS of + ?NS_MUC_ADMIN -> + process_iq_admin(From, Type, Lang, SubEl, StateData); + ?NS_MUC_OWNER -> + process_iq_owner(From, Type, Lang, SubEl, StateData); + ?NS_DISCO_INFO -> + process_iq_disco_info(From, Type, Lang, StateData); + ?NS_DISCO_ITEMS -> + process_iq_disco_items(From, Type, Lang, StateData); + ?NS_VCARD -> + process_iq_vcard(From, Type, Lang, SubEl, StateData); + ?NS_CAPTCHA -> + process_iq_captcha(From, Type, Lang, SubEl, StateData) + end, + {IQRes, NewStateData} = + case Res1 of + {result, Res, SD} -> + {IQ#iq{type = result, + sub_el = + [#xmlel{name = SubElName, + attrs = + [{<<"xmlns">>, + XMLNS}], + children = Res}]}, + SD}; + {error, Error} -> + {IQ#iq{type = error, + sub_el = [SubEl, Error]}, + StateData} + end, + ejabberd_router:route(StateData#state.jid, From, jlib:iq_to_xml(IQRes)), + case NewStateData of + stop -> {stop, normal, StateData}; + _ -> {next_state, normal_state, NewStateData} + end; + _ -> + Err = jlib:make_error_reply(Packet, + ?ERR_FEATURE_NOT_IMPLEMENTED), + ejabberd_router:route(StateData#state.jid, From, Err), + {next_state, normal_state, StateData} + end end; normal_state({route, From, Nick, #xmlel{name = <<"presence">>} = Packet}, @@ -962,11 +974,11 @@ process_groupchat_message(From, FromNick), StateData#state.server_host, StateData#state.users, - Packet), + NewPacket), NewStateData2 = case has_body_or_subject(Packet) of true -> add_message_to_history(FromNick, From, - Packet, + NewPacket, NewStateData1); false -> NewStateData1 @@ -3531,6 +3543,13 @@ get_config(Lang, StateData, From) -> <<"captcha_protected">>, (Config#config.captcha_protected))]; false -> [] + end ++ + case gen_mod:is_loaded(StateData#state.server_host, mod_mam) of + true -> + [?BOOLXFIELD(<<"Enable message archiving">>, + <<"muc#roomconfig_mam">>, + (Config#config.mam))]; + false -> [] end ++ [?JIDMULTIXFIELD(<<"Exclude Jabber IDs from CAPTCHA challenge">>, @@ -3740,6 +3759,8 @@ set_xoption([{<<"muc#roomconfig_enablelogging">>, [Val]} | Opts], Config) -> ?SET_BOOL_XOPT(logging, Val); +set_xoption([{<<"muc#roomconfig_mam">>, [Val]}|Opts], Config) -> + ?SET_BOOL_XOPT(mam, Val); set_xoption([{<<"muc#roomconfig_captcha_whitelist">>, Vals} | Opts], @@ -3902,6 +3923,9 @@ set_opts([{Opt, Val} | Opts], StateData) -> StateData#state{config = (StateData#state.config)#config{logging = Val}}; + mam -> + StateData#state{config = + (StateData#state.config)#config{mam = Val}}; captcha_whitelist -> StateData#state{config = (StateData#state.config)#config{captcha_whitelist