Diff

mod_sift/mod_sift.lua @ 137:34a3ec3e7dc3

mod_sift: Initial commit.
author Waqas Hussain <waqas20@gmail.com>
date Sat, 06 Mar 2010 21:47:20 +0500
child 138:61e1203e9e66
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_sift/mod_sift.lua	Sat Mar 06 21:47:20 2010 +0500
@@ -0,0 +1,189 @@
+
+local st = require "util.stanza";
+local jid_bare = require "util.jid".bare;
+
+-- advertise disco features
+module:add_feature("urn:xmpp:sift:1");
+
+-- supported features
+module:add_feature("urn:xmpp:sift:stanzas:iq");
+module:add_feature("urn:xmpp:sift:stanzas:message");
+module:add_feature("urn:xmpp:sift:stanzas:presence");
+module:add_feature("urn:xmpp:sift:recipients:all");
+module:add_feature("urn:xmpp:sift:senders:all");
+
+-- allowed values of 'sender' and 'recipient' attributes
+local senders = {
+	["all"] = true;
+	["local"] = true;
+	["others"] = true;
+	["remote"] = true;
+	["self"] = true;
+};
+local recipients = {
+	["all"] = true;
+	["bare"] = true;
+	["full"] = true;
+};
+
+-- this function converts a <message/>, <presence/> or <iq/> element in
+-- the SIFT namespace into a hashtable, for easy lookup
+local function to_hashtable(element)
+	if element ~= nil then
+		local hash = {};
+		-- make sure the sender and recipient attributes has a valid value
+		hash.sender = element.attr.sender or "all";
+		if not senders[hash.sender] then return false; end -- bad value, returning false
+		hash.recipient = element.attr.recipient or "all";
+		if not recipients[hash.recipient] then return false; end -- bad value, returning false
+		-- next we loop over all <allow/> elements
+		for _, tag in ipairs(element) do
+			if tag.name == "allow" and tag.attr.xmlns == "urn:xmpp:sift:1" then
+				-- make sure the element is valid
+				if not tag.attr.name or not tag.attr.ns then return false; end -- missing required attributes, returning false
+				hash[tag.attr.ns.."|"..tag.attr.name] = true;
+				hash.allowed = true; -- just a flag indicating we have some elements allowed
+			end
+		end
+		return hash;
+	end
+end
+
+local data = {}; -- table with all our data
+
+-- handle SIFT set
+module:hook("iq/self/urn:xmpp:sift:1:sift", function(event)
+	local origin, stanza = event.origin, event.stanza;
+	if stanza.attr.type == "set" then
+		local sifttag = stanza.tags[1]; -- <sift/>
+		
+		-- first, get the elements we are interested in
+		local message = sifttag:get_child("message");
+		local presence = sifttag:get_child("presence");
+		local iq = sifttag:get_child("iq");
+		
+		-- for quick lookup, convert the elements into hashtables
+		message = to_hashtable(message);
+		presence = to_hashtable(presence);
+		iq = to_hashtable(iq);
+		
+		-- make sure elements were valid
+		if message == false or presence == false or iq == false then
+			origin.send(st.error_reply(stanza, "modify", "bad-request"));
+			return true;
+		end
+		
+		local existing = data[origin.full_jid] or {}; -- get existing data, if any
+		data[origin.full_jid] = { presence = presence, message = message, iq = iq }; -- store new data
+		
+		origin.send(st.reply(stanza)); -- send back IQ result
+		
+		if not existing.presence and not origin.presence and presence then
+			-- TODO send probes
+		end
+		return true;
+	end
+end);
+
+-- handle user disconnect
+module:hook("resource-unbind", function(event)
+	data[event.origin.full_jid] = nil; -- discard data
+end);
+
+-- IQ handler
+module:hook("iq/full", function(event)
+	local origin, stanza = event.origin, event.stanza;
+	local siftdata = data[stanza.attr.to];
+	if siftdata and siftdata.iq then -- we seem to have an IQ filter
+		local tag = stanza.tags[1]; -- the IQ child
+		if not siftdata.iq[tag.attr.xmlns.."|"..tag.name] then
+			-- element not allowed; sending back generic error
+			origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+			return true;
+		end
+	end
+end, 50);
+
+-- Message to full JID handler
+module:hook("message/full", function(event)
+	local origin, stanza = event.origin, event.stanza;
+	local siftdata = data[stanza.attr.to];
+	if siftdata and siftdata.message then -- we seem to have an message filter
+		local allowed = false;
+		for _, childtag in ipairs(stanza.tags) do
+			if siftdata.message[(childtag.attr.xmlns or "jabber:client").."|"..childtag.name] then
+				allowed = true;
+			end
+		end
+		if not allowed then
+			-- element not allowed; sending back generic error
+			origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+			-- FIXME maybe send to offline storage
+			return true;
+		end
+	end
+end, 50);
+
+-- Message to bare JID handler
+module:hook("message/bare", function(event)
+	local origin, stanza = event.origin, event.stanza;
+	local user = bare_sessions[jid_bare(stanza.attr.to)];
+	local allowed = false;
+	for _, session in pairs(user or {}) do
+		local siftdata = data[session.full_jid];
+		if siftdata and siftdata.message then -- we seem to have an message filter
+			for _, childtag in ipairs(stanza.tags) do
+				if siftdata.message[(childtag.attr.xmlns or "jabber:client").."|"..childtag.name] then
+					allowed = true;
+				end
+			end
+		end
+	end
+	if not allowed then
+		-- element not allowed; sending back generic error
+		origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+		-- FIXME maybe send to offline storage
+		return true;
+	end
+end, 50);
+
+-- Presence to full JID handler
+module:hook("presence/full", function(event)
+	local origin, stanza = event.origin, event.stanza;
+	local siftdata = data[stanza.attr.to];
+	if siftdata and siftdata.presence then -- we seem to have an presence filter
+		local allowed = false;
+		for _, childtag in ipairs(stanza.tags) do
+			if siftdata.presence[(childtag.attr.xmlns or "jabber:client").."|"..childtag.name] then
+				allowed = true;
+			end
+		end
+		if not allowed then
+			-- element not allowed; sending back generic error
+			--origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+			return true;
+		end
+	end
+end, 50);
+
+-- Presence to bare JID handler
+module:hook("presence/bare", function(event)
+	local origin, stanza = event.origin, event.stanza;
+	local user = bare_sessions[jid_bare(stanza.attr.to)];
+	local allowed = false;
+	for _, session in pairs(user or {}) do
+		local siftdata = data[session.full_jid];
+		if siftdata and siftdata.presence then -- we seem to have an presence filter
+			for _, childtag in ipairs(stanza.tags) do
+				if siftdata.presence[(childtag.attr.xmlns or "jabber:client").."|"..childtag.name] then
+					allowed = true;
+				end
+			end
+		end
+	end
+	if not allowed then
+		-- element not allowed; sending back generic error
+		--origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+		return true;
+	end
+end, 50);