|
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 |