mod_service_directories/mod_service_directories.lua
changeset 552 d1e83cb12885
child 759 6531a029fce5
equal deleted inserted replaced
551:859bf77b9fbf 552:d1e83cb12885
       
     1 -- Prosody IM
       
     2 -- Copyright (C) 2011 Waqas Hussain
       
     3 -- 
       
     4 -- This project is MIT/X11 licensed. Please see the
       
     5 -- COPYING file in the source package for more information.
       
     6 --
       
     7 
       
     8 -- An implementation of [XEP-0309: Service Directories]
       
     9 
       
    10 -- Imports and defines
       
    11 
       
    12 local st = require "util.stanza";
       
    13 local jid_split = require "util.jid".split;
       
    14 local adhoc_new = module:require "adhoc".new;
       
    15 local to_ascii = require "util.encodings".idna.to_ascii;
       
    16 local nameprep = require "util.encodings".stringprep.nameprep;
       
    17 local core_post_stanza = core_post_stanza;
       
    18 local pairs, ipairs = pairs, ipairs;
       
    19 local module = module;
       
    20 local hosts = hosts;
       
    21 
       
    22 local subscription_from = {};
       
    23 local subscription_to = {};
       
    24 local contact_features = {};
       
    25 local contact_vcards = {};
       
    26 
       
    27 -- Advertise in disco
       
    28 
       
    29 module:add_identity("server", "directory", "Prosody");
       
    30 module:add_feature("urn:xmpp:server-presence");
       
    31 
       
    32 -- Handle subscriptions
       
    33 
       
    34 module:hook("presence/host", function(event) -- inbound presence to the host
       
    35 	local origin, stanza = event.origin, event.stanza;
       
    36 
       
    37 	local node, host, resource = jid_split(stanza.attr.from);
       
    38 	if stanza.attr.from ~= host then return; end -- not from a host
       
    39 
       
    40 	local t = stanza.attr.type;
       
    41 	if t == "probe" then
       
    42 		core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = host, id = stanza.attr.id }));
       
    43 	elseif t == "subscribe" then
       
    44 		subscription_from[host] = true;
       
    45 		core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = host, id = stanza.attr.id, type = "subscribed" }));
       
    46 		core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = host, id = stanza.attr.id }));
       
    47 		add_contact(host);
       
    48 	elseif t == "subscribed" then
       
    49 		subscription_to[host] = true;
       
    50 		query_host(host);
       
    51 	elseif t == "unsubscribe" then
       
    52 		subscription_from[host] = nil;
       
    53 		core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = host, id = stanza.attr.id, type = "unsubscribed" }));
       
    54 		remove_contact(host);
       
    55 	elseif t == "unsubscribed" then
       
    56 		subscription_to[host] = nil;
       
    57 		remove_contact(host);
       
    58 	end
       
    59 	return true;
       
    60 end, 10); -- priority over mod_presence
       
    61 
       
    62 function remove_contact(host, id)
       
    63 	contact_features[host] = nil;
       
    64 	contact_vcards[host] = nil;
       
    65 	if subscription_to[host] then
       
    66 		subscription_to[host] = nil;
       
    67 		core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = host, id = id, type = "unsubscribe" }));
       
    68 	end
       
    69 	if subscription_from[host] then
       
    70 		subscription_from[host] = nil;
       
    71 		core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = host, id = id, type = "unsubscribed" }));
       
    72 	end
       
    73 end
       
    74 function add_contact(host, id)
       
    75 	if not subscription_to[host] then
       
    76 		core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = host, id = id, type = "subscribe" }));
       
    77 	end
       
    78 end
       
    79 
       
    80 -- Admin ad-hoc command to subscribe
       
    81 
       
    82 local function add_contact_handler(self, data, state)
       
    83 	local layout = {
       
    84 		title = "Adding a Server Buddy";
       
    85 		instructions = "Fill out this form to add a \"server buddy\".";
       
    86 
       
    87 		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
       
    88 		{ name = "peerjid", type = "jid-single", required = true, label = "The server to add" };
       
    89 	};
       
    90 
       
    91 	if not state then
       
    92 		return { status = "executing", form = layout }, "executing";
       
    93 	elseif data.action == "canceled" then
       
    94 		return { status = "canceled" };
       
    95 	else
       
    96 		local fields = layout:data(data);
       
    97 		local peerjid = nameprep(fields.peerjid);
       
    98 		if not peerjid or peerjid == "" or #peerjid > 1023 or not to_ascii(peerjid) then
       
    99 			return { status = "completed", error = { message = "Invalid JID" } };
       
   100 		end
       
   101 		add_contact(peerjid);
       
   102 		return { status = "completed" };
       
   103 	end
       
   104 end
       
   105 
       
   106 local add_contact_command = adhoc_new("Adding a Server Buddy", "http://jabber.org/protocol/admin#server-buddy", add_contact_handler, "admin");
       
   107 module:add_item("adhoc", add_contact_command);
       
   108 
       
   109 -- Disco query remote host
       
   110 function query_host(host)
       
   111 	local stanza = st.iq({ from = module.host, to = host, type = "get", id = "mod_service_directories:disco" })
       
   112 		:query("http://jabber.org/protocol/disco#info");
       
   113 	core_post_stanza(hosts[module.host], stanza);
       
   114 end
       
   115 
       
   116 -- Handle disco query result
       
   117 module:hook("iq-result/bare/mod_service_directories:disco", function(event)
       
   118 	local origin, stanza = event.origin, event.stanza;
       
   119 
       
   120 	if not subscription_to[stanza.attr.from] then return; end -- not from a contact
       
   121 	local host = stanza.attr.from;
       
   122 
       
   123 	local query = stanza:get_child("query", "http://jabber.org/protocol/disco#info")
       
   124 	if not query then return; end
       
   125 
       
   126 	-- extract disco features
       
   127 	local features = {};
       
   128 	for _,tag in ipairs(query.tags) do
       
   129 		if tag.name == "feature" and tag.attr.var then
       
   130 			features[tag.attr.var] = true;
       
   131 		end
       
   132 	end
       
   133 	contact_features[host] = features;
       
   134 
       
   135 	if features["urn:ietf:params:xml:ns:vcard-4.0"] then
       
   136 		local stanza = st.iq({ from = module.host, to = host, type = "get", id = "mod_service_directories:vcard" })
       
   137 			:tag("vcard", { xmlns = "urn:ietf:params:xml:ns:vcard-4.0" });
       
   138 		core_post_stanza(hosts[module.host], stanza);
       
   139 	end
       
   140 	return true;
       
   141 end);
       
   142 
       
   143 -- Handle vcard result
       
   144 module:hook("iq-result/bare/mod_service_directories:vcard", function(event)
       
   145 	local origin, stanza = event.origin, event.stanza;
       
   146 
       
   147 	if not subscription_to[stanza.attr.from] then return; end -- not from a contact
       
   148 	local host = stanza.attr.from;
       
   149 
       
   150 	local vcard = stanza:get_child("vcard", "urn:ietf:params:xml:ns:vcard-4.0");
       
   151 	if not vcard then return; end
       
   152 
       
   153 	contact_vcards[host] = st.clone(vcard);
       
   154 	return true;
       
   155 end);
       
   156 
       
   157 -- PubSub
       
   158 
       
   159 -- TODO the following should be replaced by mod_pubsub
       
   160 
       
   161 module:hook("iq-get/host/http://jabber.org/protocol/pubsub:pubsub", function(event)
       
   162 	local origin, stanza = event.origin, event.stanza;
       
   163 	local payload = stanza.tags[1];
       
   164 
       
   165 	local items = payload:get_child("items", "http://jabber.org/protocol/pubsub");
       
   166 	if items and items.attr.node == "urn:xmpp:contacts" then
       
   167 		local reply = st.reply(stanza)
       
   168 			:tag("pubsub", { xmlns = "http://jabber.org/protocol/pubsub" })
       
   169 				:tag("items", { node = "urn:xmpp:contacts" });
       
   170 		for host, vcard in pairs(contact_vcards) do
       
   171 			reply:tag("item", { id = host })
       
   172 				:add_child(vcard)
       
   173 			:up();
       
   174 		end
       
   175 		origin.send(reply);
       
   176 		return true;
       
   177 	end
       
   178 end);
       
   179