]> granicus.if.org Git - ejabberd/commitdiff
* src/mod_pubsub/mod_pubsub.erl: Updated to J-EAI version
authorAlexey Shchepin <alexey@process-one.net>
Wed, 20 Jul 2005 03:09:34 +0000 (03:09 +0000)
committerAlexey Shchepin <alexey@process-one.net>
Wed, 20 Jul 2005 03:09:34 +0000 (03:09 +0000)
SVN Revision: 378

ChangeLog
src/mod_pubsub/mod_pubsub.erl

index 9412e948a9a49cf35fbc41a136e14859dce079c7..c587e0a10ce048e408fdd19649b5f727af01664f 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2005-07-20  Alexey Shchepin  <alexey@sevcom.net>
+
+       * src/mod_pubsub/mod_pubsub.erl: Updated to J-EAI version
+
 2005-07-15  Alexey Shchepin  <alexey@sevcom.net>
 
        * src/acl.erl: Slightly changed "access" option processing
index ee4ce1c2807edd137d02df2641cbaf9b1a2fffab..c957c9aca2def004ffed31474617d197885d3417 100644 (file)
         system_terminate/4,
         system_code_change/4]).
 
+-export([delete_item/3,
+        set_entities/4,
+        delete_node/2,
+        create_new_node/2,
+        subscribe_node/3,
+        get_node_config/4,
+        set_node_config/4]).
+
 -include("ejabberd.hrl").
 -include("jlib.hrl").
 
 -define(DICT, dict).
--define(MAXITEMS, 10).
+-define(MAXITEMS, 20).
+-define(MAX_PAYLOAD_SIZE, 100000).
 
 -record(pubsub_node, {host_node, host_parent, info}).
 -record(nodeinfo, {items = [],
@@ -62,6 +71,7 @@ init(Host, ServerHost, ServedHosts, Parent) ->
     lists:foreach(fun(H) ->
                          create_new_node(Host, ["home", H], ?MYJID)
                  end, ServedHosts),
+    ets:new(gen_mod:get_module_proc(Host, pubsub_presence), [set, named_table]),
     loop(Host, Parent).
 
 loop(Host, Parent) ->
@@ -88,6 +98,33 @@ loop(Host, Parent) ->
            loop(Host, Parent)
     end.
 
+%%% API functions
+
+delete_item(From, Node, ItemID) ->
+    delete_item(get_host(), From, Node, ItemID).
+
+delete_node(From, Node) ->
+    delete_node(get_host(), From, Node).
+
+create_new_node(Node, From) ->
+    create_new_node(get_host(), Node, From).
+
+subscribe_node(From, JID, Node) ->
+    subscribe_node(get_host(), From, JID, Node).
+
+set_node_config(From, Node, Els, Lang) ->
+    set_node_config(get_host(), From, Node, Els, Lang).
+
+get_host() ->
+    ejabberd_mod_pubsub ! {get_host, self()},
+    receive
+       {pubsub_host, Host} ->
+           Host
+    after 5000 ->
+           timeout
+    end.
+
+%%% Internal functions
 
 do_route(Host, From, To, Packet) ->
     {xmlelement, Name, Attrs, Els} = Packet,
@@ -124,32 +161,6 @@ do_route(Host, From, To, Packet) ->
                                          Packet, Error)
                                end,
                            ejabberd_router:route(To, From, Res);
-                       %#iq{type = get, xmlns = ?NS_REGISTER = XMLNS,
-                       %    lang = Lang, sub_el = SubEl} = IQ ->
-                       %    Res = IQ#iq{type = result,
-                       %                sub_el = [{xmlelement, "query",
-                       %                           [{"xmlns", XMLNS}],
-                       %                           iq_get_register_info(
-                       %                             From, Lang)}]},
-                       %    ejabberd_router:route(To,
-                       %                         From,
-                       %                         jlib:iq_to_xml(Res));
-                       %#iq{type = set, xmlns = ?NS_REGISTER = XMLNS,
-                       %    sub_el = SubEl} = IQ ->
-                       %    case process_iq_register_set(From, SubEl) of
-                       %       {result, IQRes} ->
-                       %           Res = IQ#iq{type = result,
-                       %                       sub_el = [{xmlelement, "query",
-                       %                                  [{"xmlns", XMLNS}],
-                       %                                  IQRes}]},
-                       %           ejabberd_router:route(
-                       %             To, From, jlib:iq_to_xml(Res));
-                       %       {error, Error} ->
-                       %           Err = jlib:make_error_reply(
-                       %                   Packet, Error),
-                       %           ejabberd_router:route(
-                       %             To, From, Err)
-                       %    end;
                        #iq{type = Type, xmlns = ?NS_PUBSUB = XMLNS,
                            sub_el = SubEl} = IQ ->
                            Res =
@@ -163,6 +174,20 @@ do_route(Host, From, To, Packet) ->
                                          Packet, Error)
                                end,
                            ejabberd_router:route(To, From, Res);
+                       #iq{type = Type, xmlns = ?NS_PUBSUB_OWNER = XMLNS,
+                           lang = Lang, sub_el = SubEl} = IQ ->
+                           Res =
+                               case iq_pubsub_owner(
+                                      Host, From, Type, Lang, SubEl) of
+                                   {result, IQRes} ->
+                                       jlib:iq_to_xml(
+                                         IQ#iq{type = result,
+                                               sub_el = IQRes});
+                                   {error, Error} ->
+                                       jlib:make_error_reply(
+                                         Packet, Error)
+                               end,
+                           ejabberd_router:route(To, From, Res);
                        #iq{type = get, xmlns = ?NS_VCARD = XMLNS,
                            lang = Lang, sub_el = SubEl} = IQ ->
                            Res = IQ#iq{type = result,
@@ -180,6 +205,19 @@ do_route(Host, From, To, Packet) ->
                        _ ->
                            ok
                    end;
+               "presence" ->
+                   Type = xml:get_attr_s("type", Attrs),
+                   if
+                       (Type == "unavailable") or (Type == "error") ->
+                           ets:delete(
+                             gen_mod:get_module_proc(Host, pubsub_presence),
+                             {From#jid.luser, From#jid.lserver});
+                       true ->
+                           ets:insert(
+                             gen_mod:get_module_proc(Host, pubsub_presence),
+                             {{From#jid.luser, From#jid.lserver}, []})
+                   end,
+                   ok;
                _ ->
                    ok
            end;
@@ -219,7 +257,6 @@ iq_disco_info(SNode) ->
              [{"category", "pubsub"},
               {"type", "generic"},
               {"name", "ejabberd/mod_pubsub"}], []},
-            %{xmlelement, "feature", [{"var", ?NS_REGISTER}], []},
             {xmlelement, "feature", [{"var", ?NS_PUBSUB}], []},
             {xmlelement, "feature", [{"var", ?NS_PUBSUB_EVENT}], []},
             {xmlelement, "feature", [{"var", ?NS_PUBSUB_OWNER}], []},
@@ -286,104 +323,6 @@ iq_disco_items(Host, From, SNode) ->
     end.
 
 
-% TODO
-%-define(XFIELD(Type, Label, Var, Val),
-%      {xmlelement, "field", [{"type", Type},
-%                             {"label", translate:translate(Lang, Label)},
-%                             {"var", Var}],
-%       [{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
-%
-%iq_get_register_info(From, Lang) ->
-%    {LUser, LServer, _} = jlib:jid_tolower(From),
-%    LUS = {LUser, LServer},
-%    Nick = case catch mnesia:dirty_read(muc_registered, LUS) of
-%             {'EXIT', Reason} ->
-%                 "";
-%             [] ->
-%                 "";
-%             [#muc_registered{nick = N}] ->
-%                 N
-%         end,
-%    [{xmlelement, "instructions", [],
-%      [{xmlcdata, translate:translate(
-%                  Lang, "You need a x:data capable client to register.")}]},
-%     {xmlelement, "x",
-%      [{"xmlns", ?NS_XDATA}],
-%      [{xmlelement, "title", [],
-%      [{xmlcdata,
-%        translate:translate(
-%          Lang, "Nick Registration")}]},
-%       {xmlelement, "instructions", [],
-%      [{xmlcdata,
-%        translate:translate(
-%          Lang, "Enter nick you want to register.")}]},
-%       ?XFIELD("text-single", "Nick", "nick", Nick)]}].
-%
-%iq_set_register_info(From, XData) ->
-%    {LUser, LServer, _} = jlib:jid_tolower(From),
-%    LUS = {LUser, LServer},
-%    case lists:keysearch("nick", 1, XData) of
-%      false ->
-%          {error, ?ERR_BAD_REQUEST};
-%      {value, {_, [Nick]}} ->
-%          F = fun() ->
-%                      case Nick of
-%                          "" ->
-%                              mnesia:delete({muc_registered, LUS}),
-%                              ok;
-%                          _ ->
-%                              Allow = case mnesia:index_read(
-%                                             muc_registered,
-%                                             Nick,
-%                                             #muc_registered.nick) of
-%                                          [] ->
-%                                              true;
-%                                          [#muc_registered{user = U}] ->
-%                                              U == LUS
-%                                      end,
-%                              if
-%                                  Allow ->
-%                                      mnesia:write(
-%                                        #muc_registered{user = LUS,
-%                                                        nick = Nick}),
-%                                      ok;
-%                                  true ->
-%                                      false
-%                              end
-%                      end
-%              end,
-%          case mnesia:transaction(F) of
-%              {atomic, ok} ->
-%                  {result, []};
-%              {atomic, false} ->
-%                  {error, ?ERR_NOT_ALLOWED};
-%              _ ->
-%                  {error, ?ERR_INTERNAL_SERVER_ERROR}
-%          end
-%    end.
-%
-%process_iq_register_set(From, SubEl) ->
-%    {xmlelement, Name, Attrs, Els} = SubEl,
-%    case xml:remove_cdata(Els) of
-%      [{xmlelement, "x", Attrs1, Els1} = XEl] ->
-%          case {xml:get_tag_attr_s("xmlns", XEl),
-%                xml:get_tag_attr_s("type", XEl)} of
-%              {?NS_XDATA, "cancel"} ->
-%                  {result, []};
-%              {?NS_XDATA, "submit"} ->
-%                  XData = jlib:parse_xdata_submit(XEl),
-%                  case XData of
-%                      invalid ->
-%                          {error, ?ERR_BAD_REQUEST};
-%                      _ ->
-%                          iq_set_register_info(From, XData)
-%                  end;
-%              _ ->
-%                  {error, ?ERR_BAD_REQUEST}
-%          end;
-%      _ ->
-%          {error, ?ERR_BAD_REQUEST}
-%    end.
 
 iq_get_vcard(Lang) ->
     [{xmlelement, "FN", [],
@@ -440,8 +379,8 @@ iq_pubsub(Host, From, Type, SubEl) ->
                    get_entities(Host, From, Node);
                {set, "entities"} ->
                    set_entities(Host, From, Node, xml:remove_cdata(Els));
-               %{get, "configure"} ->
-               %    get_node_config(From, Node);
+               {get, "affiliations"} ->
+                   get_affiliations(Host, From);
                _ ->
                    {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
            end;
@@ -456,6 +395,31 @@ iq_pubsub(Host, From, Type, SubEl) ->
                               {"var", Var}],
         [{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
 
+-define(BOOLXFIELD(Label, Var, Val),
+       ?XFIELD("boolean", Label, Var,
+               case Val of
+                   true -> "1";
+                   _ -> "0"
+               end)).
+
+-define(STRINGXFIELD(Label, Var, Val),
+       ?XFIELD("text-single", Label, Var, Val)).
+
+-define(XFIELDOPT(Type, Label, Var, Val, Opts),
+       {xmlelement, "field", [{"type", Type},
+                              {"label", translate:translate(Lang, Label)},
+                              {"var", Var}],
+        lists:map(fun(Opt) ->
+                          {xmlelement, "option", [],
+                           [{xmlelement, "value", [],
+                             [{xmlcdata, Opt}]}]}
+                  end, Opts) ++
+        [{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
+
+-define(LISTXFIELD(Label, Var, Val, Opts),
+       ?XFIELDOPT("list-single", Label, Var, Val, Opts)).
+
+
 
 %% Create new pubsub nodes
 %% This function is used during init to create the first bootstrap nodes
@@ -533,14 +497,25 @@ create_new_node(Host, Node, Owner) ->
 
 
 publish_item(Host, JID, Node, ItemID, Payload) ->
+    ejabberd_hooks:run(pubsub_publish_item, Host,
+                      [JID, ?MYJID, Node, ItemID, Payload]),
     Publisher = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
     F = fun() ->
                case mnesia:read({pubsub_node, {Host, Node}}) of
                    [#pubsub_node{info = Info} = N] ->
                        Affiliation = get_affiliation(Info, Publisher),
+                       Subscription = get_subscription(Info, Publisher),
+                       MaxSize = get_node_option(Info, max_payload_size),
+                       Model = get_node_option(Info, publish_model),
+                       Size = size(term_to_binary(Payload)),
                        if
-                           (Affiliation == owner) or
-                           (Affiliation == publisher) ->
+                           ((Model == open) or
+                           ((Model == publishers) and
+                            ((Affiliation == owner) or
+                             (Affiliation == publisher))) or
+                           ((Model == subscribers) and
+                            (Subscription == subscribed))) and
+                           (Size =< MaxSize) ->
                                NewInfo =
                                    insert_item(Info, ItemID,
                                                Publisher, Payload),
@@ -609,19 +584,19 @@ subscribe_node(Host, From, JID, Node) ->
     Subscriber = jlib:jid_tolower(SubscriberJID),
     SubscriberWithoutResource = jlib:jid_remove_resource(Subscriber),
     F = fun() ->
-               case mnesia:read({pubsub_node, {Host, Node}}) of
-                   [#pubsub_node{info = Info} = N] ->
-                       Affiliation = get_affiliation(Info, Subscriber),
-                       if
-                           Affiliation /= outcast ->
-                               NewInfo =
-                                   add_subscriber(Info, Subscriber),
-                               mnesia:write(
-                                 N#pubsub_node{info = NewInfo}),
-                               {result, []};
-                           true ->
-                               {error, ?ERR_NOT_ALLOWED}
-                       end;
+               case mnesia:read({pubsub_node, {Host, Node}}) of
+                   [#pubsub_node{info = Info} = N] ->
+                       Affiliation = get_affiliation(Info, Subscriber),
+                       AllowSubscriptions = get_node_option(Info, subscribe),
+                       if
+                           AllowSubscriptions and
+                           (Affiliation /= outcast) ->
+                               NewInfo = add_subscriber(Info, Subscriber),
+                               mnesia:write(N#pubsub_node{info = NewInfo}),
+                               {result, [], Info};
+                           true ->
+                               {error, ?ERR_NOT_ALLOWED}
+                       end;
                    [] ->
                        {error, ?ERR_ITEM_NOT_FOUND}
                end
@@ -631,7 +606,33 @@ subscribe_node(Host, From, JID, Node) ->
            case mnesia:transaction(F) of
                {atomic, {error, _} = Error} ->
                    Error;
-               {atomic, {result, Res}} ->
+               {atomic, {result, Res, Info}} ->
+                   case get_node_option(Info, send_item_subscribe) of
+                       true ->
+                           ItemsEls =
+                               lists:map(
+                                 fun(#item{id = ItemID,
+                                           payload = Payload}) ->
+                                         ItemAttrs = case ItemID of
+                                                         "" -> [];
+                                                         _ -> [{"id", ItemID}]
+                                                     end,
+                                         {xmlelement, "item",
+                                          ItemAttrs, Payload}
+                                 end, Info#nodeinfo.items),
+                           Stanza =
+                               {xmlelement, "message",
+                                [],
+                                [{xmlelement, "x",
+                                  [{"xmlns", ?NS_PUBSUB_EVENT}],
+                                  [{xmlelement, "items",
+                                    [{"node", node_to_string(Node)}],
+                                    ItemsEls}]}]},
+                           ejabberd_router:route(
+                             ?MYJID, jlib:make_jid(Subscriber), Stanza);
+                       false ->
+                           ok
+                   end,
                    {result, Res};
                _ ->
                    {error, ?ERR_INTERNAL_SERVER_ERROR}
@@ -715,7 +716,7 @@ get_items(Host, JID, Node, SMaxItems) ->
                                  {xmlelement, "item", ItemAttrs, Payload}
                          end, Items),
                    {result, [{xmlelement, "pubsub",
-                              [{"xmlns", ?NS_PUBSUB}],
+                              [{"xmlns", ?NS_PUBSUB_EVENT}],
                               [{xmlelement, "items",
                                 [{"node", node_to_string(Node)}],
                                 ItemsEls}]}]};
@@ -736,12 +737,10 @@ delete_node(Host, JID, Node) ->
                                Removed =
                                    mnesia:foldl(
                                      fun(#pubsub_node{host_node = {_, N},
-                                                      info = #nodeinfo{
-                                                        entities = Entities
-                                                       }}, Acc) ->
+                                                      info = NInfo}, Acc) ->
                                              case lists:prefix(Node, N) of
                                                  true ->
-                                                     [{N, Entities} | Acc];
+                                                     [{N, NInfo} | Acc];
                                                  _ ->
                                                      Acc
                                              end
@@ -826,7 +825,7 @@ get_entities(Host, OJID, Node) ->
                                    []} | Acc]
                          end, [], Entities),
                    {result, [{xmlelement, "pubsub",
-                              [{"xmlns", ?NS_PUBSUB}],
+                              [{"xmlns", ?NS_PUBSUB_EVENT}],
                               [{xmlelement, "entities",
                                 [{"node", node_to_string(Node)}],
                                 EntitiesEls}]}]};
@@ -916,41 +915,44 @@ set_entities(Host, OJID, Node, EntitiesEls) ->
     end.
 
 
-%get_node_config(OJID, Node) ->
-%    Owner = jlib:jid_tolower(jlib:jid_remove_resource(OJID)),
-%    case catch mnesia:dirty_read(pubsub_node, Node) of
-%      [#pubsub_node{info = Info}] ->
-%          case get_affiliation(Info, Owner) of
-%              owner ->
-%                  Entities = Info#nodeinfo.entities,
-%                  EntitiesEls =
-%                      ?DICT:fold(
-%                        fun(JID,
-%                            #entity{affiliation = Affiliation,
-%                                    subscription = Subscription},
-%                            Acc) ->
-%                                [{xmlelement, "entity",
-%                                  [{"jid", jlib:jid_to_string(JID)},
-%                                   {"affiliation",
-%                                    affiliation_to_string(Affiliation)},
-%                                   {"subscription",
-%                                    subscription_to_string(Subscription)}],
-%                                  []} | Acc]
-%                        end, [], Entities),
-%                  {result, [{xmlelement, "pubsub",
-%                             [{"xmlns", ?NS_PUBSUB_EVENT}],
-%                             [{xmlelement, "entities",
-%                               [{"node", node_to_string(Node)}],
-%                               EntitiesEls}]}]};
-%              _ ->
-%                  {error, ?ERR_NOT_ALLOWED}
-%          end;
-%      _ ->
-%          {error, ?ERR_ITEM_NOT_FOUND}
-%    end.
-
-
-
+get_affiliations(Host, JID) ->
+    LJID = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
+    case catch mnesia:dirty_select(
+                pubsub_node,
+                [{#pubsub_node{_ = '_'},
+                  [],
+                  ['$_']}]) of
+       {'EXIT', _} ->
+           {error, ?ERR_INTERNAL_SERVER_ERROR};
+       Nodes ->
+           Entities =
+               lists:flatmap(
+                 fun(#pubsub_node{host_node = {H, Node}, info = Info})
+                    when H == Host ->
+                         Affiliation = get_affiliation(Info, LJID),
+                         Subscription = get_subscription(Info, LJID),
+                         if
+                             (Affiliation /= none) or
+                             (Subscription /= none) ->
+                                 [{xmlelement, "entity",
+                                   [{"node", node_to_string(Node)},
+                                    {"jid", jlib:jid_to_string(JID)},
+                                    {"affiliation",
+                                     affiliation_to_string(Affiliation)},
+                                    {"subscription",
+                                     subscription_to_string(Subscription)}],
+                                   []}];
+                             true ->
+                                 []
+                         end;
+                    (_) ->
+                         []
+                 end, Nodes),
+           {result, [{xmlelement, "pubsub",
+                      [{"xmlns", ?NS_PUBSUB_EVENT}],
+                      [{xmlelement, "affiliations", [],
+                        Entities}]}]}
+    end.
 
 
 
@@ -1011,7 +1013,7 @@ insert_item(Info, ItemID, Publisher, Payload) ->
                          end, Items),
     Items2 = [#item{id = ItemID, publisher = Publisher, payload = Payload} |
              Items1],
-    Items3 = lists:sublist(Items2, ?MAXITEMS),
+    Items3 = lists:sublist(Items2, get_max_items(Info)),
     Info#nodeinfo{items = Items3}.
 
 remove_item(Info, ItemID) ->
@@ -1073,7 +1075,7 @@ set_info_entities(Info, Entities) ->
                  end
          end, Info#nodeinfo.entities, Entities),
     Info#nodeinfo{entities = NewEntities}.
-                               
+
 
 
 broadcast_publish_item(Host, Node, ItemID, Payload) ->
@@ -1081,13 +1083,36 @@ broadcast_publish_item(Host, Node, ItemID, Payload) ->
        [#pubsub_node{info = Info}] ->
            ?DICT:fold(
               fun(JID, #entity{subscription = Subscription}, _) ->
-                      if 
+                      Present = case get_node_option(
+                                       Info, presence_based_delivery) of
+                                    true ->
+                                        case ets:lookup(
+                                               gen_mod:get_module_proc(Host, pubsub_presence),
+                                               {element(1, JID),
+                                                element(2, JID)}) of
+                                            [_] ->
+                                                true;
+                                            [] ->
+                                                false
+                                        end;
+                                    false ->
+                                        true
+                                end,
+                      if
                           (Subscription /= none) and
-                          (Subscription /= pending) ->
+                          (Subscription /= pending) and
+                          Present ->
                               ItemAttrs = case ItemID of
                                               "" -> [];
                                               _ -> [{"id", ItemID}]
                                           end,
+                              Content = case get_node_option(
+                                               Info, deliver_payloads) of
+                                            true ->
+                                                Payload;
+                                            false ->
+                                                []
+                                        end,
                               Stanza =
                                   {xmlelement, "message", [],
                                    [{xmlelement, "event",
@@ -1096,7 +1121,7 @@ broadcast_publish_item(Host, Node, ItemID, Payload) ->
                                        [{"node", node_to_string(Node)}],
                                        [{xmlelement, "item",
                                          ItemAttrs,
-                                         Payload}]}]}]},
+                                         Content}]}]}]},
                               ejabberd_router:route(
                                 ?MYJID, jlib:make_jid(JID), Stanza);
                           true ->
@@ -1111,29 +1136,34 @@ broadcast_publish_item(Host, Node, ItemID, Payload) ->
 broadcast_retract_item(Host, Node, ItemID) ->
     case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
        [#pubsub_node{info = Info}] ->
-           ?DICT:fold(
-              fun(JID, #entity{subscription = Subscription}, _) ->
-                      if 
-                          (Subscription /= none) and
-                          (Subscription /= pending) ->
-                              ItemAttrs = case ItemID of
-                                              "" -> [];
-                                              _ -> [{"id", ItemID}]
-                                          end,
-                              Stanza =
-                                  {xmlelement, "message", [],
-                                   [{xmlelement, "event",
-                                     [{"xmlns", ?NS_PUBSUB_EVENT}],
-                                     [{xmlelement, "items",
-                                       [{"node", node_to_string(Node)}],
-                                       [{xmlelement, "retract",
-                                        ItemAttrs, []}]}]}]},
-                              ejabberd_router:route(
-                                ?MYJID, jlib:make_jid(JID), Stanza);
-                          true ->
-                              ok
-                      end
-              end, ok, Info#nodeinfo.entities);
+           case get_node_option(Info, notify_retract) of
+               true ->
+                   ?DICT:fold(
+                      fun(JID, #entity{subscription = Subscription}, _) ->
+                              if 
+                                  (Subscription /= none) and
+                                  (Subscription /= pending) ->
+                                      ItemAttrs = case ItemID of
+                                                      "" -> [];
+                                                      _ -> [{"id", ItemID}]
+                                                  end,
+                                      Stanza =
+                                          {xmlelement, "message", [],
+                                           [{xmlelement, "x",
+                                             [{"xmlns", ?NS_PUBSUB_EVENT}],
+                                             [{xmlelement, "items",
+                                               [{"node", node_to_string(Node)}],
+                                               [{xmlelement, "retract",
+                                                 ItemAttrs, []}]}]}]},
+                                      ejabberd_router:route(
+                                        ?MYJID, jlib:make_jid(JID), Stanza);
+                                  true ->
+                                      ok
+                              end
+                      end, ok, Info#nodeinfo.entities);
+               false ->
+                   ok
+           end;
        _ ->
            false
     end.
@@ -1141,28 +1171,95 @@ broadcast_retract_item(Host, Node, ItemID) ->
 
 broadcast_removed_node(Host, Removed) ->
     lists:foreach(
-      fun({Node, Entities}) ->
-             ?DICT:fold(
-                fun(JID, #entity{subscription = Subscription}, _) ->
-                        if 
-                            (Subscription /= none) and
-                            (Subscription /= pending) ->
-                                Stanza =
-                                    {xmlelement, "message", [],
-                                     [{xmlelement, "event",
-                                       [{"xmlns", ?NS_PUBSUB_EVENT}],
-                                       [{xmlelement, "delete",
-                                         [{"node", node_to_string(Node)}],
-                                           []}]}]},
-                                ejabberd_router:route(
-                                  ?MYJID, jlib:make_jid(JID), Stanza);
-                            true ->
-                                ok
-                        end
-                end, ok, Entities)
+      fun({Node, Info}) ->
+             case get_node_option(Info, notify_delete) of
+                 true ->
+                     Entities = Info#nodeinfo.entities,
+                     ?DICT:fold(
+                        fun(JID, #entity{subscription = Subscription}, _) ->
+                                if 
+                                    (Subscription /= none) and
+                                    (Subscription /= pending) ->
+                                        Stanza =
+                                            {xmlelement, "message", [],
+                                             [{xmlelement, "x",
+                                               [{"xmlns", ?NS_PUBSUB_EVENT}],
+                                               [{xmlelement, "delete",
+                                                 [{"node", node_to_string(Node)}],
+                                                 []}]}]},
+                                        ejabberd_router:route(
+                                          ?MYJID, jlib:make_jid(JID), Stanza);
+                                    true ->
+                                        ok
+                                end
+                        end, ok, Entities);
+                 false ->
+                     ok
+             end
       end, Removed).
 
 
+broadcast_config_notification(Host, Node, Lang) ->
+    case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
+       [#pubsub_node{info = Info}] ->
+           case get_node_option(Info, notify_config) of
+               true ->
+                   ?DICT:fold(
+                      fun(JID, #entity{subscription = Subscription}, _) ->
+                              Present = case get_node_option(
+                                               Info, presence_based_delivery) of
+                                            true ->
+                                                case ets:lookup(
+                                                       gen_mod:get_module_proc(Host, pubsub_presence),
+                                                       {element(1, JID),
+                                                        element(2, JID)}) of
+                                                    [_] ->
+                                                        true;
+                                                    [] ->
+                                                        false
+                                                end;
+                                            false ->
+                                                true
+                                        end,
+                              if
+                                  (Subscription /= none) and
+                                  (Subscription /= pending) and
+                                  Present ->
+                                      Fields = get_node_config_xfields(
+                                                 Node, Info, Lang),
+                                      Content = case get_node_option(
+                                                       Info, deliver_payloads) of
+                                                    true ->
+                                                        [{xmlelement, "x",
+                                                          [{"xmlns", ?NS_XDATA},
+                                                           {"type", "form"}],
+                                                          Fields}];
+                                                    false ->
+                                                        []
+                                                end,
+                                      Stanza =
+                                          {xmlelement, "message", [],
+                                           [{xmlelement, "x",
+                                             [{"xmlns", ?NS_PUBSUB_EVENT}],
+                                             [{xmlelement, "items",
+                                               [{"node", node_to_string(Node)}],
+                                               [{xmlelement, "item",
+                                                 [{"id", "configuration"}],
+                                                 Content}]}]}]},
+                                      ejabberd_router:route(
+                                        ?MYJID, jlib:make_jid(JID), Stanza);
+                                  true ->
+                                      ok
+                              end
+                      end, ok, Info#nodeinfo.entities);
+               false ->
+                   ok
+           end;
+       _ ->
+           false
+    end.
+
+
 
 system_continue(Parent, _, State) ->
     loop(State, Parent).
@@ -1176,6 +1273,303 @@ system_code_change(State, _Mod, Ver, _Extra) ->
 
 
 
+iq_pubsub_owner(Host, From, Type, Lang, SubEl) ->
+    {xmlelement, _, _, SubEls} = SubEl,
+    case xml:remove_cdata(SubEls) of
+       [{xmlelement, Name, Attrs, Els}] ->
+           SNode = xml:get_attr_s("node", Attrs),
+           Node = string:tokens(SNode, "/"),
+           case {Type, Name} of
+               {get, "configure"} ->
+                   get_node_config(Host, From, Node, Lang);
+               {set, "configure"} ->
+                   set_node_config(Host, From, Node, Els, Lang);
+               _ ->
+                   {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
+           end;
+       _ ->
+           {error, ?ERR_BAD_REQUEST}
+    end.
+
+get_node_config(Host, From, Node, Lang) ->
+    case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
+       [#pubsub_node{info = Info}] ->
+           case get_affiliation(Info, From) of
+               owner ->
+                   Fields = get_node_config_xfields(Node, Info, Lang),
+                   {result, [{xmlelement, "pubsub",
+                              [{"xmlns", ?NS_PUBSUB_OWNER}],
+                              [{xmlelement, "configure",
+                                [{"node", node_to_string(Node)}],
+                                [{xmlelement, "x", [{"xmlns", ?NS_XDATA},
+                                                    {"type", "form"}],
+                                  Fields}]}]}]};
+               _ ->
+                   {error, ?ERR_NOT_AUTHORIZED}
+           end;
+       _ ->
+           {error, ?ERR_ITEM_NOT_FOUND}
+    end.
+
+% TODO: move to jlib.hrl
+-define(NS_PUBSUB_NODE_CONFIG, "http://jabber.org/protocol/pubsub#node_config").
+
+-define(BOOL_CONFIG_FIELD(Label, Var),
+       ?BOOLXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
+                   get_node_option(Info, Var))).
+
+-define(STRING_CONFIG_FIELD(Label, Var),
+       ?STRINGXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
+                     get_node_option(Info, Var))).
+
+-define(INTEGER_CONFIG_FIELD(Label, Var),
+       ?STRINGXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
+                     integer_to_list(get_node_option(Info, Var)))).
+
+-define(JLIST_CONFIG_FIELD(Label, Var, Opts),
+       ?LISTXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
+                   jlib:jid_to_string(get_node_option(Info, Var)),
+                   [jlib:jid_to_string(O) || O <- Opts])).
+
+-define(ALIST_CONFIG_FIELD(Label, Var, Opts),
+       ?LISTXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
+                   atom_to_list(get_node_option(Info, Var)),
+                   [atom_to_list(O) || O <- Opts])).
+
+
+-define(DEFAULT_OPTIONS,
+       [{deliver_payloads, true},
+        {notify_config, false},
+        {notify_delete, false},
+        {notify_retract, true},
+        {persist_items, true},
+        {max_items, ?MAXITEMS div 2},
+        {subscribe, true},
+        {subscription_model, open},
+        {publish_model, publishers},
+        {max_payload_size, ?MAX_PAYLOAD_SIZE},
+        {send_item_subscribe, false},
+        {presence_based_delivery, false}]).
+
+get_node_option(Info, current_approver) ->
+    Default = hd(get_owners_jids(Info)),
+    Options = Info#nodeinfo.options,
+    element(
+      2, element(2, lists:keysearch(
+                     current_approver, 1,
+                     Options ++ [{current_approver, Default}])));
+get_node_option(#nodeinfo{options = Options}, Var) ->
+    element(
+      2, element(2, lists:keysearch(Var, 1, Options ++ ?DEFAULT_OPTIONS))).
+
+get_max_items(Info) ->
+    case get_node_option(Info, persist_items) of
+       true ->
+           get_node_option(Info, max_items);
+       false ->
+           0
+    end.
+
+get_owners_jids(Info) ->
+    Entities = Info#nodeinfo.entities,
+    Owners =
+       ?DICT:fold(
+          fun(JID,
+              #entity{affiliation = Affiliation,
+                      subscription = Subscription},
+              Acc) ->
+                  case Affiliation of
+                      owner ->
+                          [JID | Acc];
+                      _ ->
+                          Acc
+                  end
+          end, [], Entities),
+    lists:sort(Owners).
+
+
+get_node_config_xfields(Node, Info, Lang) ->
+    [?XFIELD("hidden", "", "FORM_TYPE", ?NS_PUBSUB_NODE_CONFIG),
+     ?BOOL_CONFIG_FIELD("Deliver payloads with event notifications", deliver_payloads),
+     ?BOOL_CONFIG_FIELD("Notify subscribers when the node configuration changes", notify_config),
+     ?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),
+     ?INTEGER_CONFIG_FIELD("Max # of items to persist", max_items),
+     ?BOOL_CONFIG_FIELD("Whether to allow subscriptions", subscribe),
+     ?ALIST_CONFIG_FIELD("Specify the subscriber model", subscription_model,
+                        [open]),
+     ?ALIST_CONFIG_FIELD("Specify the publisher model", publish_model,
+                        [publishers, subscribers, open]),
+     ?INTEGER_CONFIG_FIELD("Max payload size in bytes", max_payload_size),
+     ?BOOL_CONFIG_FIELD("Send items to new subscribers", send_item_subscribe),
+     ?BOOL_CONFIG_FIELD("Only deliver notifications to available users", presence_based_delivery),
+     ?JLIST_CONFIG_FIELD("Specify the current subscription approver", current_approver,
+                        get_owners_jids(Info))
+    ].
+
+
+set_node_config(Host, From, Node, Els, Lang) ->
+    case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
+       [#pubsub_node{info = Info} = N] ->
+           case get_affiliation(Info, From) of
+               owner ->
+                   case xml:remove_cdata(Els) of
+                       [{xmlelement, "x", _Attrs1, _Els1} = XEl] ->
+                           case {xml:get_tag_attr_s("xmlns", XEl),
+                                 xml:get_tag_attr_s("type", XEl)} of
+                               {?NS_XDATA, "cancel"} ->
+                                   {result, []};
+                               {?NS_XDATA, "submit"} ->
+                                   CurOpts = Info#nodeinfo.options,
+                                   set_node_config1(
+                                     Host, From, Node, XEl, CurOpts, Lang);
+                               _ ->
+                                   {error, ?ERR_BAD_REQUEST}
+                           end;
+                       _ ->
+                           {error, ?ERR_BAD_REQUEST}
+                   end;
+               _ ->
+                   {error, ?ERR_NOT_AUTHORIZED}
+           end;
+       _ ->
+           {error, ?ERR_ITEM_NOT_FOUND}
+    end.
+
+
+set_node_config1(Host, From, Node, XEl, CurOpts, Lang) ->
+    XData = jlib:parse_xdata_submit(XEl),
+    case XData of
+       invalid ->
+           {error, ?ERR_BAD_REQUEST};
+       _ ->
+           case set_xoption(XData, CurOpts) of
+               NewOpts when is_list(NewOpts) ->
+                   change_node_opts(Host, NewOpts, Node, Lang);
+               Err ->
+                   Err
+           end
+    end.
+
+add_opt(Key, Value, Opts) ->
+    Opts1 = lists:keydelete(Key, 1, Opts),
+    [{Key, Value} | Opts1].
+
+
+-define(SET_BOOL_XOPT(Opt, Val),
+       case Val of
+           "0" -> set_xoption(Opts, add_opt(Opt, false, NewOpts));
+           "1" -> set_xoption(Opts, add_opt(Opt, true, NewOpts));
+           _ -> {error, ?ERR_BAD_REQUEST}
+       end).
+
+-define(SET_STRING_XOPT(Opt, Val),
+       set_xoption(Opts, add_opt(Opt, Val, NewOpts))).
+
+-define(SET_INTEGER_XOPT(Opt, Val, Min, Max),
+       case catch list_to_integer(Val) of
+           IVal when is_integer(IVal),
+                     IVal >= Min,
+                     IVal =< Max ->
+               set_xoption(Opts, add_opt(Opt, IVal, NewOpts));
+           _ ->
+               {error, ?ERR_BAD_REQUEST}
+       end).
+
+-define(SET_ALIST_XOPT(Opt, Val, Vals),
+       case lists:member(Val, [atom_to_list(V) || V <- Vals]) of
+           true ->
+               set_xoption(Opts, add_opt(Opt, list_to_atom(Val), NewOpts));
+           false ->
+               {error, ?ERR_BAD_REQUEST}
+       end).
+
+
+set_xoption([], NewOpts) ->
+    NewOpts;
+set_xoption([{"FORM_TYPE", _} | Opts], NewOpts) ->
+    set_xoption(Opts, NewOpts);
+set_xoption([{"pubsub#deliver_payloads", [Val]} | Opts], NewOpts) ->
+    ?SET_BOOL_XOPT(deliver_payloads, Val);
+set_xoption([{"pubsub#notify_config", [Val]} | Opts], NewOpts) ->
+    ?SET_BOOL_XOPT(notify_config, Val);
+set_xoption([{"pubsub#notify_delete", [Val]} | Opts], NewOpts) ->
+    ?SET_BOOL_XOPT(notify_delete, Val);
+set_xoption([{"pubsub#notify_retract", [Val]} | Opts], NewOpts) ->
+    ?SET_BOOL_XOPT(notify_retract, Val);
+set_xoption([{"pubsub#persist_items", [Val]} | Opts], NewOpts) ->
+    ?SET_BOOL_XOPT(persist_items, Val);
+set_xoption([{"pubsub#max_items", [Val]} | Opts], NewOpts) ->
+    ?SET_INTEGER_XOPT(max_items, Val, 0, ?MAXITEMS);
+set_xoption([{"pubsub#subscribe", [Val]} | Opts], NewOpts) ->
+    ?SET_BOOL_XOPT(subscribe, Val);
+set_xoption([{"pubsub#subscription_model", [Val]} | Opts], NewOpts) ->
+    ?SET_ALIST_XOPT(subscription_model, Val, [open]);
+set_xoption([{"pubsub#publish_model", [Val]} | Opts], NewOpts) ->
+    ?SET_ALIST_XOPT(publish_model, Val, [publishers, subscribers, open]);
+set_xoption([{"pubsub#max_payload_size", [Val]} | Opts], NewOpts) ->
+    ?SET_INTEGER_XOPT(max_payload_size, Val, 0, ?MAX_PAYLOAD_SIZE);
+set_xoption([{"pubsub#send_item_subscribe", [Val]} | Opts], NewOpts) ->
+    ?SET_BOOL_XOPT(send_item_subscribe, Val);
+set_xoption([{"pubsub#presence_based_delivery", [Val]} | Opts], NewOpts) ->
+    ?SET_BOOL_XOPT(presence_based_delivery, Val);
+set_xoption([{"pubsub#current_approver", _} | Opts], NewOpts) ->
+    % TODO
+    set_xoption(Opts, NewOpts);
+%set_xoption([{"title", [Val]} | Opts], NewOpts) ->
+%    ?SET_STRING_XOPT(title, Val);
+set_xoption([_ | _Opts], _NewOpts) ->
+    {error, ?ERR_BAD_REQUEST}.
+
+
+change_node_opts(Host, NewOpts, Node, Lang) ->
+    F = fun() ->
+               case mnesia:read({pubsub_node, {Host, Node}}) of
+                   [#pubsub_node{info = Info} = N] ->
+                       NewInfo = Info#nodeinfo{options = NewOpts},
+                       mnesia:write(
+                         N#pubsub_node{info = NewInfo}),
+                       {result, []};
+                   [] ->
+                       {error, ?ERR_ITEM_NOT_FOUND}
+               end
+       end,
+    case mnesia:transaction(F) of
+       {atomic, {error, _} = Error} ->
+           Error;
+       {atomic, {result, Res}} ->
+           broadcast_config_notification(Host, Node, Lang),
+           {result, Res};
+       _ ->
+           {error, ?ERR_INTERNAL_SERVER_ERROR}
+    end.
+
+
+
+
+
+find_my_host(LServer) ->
+    Parts = string:tokens(LServer, "."),
+    find_my_host(Parts, ?MYHOSTS).
+
+find_my_host([], _Hosts) ->
+    ?MYNAME;
+find_my_host([_ | Tail] = Parts, Hosts) ->
+    Domain = parts_to_string(Parts),
+    case lists:member(Domain, Hosts) of
+       true ->
+           Domain;
+       false ->
+           find_my_host(Tail, Hosts)
+    end.
+
+parts_to_string(Parts) ->
+    string:strip(lists:flatten(lists:map(fun(S) -> [S, $.] end, Parts)),
+                right, $.).
+
+
+
 update_table(Host) ->
     Fields = record_info(fields, pubsub_node),
     case mnesia:table_info(pubsub_node, attributes) of
@@ -1222,3 +1616,4 @@ update_table(Host) ->
 
 
 
+