From 35d25d3420c267b52c33b6d46956c8e05920b17e Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Thu, 3 Jul 2008 09:56:31 +0000 Subject: [PATCH] PubSub improvements, and solves (EJAB-453) (EJAB-608) SVN Revision: 1408 --- ChangeLog | 32 ++++++ src/ejabberd_c2s.erl | 3 + src/ejabberd_local.erl | 7 ++ src/ejabberd_receiver.erl | 6 +- src/ejabberd_sm.erl | 15 ++- src/mod_caps.erl | 12 ++ src/mod_pubsub/gen_pubsub_node.erl | 2 + src/mod_pubsub/mod_pubsub.erl | 102 ++++++++++++----- src/mod_pubsub/node.template | 9 +- src/mod_pubsub/node_buddy.erl | 8 ++ src/mod_pubsub/node_club.erl | 9 +- src/mod_pubsub/node_default.erl | 116 +++++++++++++++---- src/mod_pubsub/node_dispatch.erl | 9 +- src/mod_pubsub/node_pep.erl | 11 +- src/mod_pubsub/node_private.erl | 9 +- src/mod_pubsub/node_public.erl | 9 +- src/mod_pubsub/node_zoo.erl | 177 +++++++++++++++++++++++++++++ src/web/ejabberd_http.erl | 14 ++- src/web/ejabberd_http_poll.erl | 40 +++++-- 19 files changed, 516 insertions(+), 74 deletions(-) create mode 100644 src/mod_pubsub/node_zoo.erl diff --git a/ChangeLog b/ChangeLog index 5226fe079..0d8ee14ad 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,35 @@ +2008-07-03 Christophe Romain + + * src/mod_pubsub/mod_pubsub.erl: Fix permission control on item + retrieve (EJAB-453) + * src/mod_pubsub/node_dispatch.erl: Likewise + * src/mod_pubsub/node_buddy.erl: Likewise + * src/mod_pubsub/node_private.erl: Likewise + * src/mod_pubsub/node_public.erl: Likewise + * src/mod_pubsub/node_default.erl: Likewise + * src/mod_pubsub/node_pep.erl: Likewise + * src/mod_pubsub/node_club.erl: Likewise + * src/mod_pubsub/gen_pubsub_node.erl: Likewise + * src/mod_pubsub/node.template: Likewise + + * src/mod_pubsub/mod_pubsub.erl: Allow subscriber to request specific + items by ItemID; Allow to retrieve pubsub#title configuration (thanks + to Kevin Crosbie); Fix forbidden result on setting + affiliation/subscription + + * src/mod_pubsub/node_zoo.erl: Add node type without + home// constraint + + * src/ejabberd_local.erl: prevent iq_response table overload + (EJAB-608) + * src/mod_caps.erl: Likewise + + * src/web/ejabberd_http.erl: Retrieve correct IP from http connection + * src/web/ejabberd_http_poll.erl: Likewise + * src/ejabberd_receiver.erl: Likewise + * src/ejabberd_sm.erl: Likewise + * src/ejabberd_c2s.erl: Likewise + 2008-06-29 Badlop * src/ejabberd_ctl.erl: Web Admin and Ad-hoc admin: dump only diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 358b1fe32..5afd6cc53 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -1267,6 +1267,9 @@ handle_info({route, From, To, Packet}, StateName, StateData) -> handle_info({'DOWN', Monitor, _Type, _Object, _Info}, _StateName, StateData) when Monitor == StateData#state.socket_monitor -> {stop, normal, StateData}; +handle_info({peername, IP}, StateName, StateData) -> + ejabberd_sm:set_session_ip(StateData#state.sid, IP), + fsm_next_state(StateName, StateData#state{ip = IP}); handle_info(Info, StateName, StateData) -> ?ERROR_MSG("Unexpected info: ~p", [Info]), fsm_next_state(StateName, StateData). diff --git a/src/ejabberd_local.erl b/src/ejabberd_local.erl index b54173581..67f5b9636 100644 --- a/src/ejabberd_local.erl +++ b/src/ejabberd_local.erl @@ -37,6 +37,7 @@ register_iq_handler/5, register_iq_response_handler/4, unregister_iq_handler/2, + unregister_iq_response_handler/2, refresh_iq_handlers/0, bounce_resource_packet/3 ]). @@ -138,6 +139,9 @@ register_iq_handler(Host, XMLNS, Module, Fun) -> register_iq_handler(Host, XMLNS, Module, Fun, Opts) -> ejabberd_local ! {register_iq_handler, Host, XMLNS, Module, Fun, Opts}. +unregister_iq_response_handler(Host, ID) -> + ejabberd_local ! {unregister_iq_response_handler, Host, ID}. + unregister_iq_handler(Host, XMLNS) -> ejabberd_local ! {unregister_iq_handler, Host, XMLNS}. @@ -214,6 +218,9 @@ handle_info({route, From, To, Packet}, State) -> handle_info({register_iq_response_handler, _Host, ID, Module, Function}, State) -> mnesia:dirty_write(#iq_response{id = ID, module = Module, function = Function}), {noreply, State}; +handle_info({unregister_iq_response_handler, _Host, ID}, State) -> + mnesia:dirty_delete({iq_response, ID}), + {noreply, State}; handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) -> ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function}), catch mod_disco:register_feature(Host, XMLNS), diff --git a/src/ejabberd_receiver.erl b/src/ejabberd_receiver.erl index 72b3608d6..c178df62a 100644 --- a/src/ejabberd_receiver.erl +++ b/src/ejabberd_receiver.erl @@ -269,7 +269,8 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- activate_socket(#state{socket = Socket, - sock_mod = SockMod}) -> + sock_mod = SockMod, + c2s_pid = C2SPid}) -> PeerName = case SockMod of gen_tcp -> @@ -282,7 +283,8 @@ activate_socket(#state{socket = Socket, case PeerName of {error, _Reason} -> self() ! {tcp_closed, Socket}; - {ok, _} -> + {ok, IP} -> + C2SPid ! {peername, IP}, ok end. diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index 2d4b7c5b9..bb543707f 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -49,7 +49,8 @@ ctl_process/2, get_session_pid/3, get_user_info/3, - get_user_ip/3 + get_user_ip/3, + set_session_ip/2 ]). %% gen_server callbacks @@ -164,6 +165,18 @@ get_user_info(User, Server, Resource) -> [{node, Node}, {conn, Conn}, {ip, IP}] end. +set_session_ip(SID, IP) -> + case mnesia:dirty_read(session, SID) of + [#session{info = Info} = Session] -> + NewInfo = case lists:keymember(ip, 1, Info) of + true -> lists:keyreplace(ip, 1, Info, {ip, IP}); + false -> [{ip, IP}|Info] + end, + mnesia:dirty_write(Session#session{info = NewInfo}); + _ -> + ok + end. + set_presence(SID, User, Server, Resource, Priority, Presence, Info) -> set_session(SID, User, Server, Resource, Priority, Info), ejabberd_hooks:run(set_presence_hook, jlib:nameprep(Server), diff --git a/src/mod_caps.erl b/src/mod_caps.erl index 2be0fac66..3240aa6e2 100644 --- a/src/mod_caps.erl +++ b/src/mod_caps.erl @@ -53,6 +53,7 @@ -define(PROCNAME, ejabberd_mod_caps). -define(DICT, dict). +-define(CAPS_QUERY_TIMEOUT, 60000). % 1mn without answer, consider client never answer -record(caps, {node, version, exts}). -record(caps_features, {node_pair, features}). @@ -220,6 +221,7 @@ handle_cast({note_caps, From, ejabberd_local:register_iq_response_handler (Host, ID, ?MODULE, handle_disco_response), ejabberd_router:route(jlib:make_jid("", Host, ""), From, Stanza), + timer:send_after(?CAPS_QUERY_TIMEOUT, self(), {disco_timeout, ID}), ?DICT:store(ID, {Node, SubNode}, Dict) end, Requests, Missing), {noreply, State#state{disco_requests = NewRequests}}; @@ -274,6 +276,16 @@ handle_cast({disco_response, From, _To, end, NewRequests = ?DICT:erase(ID, Requests), {noreply, State#state{disco_requests = NewRequests}}; +handle_cast({disco_timeout, ID}, #state{host = Host, disco_requests = Requests} = State) -> + %% do not wait a response anymore for this IQ, client certainly will never answer + NewRequests = case ?DICT:is_key(ID, Requests) of + true -> + ejabberd_local:unregister_iq_response_handler(Host, ID), + ?DICT:erase(ID, Requests); + false -> + Requests + end, + {noreply, State#state{disco_requests = NewRequests}}; handle_cast(visit_feature_queries, #state{feature_queries = FeatureQueries} = State) -> Timestamp = timestamp(), NewFeatureQueries = diff --git a/src/mod_pubsub/gen_pubsub_node.erl b/src/mod_pubsub/gen_pubsub_node.erl index 195ed3934..bbbb5c409 100644 --- a/src/mod_pubsub/gen_pubsub_node.erl +++ b/src/mod_pubsub/gen_pubsub_node.erl @@ -62,7 +62,9 @@ behaviour_info(callbacks) -> {get_states, 2}, {get_state, 3}, {set_state, 1}, + {get_items, 7}, {get_items, 2}, + {get_item, 8}, {get_item, 3}, {set_item, 1}, {get_item_name, 3} diff --git a/src/mod_pubsub/mod_pubsub.erl b/src/mod_pubsub/mod_pubsub.erl index 349c7ad4f..92f5cf959 100644 --- a/src/mod_pubsub/mod_pubsub.erl +++ b/src/mod_pubsub/mod_pubsub.erl @@ -30,16 +30,17 @@ %%% %%% @reference See XEP-0060: Pubsub for %%% the latest version of the PubSub specification. -%%% This module uses version 1.10 of the specification as a base. +%%% This module uses version 1.11 of the specification as a base. %%% Most of the specification is implemented. -%%% Code is derivated from the original pubsub v1.7, functions concerning config may be rewritten. -%%% Code also inspired from the original PEP patch by Magnus Henoch (mangeATfreemail.hu) +%%% Functions concerning configuration should be rewritten. +%%% Code is derivated from the original pubsub v1.7, by Alexey Shchepin %%% TODO %%% plugin: generate Reply (do not use broadcast atom anymore) -module(mod_pubsub). --version('1.10-01'). +-author('christophe.romain@process-one.net'). +-version('1.11-01'). -behaviour(gen_server). -behaviour(gen_mod). @@ -912,7 +913,17 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, _Lang, Access, Plugins) -> unsubscribe_node(Host, Node, From, JID, SubId); {get, "items"} -> MaxItems = xml:get_attr_s("max_items", Attrs), - get_items(Host, Node, From, MaxItems); + SubId = xml:get_attr_s("subid", Attrs), + ItemIDs = lists:foldl(fun + ({xmlelement, "item", ItemAttrs, _}, Acc) -> + case xml:get_attr_s("id", ItemAttrs) of + "" -> Acc; + ItemID -> ItemID + end; + (_, Acc) -> + Acc + end, [], xml:remove_cdata(Els)), + get_items(Host, Node, From, SubId, MaxItems, ItemIDs); {get, "subscriptions"} -> get_subscriptions(Host, From, Plugins); {get, "affiliations"} -> @@ -1436,7 +1447,7 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) -> Action = fun(#pubsub_node{options = Options, type = Type}) -> Features = features(Type), PublishFeature = lists:member("publish", Features), - Model = get_option(Options, publish_model), + PublishModel = get_option(Options, publish_model), MaxItems = max_items(Options), PayloadSize = size(term_to_binary(Payload)), PayloadMaxSize = get_option(Options, max_payload_size), @@ -1459,7 +1470,7 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) -> %% % Publisher attempts to publish to transient notification node with item %% {error, extended_error(?ERR_BAD_REQUEST, "item-forbidden")}; true -> - node_call(Type, publish_item, [Host, Node, Publisher, Model, MaxItems, ItemId, Payload]) + node_call(Type, publish_item, [Host, Node, Publisher, PublishModel, MaxItems, ItemId, Payload]) end end, %%ejabberd_hooks:run(pubsub_publish_item, Host, [Host, Node, JID, service_jid(Host), ItemId, Payload]), @@ -1622,7 +1633,7 @@ purge_node(Host, Node, Owner) -> %%

The permission are not checked in this function.

%% @todo We probably need to check that the user doing the query has the right %% to read the items. -get_items(Host, Node, _JID, SMaxItems) -> +get_items(Host, Node, From, SubId, SMaxItems, ItemIDs) -> MaxItems = if SMaxItems == "" -> ?MAXITEMS; @@ -1636,24 +1647,60 @@ get_items(Host, Node, _JID, SMaxItems) -> {error, Error} -> {error, Error}; _ -> - case get_items(Host, Node) of - [] -> - {error, ?ERR_ITEM_NOT_FOUND}; - Items -> + Action = fun(#pubsub_node{options = Options, type = Type}) -> + Features = features(Type), + RetreiveFeature = lists:member("retrieve-items", Features), + PersistentFeature = lists:member("persistent-items", Features), + AccessModel = get_option(Options, access_model), + AllowedGroups = get_option(Options, roster_groups_allowed), + {PresenceSubscription, RosterGroup} = + case Host of + {OUser, OServer, _} -> + get_roster_info(OUser, OServer, + jlib:jid_tolower(From), AllowedGroups); + _ -> + {true, true} + end, + if + not RetreiveFeature -> + %% Item Retrieval Not Supported + {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "retrieve-items")}; + not PersistentFeature -> + %% Persistent Items Not Supported + {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "persistent-items")}; + true -> + node_call(Type, get_items, + [Host, Node, From, + AccessModel, PresenceSubscription, RosterGroup, + SubId]) + end + end, + case transaction(Host, Node, Action, sync_dirty) of + {error, Reason} -> + {error, Reason}; + {result, Items} -> + SendItems = case ItemIDs of + [] -> + Items; + _ -> + lists:filter(fun(Item) -> + lists:member(Item, ItemIDs) + end, Items) + end, %% Generate the XML response (Item list), limiting the %% number of items sent to MaxItems: ItemsEls = lists:map( - fun(#pubsub_item{itemid = {ItemId, _}, - payload = Payload}) -> - ItemAttrs = case ItemId of - "" -> []; - _ -> [{"id", ItemId}] - end, - {xmlelement, "item", ItemAttrs, Payload} - end, lists:sublist(Items, MaxItems)), + fun(#pubsub_item{itemid = {ItemId, _}, + payload = Payload}) -> + ItemAttrs = case ItemId of + "" -> []; + _ -> [{"id", ItemId}] + end, + {xmlelement, "item", ItemAttrs, Payload} + end, lists:sublist(SendItems, MaxItems)), {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], - [{xmlelement, "items", [{"node", node_to_string(Node)}], - ItemsEls}]}]} + [{xmlelement, "items", [{"node", node_to_string(Node)}], + ItemsEls}]}]} end end. @@ -1809,7 +1856,7 @@ set_affiliations(Host, Node, From, EntitiesEls) -> end, Entities), {result, []}; _ -> - {error, ?ERR_NOT_ALLOWED} + {error, ?ERR_FORBIDDEN} end end, transaction(Host, Node, Action, sync_dirty) @@ -1870,7 +1917,7 @@ get_subscriptions(Host, Node, JID) -> %% Service does not support manage subscriptions {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "manage-affiliations")}; Affiliation /= {result, owner} -> - % Entity is not an owner + %% Entity is not an owner {error, ?ERR_FORBIDDEN}; true -> node_call(Type, get_node_subscriptions, [Host, Node]) @@ -1938,7 +1985,7 @@ set_subscriptions(Host, Node, From, EntitiesEls) -> end, Entities), {result, []}; _ -> - {error, ?ERR_NOT_ALLOWED} + {error, ?ERR_FORBIDDEN} end end, transaction(Host, Node, Action, sync_dirty) @@ -2438,6 +2485,7 @@ get_configure_xfields(_Type, Options, Lang, _Owners) -> ?BOOL_CONFIG_FIELD("Notify subscribers when the node is deleted", notify_delete), ?BOOL_CONFIG_FIELD("Notify subscribers when items are removed from the node", notify_retract), ?BOOL_CONFIG_FIELD("Persist items to storage", persist_items), + ?STRING_CONFIG_FIELD("A friendly name for the node", title), ?INTEGER_CONFIG_FIELD("Max # of items to persist", max_items), ?BOOL_CONFIG_FIELD("Whether to allow subscriptions", subscribe), ?ALIST_CONFIG_FIELD("Specify the access model", access_model, @@ -2605,7 +2653,7 @@ features() -> [ %"access-authorize", % OPTIONAL "access-open", % OPTIONAL this relates to access_model option in node_default - %"access-presence", % OPTIONAL + "access-presence", % OPTIONAL this relates to access_model option in node_pep %"access-roster", % OPTIONAL %"access-whitelist", % OPTIONAL % see plugin "auto-create", % OPTIONAL @@ -2619,7 +2667,7 @@ features() -> % see plugin "filtered-notifications", % RECOMMENDED %TODO "get-pending", % OPTIONAL % see plugin "instant-nodes", % RECOMMENDED - %TODO "item-ids", % RECOMMENDED + "item-ids", % RECOMMENDED "last-published", % RECOMMENDED %TODO "cache-last-item", %TODO "leased-subscription", % OPTIONAL diff --git a/src/mod_pubsub/node.template b/src/mod_pubsub/node.template index 850a38ec9..973aa38a4 100644 --- a/src/mod_pubsub/node.template +++ b/src/mod_pubsub/node.template @@ -61,7 +61,9 @@ get_states/2, get_state/3, set_state/1, + get_items/7, get_items/2, + get_item/8, get_item/3, set_item/1 ]). @@ -94,7 +96,6 @@ features() -> ["create-nodes", "delete-nodes", "instant-nodes", - "item-ids", "outcast-affiliation", "persistent-items", "publish", @@ -170,8 +171,14 @@ set_state(State) -> get_items(Host, Node) -> node_default:get_items(Host, Node). +get_items(Host, Node, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) + node_default:get_items(Host, Node, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). + get_item(Host, Node, ItemId) -> node_default:get_item(Host, Node, ItemId). +get_item(Host, Node, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> + node_default:get_item(Host, Node, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). + set_item(Item) -> node_default:set_item(Item). diff --git a/src/mod_pubsub/node_buddy.erl b/src/mod_pubsub/node_buddy.erl index cdf3a696f..cd051b66f 100644 --- a/src/mod_pubsub/node_buddy.erl +++ b/src/mod_pubsub/node_buddy.erl @@ -62,7 +62,9 @@ get_states/2, get_state/3, set_state/1, + get_items/7, get_items/2, + get_item/8, get_item/3, set_item/1, get_item_name/3 @@ -172,9 +174,15 @@ set_state(State) -> get_items(Host, Node) -> node_default:get_items(Host, Node). +get_items(Host, Node, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> + node_default:get_items(Host, Node, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). + get_item(Host, Node, ItemId) -> node_default:get_item(Host, Node, ItemId). +get_item(Host, Node, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> + node_default:get_item(Host, Node, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). + set_item(Item) -> node_default:set_item(Item). diff --git a/src/mod_pubsub/node_club.erl b/src/mod_pubsub/node_club.erl index d927053fb..155edc10a 100644 --- a/src/mod_pubsub/node_club.erl +++ b/src/mod_pubsub/node_club.erl @@ -62,7 +62,9 @@ get_states/2, get_state/3, set_state/1, + get_items/7, get_items/2, + get_item/8, get_item/3, set_item/1, get_item_name/3 @@ -96,7 +98,6 @@ features() -> ["create-nodes", "delete-nodes", "instant-nodes", - "item-ids", "outcast-affiliation", "persistent-items", "publish", @@ -172,9 +173,15 @@ set_state(State) -> get_items(Host, Node) -> node_default:get_items(Host, Node). +get_items(Host, Node, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> + node_default:get_items(Host, Node, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). + get_item(Host, Node, ItemId) -> node_default:get_item(Host, Node, ItemId). +get_item(Host, Node, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> + node_default:get_item(Host, Node, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). + set_item(Item) -> node_default:set_item(Item). diff --git a/src/mod_pubsub/node_default.erl b/src/mod_pubsub/node_default.erl index fc4194c32..491676269 100644 --- a/src/mod_pubsub/node_default.erl +++ b/src/mod_pubsub/node_default.erl @@ -69,7 +69,9 @@ get_states/2, get_state/3, set_state/1, + get_items/7, get_items/2, + get_item/8, get_item/3, set_item/1, get_item_name/3 @@ -158,7 +160,6 @@ features() -> "auto-create", "delete-nodes", "instant-nodes", - "item-ids", "manage-subscriptions", "modify-affiliations", "outcast-affiliation", @@ -192,18 +193,14 @@ features() -> %% module by implementing this function like this: %% ```check_create_user_permission(Host, Node, Owner, Access) -> %% node_default:check_create_user_permission(Host, Node, Owner, Access).'''

-create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) -> +create_node_permission(_Host, ServerHost, Node, _ParentNode, Owner, Access) -> LOwner = jlib:jid_tolower(Owner), {User, Server, _Resource} = LOwner, Allowed = case acl:match_rule(ServerHost, Access, LOwner) of allow -> - if Server == Host -> %% Server == ServerHost ?? - true; - true -> - case Node of - ["home", Server, User | _] -> true; - _ -> false - end + case Node of + ["home", Server, User | _] -> true; + _ -> false end; _ -> case Owner of @@ -211,8 +208,7 @@ create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) -> _ -> false end end, - ChildOK = true, %% TODO test with ParentNode - {result, Allowed and ChildOK}. + {result, Allowed}. %% @spec (Host, Node, Owner) -> %% {result, Result} | exit @@ -297,12 +293,12 @@ subscribe_node(Host, Node, Sender, Subscriber, AccessModel, not Authorized -> %% JIDs do not match {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "invalid-jid")}; - Subscription == pending -> - %% Requesting entity has pending subscription - {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "pending-subscription")}; Affiliation == outcast -> %% Requesting entity is blocked {error, ?ERR_FORBIDDEN}; + Subscription == pending -> + %% Requesting entity has pending subscription + {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "pending-subscription")}; (AccessModel == presence) and (not PresenceSubscription) -> %% Entity is not authorized to create a subscription (presence subscription required) {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")}; @@ -446,6 +442,7 @@ publish_item(Host, Node, Publisher, PublishModel, MaxItems, ItemId, Payload) -> {error, ?ERR_FORBIDDEN}; true -> PubId = {PublisherKey, now()}, + %% TODO: check creation, presence, roster (EJAB-663) Item = case get_item(Host, Node, ItemId) of {error, ?ERR_ITEM_NOT_FOUND} -> #pubsub_item{itemid = {ItemId, {Host, Node}}, @@ -501,7 +498,7 @@ remove_extra_items(Host, Node, MaxItems, ItemIds) -> %% ItemId = string() %% @doc

Triggers item deletion.

%%

Default plugin: The user performing the deletion must be the node owner -%% or a node publisher e item publisher.

+%% or a publisher.

delete_item(Host, Node, Publisher, ItemId) -> PublisherKey = jlib:jid_tolower(jlib:jid_remove_resource(Publisher)), State = case get_state(Host, Node, PublisherKey) of @@ -542,17 +539,16 @@ delete_item(Host, Node, Publisher, ItemId) -> purge_node(Host, Node, Owner) -> OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), case get_state(Host, Node, OwnerKey) of - {error, ?ERR_ITEM_NOT_FOUND} -> - %% This should not append (case node does not exists) - {error, ?ERR_ITEM_NOT_FOUND}; {result, #pubsub_state{items = Items, affiliation = owner}} -> lists:foreach(fun(ItemId) -> mnesia:delete({pubsub_item, {ItemId, {Host, Node}}}) end, Items), {result, {default, broadcast}}; + {result, _} -> + %% Entity is not owner + {error, ?ERR_FORBIDDEN}; _ -> - %% Entity is not an owner - {error, ?ERR_FORBIDDEN} + {error, ?ERR_ITEM_NOT_FOUND} end. %% @spec (Host, JID) -> [{Node,Affiliation}] @@ -588,7 +584,7 @@ get_affiliation(Host, Node, Owner) -> OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), Affiliation = case get_state(Host, Node, OwnerKey) of {result, #pubsub_state{affiliation = A}} -> A; - _ -> unknown + _ -> none end, {result, Affiliation}. @@ -638,7 +634,7 @@ get_subscription(Host, Node, Owner) -> OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), Subscription = case get_state(Host, Node, OwnerKey) of {result, #pubsub_state{subscription = S}} -> S; - _ -> unknown + _ -> none end, {result, Subscription}. @@ -713,6 +709,44 @@ get_items(Host, Node) -> Items = mnesia:match_object( #pubsub_item{itemid = {'_', {Host, Node}}, _ = '_'}), {result, Items}. +get_items(Host, Node, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -> + {Affiliation, Subscription} = + case get_state(Host, Node, jlib:jid_tolower(jlib:jid_remove_resource(JID))) of + {result, #pubsub_state{affiliation = A, subscription = S}} -> {A, S}; + _ -> {none, none} + end, + Subscribed = not ((Subscription == none) or (Subscription == pending)), + if + %%SubID == "", ?? -> + %% Entity has multiple subscriptions to the node but does not specify a subscription ID + %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; + %%InvalidSubID -> + %% Entity is subscribed but specifies an invalid subscription ID + %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; + Affiliation == outcast -> + %% Requesting entity is blocked + {error, ?ERR_FORBIDDEN}; + (AccessModel == open) and (not Subscribed) -> + %% Entity is not subscribed + {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-subscribed")}; + (AccessModel == presence) and (not PresenceSubscription) -> + %% Entity is not authorized to create a subscription (presence subscription required) + {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")}; + (AccessModel == roster) and (not RosterGroup) -> + %% Entity is not authorized to create a subscription (not in roster group) + {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")}; + (AccessModel == whitelist) -> % TODO: to be done + %% Node has whitelist access model + {error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")}; + (AccessModel == authorize) -> % TODO: to be done + %% Node has authorize access model + {error, ?ERR_FORBIDDEN}; + %%MustPay -> + %% % Payment is required for a subscription + %% {error, ?ERR_PAYMENT_REQUIRED}; + true -> + get_items(Host, Node) + end. %% @spec (Host, Node, ItemId) -> [Item] | [] %% Host = mod_pubsub:host() @@ -727,6 +761,44 @@ get_item(Host, Node, ItemId) -> _ -> {error, ?ERR_ITEM_NOT_FOUND} end. +get_item(Host, Node, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -> + {Affiliation, Subscription} = + case get_state(Host, Node, jlib:jid_tolower(jlib:jid_remove_resource(JID))) of + {result, #pubsub_state{affiliation = A, subscription = S}} -> {A, S}; + _ -> {none, none} + end, + Subscribed = not ((Subscription == none) or (Subscription == pending)), + if + %%SubID == "", ?? -> + %% Entity has multiple subscriptions to the node but does not specify a subscription ID + %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; + %%InvalidSubID -> + %% Entity is subscribed but specifies an invalid subscription ID + %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; + Affiliation == outcast -> + %% Requesting entity is blocked + {error, ?ERR_FORBIDDEN}; + (AccessModel == open) and (not Subscribed) -> + %% Entity is not subscribed + {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-subscribed")}; + (AccessModel == presence) and (not PresenceSubscription) -> + %% Entity is not authorized to create a subscription (presence subscription required) + {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")}; + (AccessModel == roster) and (not RosterGroup) -> + %% Entity is not authorized to create a subscription (not in roster group) + {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")}; + (AccessModel == whitelist) -> % TODO: to be done + %% Node has whitelist access model + {error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")}; + (AccessModel == authorize) -> % TODO: to be done + %% Node has authorize access model + {error, ?ERR_FORBIDDEN}; + %%MustPay -> + %% % Payment is required for a subscription + %% {error, ?ERR_PAYMENT_REQUIRED}; + true -> + get_item(Host, Node, ItemId) + end. %% @spec (Item) -> ok | {error, Reason::stanzaError()} %% Item = mod_pubsub:pubsubItems() diff --git a/src/mod_pubsub/node_dispatch.erl b/src/mod_pubsub/node_dispatch.erl index 3b4418c3e..0532826d2 100644 --- a/src/mod_pubsub/node_dispatch.erl +++ b/src/mod_pubsub/node_dispatch.erl @@ -60,7 +60,9 @@ get_states/2, get_state/3, set_state/1, + get_items/7, get_items/2, + get_item/8, get_item/3, set_item/1, get_item_name/3 @@ -94,7 +96,6 @@ features() -> ["create-nodes", "delete-nodes", "instant-nodes", - "item-ids", "outcast-affiliation", "persistent-items", "publish", @@ -175,9 +176,15 @@ set_state(State) -> get_items(Host, Node) -> node_default:get_items(Host, Node). +get_items(Host, Node, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> + node_default:get_items(Host, Node, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). + get_item(Host, Node, ItemId) -> node_default:get_item(Host, Node, ItemId). +get_item(Host, Node, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> + node_default:get_item(Host, Node, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). + set_item(Item) -> node_default:set_item(Item). diff --git a/src/mod_pubsub/node_pep.erl b/src/mod_pubsub/node_pep.erl index 2fceb03ed..2c648148d 100644 --- a/src/mod_pubsub/node_pep.erl +++ b/src/mod_pubsub/node_pep.erl @@ -58,7 +58,9 @@ get_states/2, get_state/3, set_state/1, + get_items/7, get_items/2, + get_item/8, get_item/3, set_item/1, get_item_name/3 @@ -96,7 +98,6 @@ features() -> "auto-subscribe", %* "delete-nodes", %* "filtered-notifications", %* - "item-ids", "modify-affiliations", "outcast-affiliation", "persistent-items", @@ -185,7 +186,7 @@ get_node_subscriptions(_Host, _Node) -> {result, []}. get_subscription(_Host, _Node, _Owner) -> - {result, unknown}. + {result, none}. set_subscription(_Host, _Node, _Owner, _Subscription) -> ok. @@ -202,9 +203,15 @@ set_state(State) -> get_items(Host, Node) -> node_default:get_items(Host, Node). +get_items(Host, Node, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> + node_default:get_items(Host, Node, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). + get_item(Host, Node, ItemId) -> node_default:get_item(Host, Node, ItemId). +get_item(Host, Node, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> + node_default:get_item(Host, Node, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). + set_item(Item) -> node_default:set_item(Item). diff --git a/src/mod_pubsub/node_private.erl b/src/mod_pubsub/node_private.erl index 35f63c925..00e952392 100644 --- a/src/mod_pubsub/node_private.erl +++ b/src/mod_pubsub/node_private.erl @@ -62,7 +62,9 @@ get_states/2, get_state/3, set_state/1, + get_items/7, get_items/2, + get_item/8, get_item/3, set_item/1, get_item_name/3 @@ -96,7 +98,6 @@ features() -> ["create-nodes", "delete-nodes", "instant-nodes", - "item-ids", "outcast-affiliation", "persistent-items", "publish", @@ -175,9 +176,15 @@ set_state(State) -> get_items(Host, Node) -> node_default:get_items(Host, Node). +get_items(Host, Node, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> + node_default:get_items(Host, Node, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). + get_item(Host, Node, ItemId) -> node_default:get_item(Host, Node, ItemId). +get_item(Host, Node, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> + node_default:get_item(Host, Node, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). + set_item(Item) -> node_default:set_item(Item). diff --git a/src/mod_pubsub/node_public.erl b/src/mod_pubsub/node_public.erl index 1586abe86..ab4107a76 100644 --- a/src/mod_pubsub/node_public.erl +++ b/src/mod_pubsub/node_public.erl @@ -62,7 +62,9 @@ get_states/2, get_state/3, set_state/1, + get_items/7, get_items/2, + get_item/8, get_item/3, set_item/1, get_item_name/3 @@ -96,7 +98,6 @@ features() -> ["create-nodes", "delete-nodes", "instant-nodes", - "item-ids", "outcast-affiliation", "persistent-items", "publish", @@ -172,9 +173,15 @@ set_state(State) -> get_items(Host, Node) -> node_default:get_items(Host, Node). +get_items(Host, Node, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> + node_default:get_items(Host, Node, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). + get_item(Host, Node, ItemId) -> node_default:get_item(Host, Node, ItemId). +get_item(Host, Node, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> + node_default:get_item(Host, Node, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). + set_item(Item) -> node_default:set_item(Item). diff --git a/src/mod_pubsub/node_zoo.erl b/src/mod_pubsub/node_zoo.erl new file mode 100644 index 000000000..1cb8a73cb --- /dev/null +++ b/src/mod_pubsub/node_zoo.erl @@ -0,0 +1,177 @@ +%%% ==================================================================== +%%% ``The contents of this file are subject to the Erlang Public License, +%%% Version 1.1, (the "License"); you may not use this file except in +%%% compliance with the License. You should have received a copy of the +%%% Erlang Public License along with this software. If not, it can be +%%% retrieved via the world wide web at http://www.erlang.org/. +%%% +%%% Software distributed under the License is distributed on an "AS IS" +%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%%% the License for the specific language governing rights and limitations +%%% under the License. +%%% +%%% The Initial Developer of the Original Code is Process-one. +%%% Portions created by Process-one are Copyright 2006-2008, Process-one +%%% All Rights Reserved.'' +%%% This software is copyright 2006-2008, Process-one. +%%% +%%% @copyright 2006-2008 Process-one +%%% @author Christophe romain +%%% [http://www.process-one.net/] +%%% @version {@vsn}, {@date} {@time} +%%% @end +%%% ==================================================================== + +-module(node_zoo). +-author('christophe.romain@process-one.net'). + +-include("pubsub.hrl"). +-include("jlib.hrl"). + +-behaviour(gen_pubsub_node). + +%% API definition +-export([init/3, terminate/2, + options/0, features/0, + create_node_permission/6, + create_node/3, + delete_node/2, + purge_node/3, + subscribe_node/8, + unsubscribe_node/5, + publish_item/7, + delete_item/4, + remove_extra_items/4, + get_entity_affiliations/2, + get_node_affiliations/2, + get_affiliation/3, + set_affiliation/4, + get_entity_subscriptions/2, + get_node_subscriptions/2, + get_subscription/3, + set_subscription/4, + get_states/2, + get_state/3, + set_state/1, + get_items/7, + get_items/2, + get_item/8, + get_item/3, + set_item/1 + ]). + + +init(Host, ServerHost, Opts) -> + node_default:init(Host, ServerHost, Opts). + +terminate(Host, ServerHost) -> + node_default:terminate(Host, ServerHost). + +options() -> + [{node_type, zoo}, + {deliver_payloads, true}, + {notify_config, false}, + {notify_delete, false}, + {notify_retract, true}, + {persist_items, true}, + {max_items, ?MAXITEMS div 2}, + {subscribe, true}, + {access_model, open}, + {roster_groups_allowed, []}, + {publish_model, publishers}, + {max_payload_size, ?MAX_PAYLOAD_SIZE}, + {send_last_published_item, never}, + {deliver_notifications, true}, + {presence_based_delivery, false}]. + +features() -> + node_default:features(). + +%% use same code as node_default, but do not limite node to +%% the home/localhost/user/... hierarchy +%% any node is allowed +create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> + LOwner = jlib:jid_tolower(Owner), + {User, Server, _Resource} = LOwner, + Allowed = case acl:match_rule(ServerHost, Access, LOwner) of + allow -> + true; + _ -> + case Owner of + {jid, "", _, "", "", _, ""} -> true; + _ -> false + end + end, + {result, Allowed}. + +create_node(Host, Node, Owner) -> + node_default:create_node(Host, Node, Owner). + +delete_node(Host, Removed) -> + node_default:delete_node(Host, Removed). + +subscribe_node(Host, Node, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup) -> + node_default:subscribe_node(Host, Node, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup). + +unsubscribe_node(Host, Node, Sender, Subscriber, SubID) -> + node_default:unsubscribe_node(Host, Node, Sender, Subscriber, SubID). + +publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload) -> + node_default:publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload). + +remove_extra_items(Host, Node, MaxItems, ItemIds) -> + node_default:remove_extra_items(Host, Node, MaxItems, ItemIds). + +delete_item(Host, Node, JID, ItemId) -> + node_default:delete_item(Host, Node, JID, ItemId). + +purge_node(Host, Node, Owner) -> + node_default:purge_node(Host, Node, Owner). + +get_entity_affiliations(Host, Owner) -> + node_default:get_entity_affiliations(Host, Owner). + +get_node_affiliations(Host, Node) -> + node_default:get_node_affiliations(Host, Node). + +get_affiliation(Host, Node, Owner) -> + node_default:get_affiliation(Host, Node, Owner). + +set_affiliation(Host, Node, Owner, Affiliation) -> + node_default:set_affiliation(Host, Node, Owner, Affiliation). + +get_entity_subscriptions(Host, Owner) -> + node_default:get_entity_subscriptions(Host, Owner). + +get_node_subscriptions(Host, Node) -> + node_default:get_node_subscriptions(Host, Node). + +get_subscription(Host, Node, Owner) -> + node_default:get_subscription(Host, Node, Owner). + +set_subscription(Host, Node, Owner, Subscription) -> + node_default:set_subscription(Host, Node, Owner, Subscription). + +get_states(Host, Node) -> + node_default:get_states(Host, Node). + +get_state(Host, Node, JID) -> + node_default:get_state(Host, Node, JID). + +set_state(State) -> + node_default:set_state(State). + +get_items(Host, Node) -> + node_default:get_items(Host, Node). + +get_items(Host, Node, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> + node_default:get_items(Host, Node, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). + +get_item(Host, Node, ItemId) -> + node_default:get_item(Host, Node, ItemId). + +get_item(Host, Node, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> + node_default:get_item(Host, Node, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). + +set_item(Item) -> + node_default:set_item(Item). diff --git a/src/web/ejabberd_http.erl b/src/web/ejabberd_http.erl index 51384a216..34eeef739 100644 --- a/src/web/ejabberd_http.erl +++ b/src/web/ejabberd_http.erl @@ -290,7 +290,7 @@ process_request(#state{request_method = Method, LQ end, LPath = string:tokens(NPath, "/"), - {ok, {IP, _Port}} = + {ok, IP} = case SockMod of gen_tcp -> inet:peername(Socket); @@ -302,7 +302,7 @@ process_request(#state{request_method = Method, q = LQuery, auth = Auth, lang = Lang, - ip=IP}, + ip = IP}, %% XXX bard: This previously passed control to %% ejabberd_web:process_get, now passes it to a local %% procedure (process) that handles dispatching based on @@ -348,12 +348,20 @@ process_request(#state{request_method = Method, LQ -> LQ end, + {ok, IP} = + case SockMod of + gen_tcp -> + inet:peername(Socket); + _ -> + SockMod:peername(Socket) + end, Request = #request{method = Method, path = LPath, q = LQuery, auth = Auth, data = Data, - lang = Lang}, + lang = Lang, + ip = IP}, case process(RequestHandlers, Request) of El when element(1, El) == xmlelement -> make_xhtml_output(State, 200, [], El); diff --git a/src/web/ejabberd_http_poll.erl b/src/web/ejabberd_http_poll.erl index f5b419275..d93bec791 100644 --- a/src/web/ejabberd_http_poll.erl +++ b/src/web/ejabberd_http_poll.erl @@ -50,13 +50,16 @@ -record(http_poll, {id, pid}). +-define(NULL_PEER, {{0, 0, 0, 0}, 0}). + -record(state, {id, key, output = "", input = "", waiting_input = false, %% {ReceiverPid, Tag} last_receiver, - timer}). + timer, + ip = ?NULL_PEER }). %-define(DBGFSM, true). @@ -94,11 +97,18 @@ setopts({http_poll, FsmRef}, Opts) -> ok end. -sockname(_Socket) -> - {ok, {{0, 0, 0, 0}, 0}}. +sockname(_) -> + {ok, ?NULL_PEER}. -peername(_Socket) -> - {ok, {{0, 0, 0, 0}, 0}}. +peername({http_poll, FsmRef}) -> + gen_fsm:send_all_state_event(FsmRef, {peername, self()}), + %% XXX should improve that, but sync call seems not possible + receive + {peername, PeerName} -> {ok, PeerName} + after 1000 -> {ok, ?NULL_PEER} + end; +peername(_) -> + {ok, ?NULL_PEER}. controlling_process(_Socket, _Pid) -> ok. @@ -107,7 +117,7 @@ close({http_poll, FsmRef}) -> catch gen_fsm:sync_send_all_state_event(FsmRef, close). -process([], #request{data = Data} = _Request) -> +process([], #request{data = Data, ip = IP} = _Request) -> case catch parse_request(Data) of {ok, ID1, Key, NewKey, Packet} -> ID = if @@ -123,7 +133,7 @@ process([], #request{data = Data} = _Request) -> true -> ID1 end, - case http_put(ID, Key, NewKey, Packet) of + case http_put(ID, Key, NewKey, Packet, IP) of {error, not_exists} -> {200, ?BAD_REQUEST, ""}; {error, bad_key} -> @@ -228,6 +238,10 @@ handle_event({activate, From}, StateName, StateData) -> }} end; +handle_event({peername, From}, StateName, StateData) -> + From ! {peername, StateData#state.ip}, + {next_state, StateName, StateData}; + handle_event(_Event, StateName, StateData) -> {next_state, StateName, StateData}. @@ -249,7 +263,7 @@ handle_sync_event(stop, _From, _StateName, StateData) -> Reply = ok, {stop, normal, Reply, StateData}; -handle_sync_event({http_put, Key, NewKey, Packet}, +handle_sync_event({http_put, Key, NewKey, Packet, IP}, _From, StateName, StateData) -> Allow = case StateData#state.key of "" -> @@ -271,7 +285,8 @@ handle_sync_event({http_put, Key, NewKey, Packet}, Input = [StateData#state.input|Packet], Reply = ok, {reply, Reply, StateName, StateData#state{input = Input, - key = NewKey}}; + key = NewKey, + ip = IP}}; {Receiver, _Tag} -> Receiver ! {tcp, {http_poll, self()}, list_to_binary(Packet)}, @@ -282,7 +297,8 @@ handle_sync_event({http_put, Key, NewKey, Packet}, StateData#state{waiting_input = false, last_receiver = Receiver, key = NewKey, - timer = Timer}} + timer = Timer, + ip = IP}} end; true -> Reply = {error, bad_key}, @@ -343,13 +359,13 @@ terminate(_Reason, _StateName, StateData) -> %%% Internal functions %%%---------------------------------------------------------------------- -http_put(ID, Key, NewKey, Packet) -> +http_put(ID, Key, NewKey, Packet, IP) -> case mnesia:dirty_read({http_poll, ID}) of [] -> {error, not_exists}; [#http_poll{pid = FsmRef}] -> gen_fsm:sync_send_all_state_event( - FsmRef, {http_put, Key, NewKey, Packet}) + FsmRef, {http_put, Key, NewKey, Packet, IP}) end. http_get(ID) -> -- 2.40.0