]> granicus.if.org Git - ejabberd/commitdiff
PubSub improvements, and solves (EJAB-453) (EJAB-608)
authorChristophe Romain <christophe.romain@process-one.net>
Thu, 3 Jul 2008 09:56:31 +0000 (09:56 +0000)
committerChristophe Romain <christophe.romain@process-one.net>
Thu, 3 Jul 2008 09:56:31 +0000 (09:56 +0000)
SVN Revision: 1408

19 files changed:
ChangeLog
src/ejabberd_c2s.erl
src/ejabberd_local.erl
src/ejabberd_receiver.erl
src/ejabberd_sm.erl
src/mod_caps.erl
src/mod_pubsub/gen_pubsub_node.erl
src/mod_pubsub/mod_pubsub.erl
src/mod_pubsub/node.template
src/mod_pubsub/node_buddy.erl
src/mod_pubsub/node_club.erl
src/mod_pubsub/node_default.erl
src/mod_pubsub/node_dispatch.erl
src/mod_pubsub/node_pep.erl
src/mod_pubsub/node_private.erl
src/mod_pubsub/node_public.erl
src/mod_pubsub/node_zoo.erl [new file with mode: 0644]
src/web/ejabberd_http.erl
src/web/ejabberd_http_poll.erl

index 5226fe079ed119fd5c7330fd8c2bdde1ca046213..0d8ee14ad5a003d1856870bfc89817082ff6727e 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,35 @@
+2008-07-03  Christophe Romain  <christophe.romain@process-one.net>
+
+       * 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/<host>/<user> 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  <badlop@process-one.net>
 
        * src/ejabberd_ctl.erl: Web Admin and Ad-hoc admin: dump only
index 358b1fe32965259d5204de973592c9ab9e396436..5afd6cc5341a6869e6b5a4da649030fea52b7a39 100644 (file)
@@ -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).
index b54173581f509dd4e829be559cc6f680fdca6f99..67f5b963694b72195d7b7e6397e8f372035f8851 100644 (file)
@@ -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),
index 72b3608d69db52357505dbb523c28f09e6c8160c..c178df62a0e305deb6fe70e2e25450ebaedce599 100644 (file)
@@ -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.
 
index 2d4b7c5b970ec097a66704a3c32f9d782d08c654..bb543707f8cb9721a886b611c21b6aa167174e43 100644 (file)
@@ -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),
index 2be0fac668826b460684fddf8c6094d21209a2a3..3240aa6e28d3183e44f098a95c19780f8ab72886 100644 (file)
@@ -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 =
index 195ed393439994db893948d0651d89887157a31d..bbbb5c40942e45b3953ec94b298b845abd81b7f6 100644 (file)
@@ -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}
index 349c7ad4fe9969a33775e7fc6a608912d4bf86c2..92f5cf95941f5fe7b49154c7d27f24f1c4077b71 100644 (file)
 %%%
 %%% @reference See <a href="http://www.xmpp.org/extensions/xep-0060.html">XEP-0060: Pubsub</a> 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 <alexey@process-one.net>
 
 %%% 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) ->
 %% <p>The permission are not checked in this function.</p>
 %% @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
index 850a38ec9db4aa40cd408cf7cee2679117694b32..973aa38a4af14605ff03bd52c9141aa470926c74 100644 (file)
@@ -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).
index cdf3a696f36ac5a5a852b6a709fea13aedc70747..cd051b66f923e4b0f3b1b05ad25ca3d406e8f4d2 100644 (file)
@@ -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).
 
index d927053fbd351c1e444b83d087c1c90bba67da98..155edc10a475aad2f4e2a2c3c9a56b2936d55782 100644 (file)
@@ -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).
 
index fc4194c325e0d0b80e838cb1291e81b24e38930f..491676269f48e391c98bd73ea9f0d7cc4492974c 100644 (file)
@@ -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).'''</p>
-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 <p>Triggers item deletion.</p>
 %% <p>Default plugin: The user performing the deletion must be the node owner
-%% or a node publisher e item publisher.</p>
+%% or a publisher.</p>
 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()
index 3b4418c3e20c31f5ec3c979ecf9e3c69a9777886..0532826d2bb7ecd75b9e993748e732c76460eb7a 100644 (file)
@@ -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).
 
index 2fceb03ed2a5f4f9ee0362c3522775dc73c7758e..2c648148dcd33839d8c4a4f6a4107b0900372618 100644 (file)
@@ -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).
 
index 35f63c9259967be6c60898daf28b7cc28a75c509..00e9523929c19760d124062766416cc5e42c3360 100644 (file)
@@ -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).
 
index 1586abe86b161da1fa070b3e2232c274656100ed..ab4107a76e00aa3a2e79304362a5013bbd913d3a 100644 (file)
@@ -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 (file)
index 0000000..1cb8a73
--- /dev/null
@@ -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 <christophe.romain@process-one.net>
+%%%   [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).
index 51384a21628a925f7b566dd40c6da0abe2e6c126..34eeef73967bcf9f8cbc70dd51b36755867d7537 100644 (file)
@@ -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);
index f5b419275fe3f1c592c9590dba162b3f433b3fe7..d93bec791c5cd435abdf3b66317321edc56cdbcd 100644 (file)
 
 -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) ->