plugins/mod_disco.lua

1
-- Prosody IM
2
-- Copyright (C) 2008-2010 Matthew Wild
3
-- Copyright (C) 2008-2010 Waqas Hussain
4
-- 
5
-- This project is MIT/X11 licensed. Please see the
6
-- COPYING file in the source package for more information.
7
--
8
 
9
local get_children = require "core.hostmanager".get_children;
10
local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed;
11
local jid_split = require "util.jid".split;
12
local jid_bare = require "util.jid".bare;
13
local st = require "util.stanza"
14
local calculate_hash = require "util.caps".calculate_hash;
15
 
16
local disco_items = module:get_option("disco_items") or {};
17
do -- validate disco_items
18
	for _, item in ipairs(disco_items) do
19
		local err;
20
		if type(item) ~= "table" then
21
			err = "item is not a table";
22
		elseif type(item[1]) ~= "string" then
23
			err = "item jid is not a string";
24
		elseif item[2] and type(item[2]) ~= "string" then
25
			err = "item name is not a string";
26
		end
27
		if err then
28
			module:log("error", "option disco_items is malformed: %s", err);
29
			disco_items = {}; -- TODO clean up data instead of removing it?
30
			break;
31
		end
32
	end
33
end
34
 
35
module:add_identity("server", "im", "Prosody"); -- FIXME should be in the non-existing mod_router
36
module:add_feature("http://jabber.org/protocol/disco#info");
37
module:add_feature("http://jabber.org/protocol/disco#items");
38
 
39
-- Generate and cache disco result and caps hash
40
local _cached_server_disco_info, _cached_server_caps_feature, _cached_server_caps_hash;
41
local function build_server_disco_info()
42
	local query = st.stanza("query", { xmlns = "http://jabber.org/protocol/disco#info" });
43
	local done = {};
44
	for _,identity in ipairs(module:get_host_items("identity")) do
45
		local identity_s = identity.category.."\0"..identity.type;
46
		if not done[identity_s] then
47
			query:tag("identity", identity):up();
48
			done[identity_s] = true;
49
		end
50
	end
51
	for _,feature in ipairs(module:get_host_items("feature")) do
52
		if not done[feature] then
53
			query:tag("feature", {var=feature}):up();
54
			done[feature] = true;
55
		end
56
	end
57
	_cached_server_disco_info = query;
58
	_cached_server_caps_hash = calculate_hash(query);
59
	_cached_server_caps_feature = st.stanza("c", {
60
		xmlns = "http://jabber.org/protocol/caps";
61
		hash = "sha-1";
62
		node = "http://prosody.im";
63
		ver = _cached_server_caps_hash;
64
	});
65
end
66
local function clear_disco_cache()
67
	_cached_server_disco_info, _cached_server_caps_feature, _cached_server_caps_hash = nil, nil, nil;
68
end
69
local function get_server_disco_info()
70
	if not _cached_server_disco_info then build_server_disco_info(); end
71
	return _cached_server_disco_info;
72
end
73
local function get_server_caps_feature()
74
	if not _cached_server_caps_feature then build_server_disco_info(); end
75
	return _cached_server_caps_feature;
76
end
77
local function get_server_caps_hash()
78
	if not _cached_server_caps_hash then build_server_disco_info(); end
79
	return _cached_server_caps_hash;
80
end
81
 
82
module:hook("item-added/identity", clear_disco_cache);
83
module:hook("item-added/feature", clear_disco_cache);
84
module:hook("item-removed/identity", clear_disco_cache);
85
module:hook("item-removed/feature", clear_disco_cache);
86
 
87
-- Handle disco requests to the server
88
module:hook("iq/host/http://jabber.org/protocol/disco#info:query", function(event)
89
	local origin, stanza = event.origin, event.stanza;
90
	if stanza.attr.type ~= "get" then return; end
91
	local node = stanza.tags[1].attr.node;
92
	if node and node ~= "" and node ~= "http://prosody.im#"..get_server_caps_hash() then return; end -- TODO fire event?
93
	local reply_query = get_server_disco_info();
94
	reply_query.node = node;
95
	local reply = st.reply(stanza):add_child(reply_query);
96
	origin.send(reply);
97
	return true;
98
end);
99
module:hook("iq/host/http://jabber.org/protocol/disco#items:query", function(event)
100
	local origin, stanza = event.origin, event.stanza;
101
	if stanza.attr.type ~= "get" then return; end
102
	local node = stanza.tags[1].attr.node;
103
	if node and node ~= "" then return; end -- TODO fire event?
104
 
105
	local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items");
106
	for jid in pairs(get_children(module.host)) do
107
		reply:tag("item", {jid = jid}):up();
108
	end
109
	for _, item in ipairs(disco_items) do
110
		reply:tag("item", {jid=item[1], name=item[2]}):up();
111
	end
112
	origin.send(reply);
113
	return true;
114
end);
115
 
116
-- Handle caps stream feature
117
module:hook("stream-features", function (event)
118
	if event.origin.type == "c2s" then
119
		event.features:add_child(get_server_caps_feature());
120
	end
121
end);
122
 
123
-- Handle disco requests to user accounts
124
module:hook("iq/bare/http://jabber.org/protocol/disco#info:query", function(event)
125
	local origin, stanza = event.origin, event.stanza;
126
	if stanza.attr.type ~= "get" then return; end
127
	local node = stanza.tags[1].attr.node;
128
	if node and node ~= "" then return; end -- TODO fire event?
129
	local username = jid_split(stanza.attr.to) or origin.username;
130
	if not stanza.attr.to or is_contact_subscribed(username, module.host, jid_bare(stanza.attr.from)) then
131
		local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#info'});
132
		if not reply.attr.from then reply.attr.from = origin.username.."@"..origin.host; end -- COMPAT To satisfy Psi when querying own account
133
		module:fire_event("account-disco-info", { origin = origin, stanza = reply });
134
		origin.send(reply);
135
		return true;
136
	end
137
end);
138
module:hook("iq/bare/http://jabber.org/protocol/disco#items:query", function(event)
139
	local origin, stanza = event.origin, event.stanza;
140
	if stanza.attr.type ~= "get" then return; end
141
	local node = stanza.tags[1].attr.node;
142
	if node and node ~= "" then return; end -- TODO fire event?
143
	local username = jid_split(stanza.attr.to) or origin.username;
144
	if not stanza.attr.to or is_contact_subscribed(username, module.host, jid_bare(stanza.attr.from)) then
145
		local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#items'});
146
		if not reply.attr.from then reply.attr.from = origin.username.."@"..origin.host; end -- COMPAT To satisfy Psi when querying own account
147
		module:fire_event("account-disco-items", { origin = origin, stanza = reply });
148
		origin.send(reply);
149
		return true;
150
	end
151
end);