]> code.delx.au - pymsnt/blob - src/disco.py
Updated roster-subsync code in 0.10.x and trunk to fix a critical bug (clients follow...
[pymsnt] / src / disco.py
1 # Copyright 2004-2005 James Bunton <james@delx.cjb.net>
2 # Licensed for distribution under the GPL version 2, check COPYING for details
3
4 import utils
5 from tlib.xmlw import Element, jid
6 from twisted.internet.defer import Deferred
7 from twisted.internet import reactor
8 from debug import LogEvent, INFO, WARN, ERROR
9 import sys
10 import config
11 import legacy
12 import lang
13
14 XMPP_STANZAS = "urn:ietf:params:xml:ns:xmpp-stanzas"
15 DISCO = "http://jabber.org/protocol/disco"
16 DISCO_ITEMS = DISCO + "#items"
17 DISCO_INFO = DISCO + "#info"
18 COMMANDS = "http://jabber.org/protocol/commands"
19 CAPS = "http://jabber.org/protocol/caps"
20 SUBSYNC = "http://delx.cjb.net/protocol/roster-subsync"
21 MUC = "http://jabber.org/protocol/muc"
22 MUC_USER = MUC + "#user"
23 FEATURE_NEG = "http://jabber.org/protocol/feature-neg"
24 SI = "http://jabber.org/protocol/si"
25 FT = "http://jabber.org/protocol/si/profile/file-transfer"
26 S5B = "http://jabber.org/protocol/bytestreams"
27 IBB = "http://jabber.org/protocol/ibb"
28 IQGATEWAY = "jabber:iq:gateway"
29 IQVERSION = "jabber:iq:version"
30 IQREGISTER = "jabber:iq:register"
31 IQROSTER = "jabber:iq:roster"
32 IQAVATAR = "jabber:iq:avatar"
33 IQOOB = "jabber:iq:oob"
34 XOOB = "jabber:x:oob"
35 XCONFERENCE = "jabber:x:conference"
36 XEVENT = "jabber:x:event"
37 XDELAY = "jabber:x:delay"
38 XAVATAR = "jabber:x:avatar"
39 XDATA = "jabber:x:data"
40 STORAGEAVATAR = "storage:client:avatar"
41 XVCARDUPDATE = "vcard-temp:x:update"
42 VCARDTEMP = "vcard-temp"
43
44
45
46
47 class ServerDiscovery:
48 """ Handles everything IQ related. You can send IQ stanzas and receive a Deferred
49 to notify you when a response comes, or if there's a timeout.
50 Also manages discovery for server & client """
51
52 # TODO rename this file & class to something more sensible
53
54 def __init__ (self, pytrans):
55 LogEvent(INFO)
56 self.pytrans = pytrans
57 self.identities = {}
58 self.features = {}
59 self.nodes = {}
60 self.deferredIqs = {} # A dict indexed by (jid, id) of deferreds to fire
61
62 self.addFeature(DISCO, None, config.jid)
63 self.addFeature(DISCO, None, "USER")
64
65 def sendIq(self, el, timeout=15):
66 """ Used for sending IQ packets.
67 The id attribute for the IQ will be autogenerated if it is not there yet.
68 Returns a deferred which will fire with the matching IQ response as it's sole argument. """
69 def checkDeferred():
70 if(not d.called):
71 d.errback()
72 del self.deferredIqs[(jid, ID)]
73
74 jid = el.getAttribute("to")
75 ID = el.getAttribute("id")
76 if(not ID):
77 ID = self.pytrans.makeMessageID()
78 el.attributes["id"] = ID
79 self.pytrans.send(el)
80 d = Deferred()
81 self.deferredIqs[(jid, ID)] = d
82 reactor.callLater(timeout, checkDeferred)
83 return d
84
85 def addIdentity(self, category, ctype, name, jid):
86 """ Adds an identity to this JID's discovery profile. If jid == "USER" then MSN users will get this identity. """
87 LogEvent(INFO)
88 if not self.identities.has_key(jid):
89 self.identities[jid] = []
90 self.identities[jid].append((category, ctype, name))
91
92 def addFeature(self, var, handler, jid):
93 """ Adds a feature to this JID's discovery profile. If jid == "USER" then MSN users will get this feature. """
94 LogEvent(INFO)
95 if not self.features.has_key(jid):
96 self.features[jid] = []
97 self.features[jid].append((var, handler))
98
99 def addNode(self, node, handler, name, jid, rootnode):
100 """ Adds a node to this JID's discovery profile. If jid == "USER" then MSN users will get this node. """
101 LogEvent(INFO)
102 if not self.nodes.has_key(jid):
103 self.nodes[jid] = {}
104 self.nodes[jid][node] = (handler, name, rootnode)
105
106 def onIq(self, el):
107 """ Decides what to do with an IQ """
108 fro = el.getAttribute("from")
109 to = el.getAttribute("to")
110 ID = el.getAttribute("id")
111 iqType = el.getAttribute("type")
112 ulang = utils.getLang(el)
113 try: # Stringprep
114 froj = jid.intern(fro)
115 to = jid.intern(to).full()
116 except Exception, e:
117 LogEvent(WARN, "", "Dropping IQ because of stringprep error")
118
119 # Check if it's a response to a send IQ
120 if self.deferredIqs.has_key((fro, ID)) and (iqType == "error" or iqType == "result"):
121 LogEvent(INFO, "", "Doing callback")
122 self.deferredIqs[(fro, ID)].callback(el)
123 del self.deferredIqs[(fro, ID)]
124 return
125
126 if not (iqType == "get" or iqType == "set"): return # Not interested
127
128 LogEvent(INFO, "", "Looking for handler")
129
130 for query in el.elements():
131 xmlns = query.defaultUri
132 node = query.getAttribute("node")
133
134 if xmlns.startswith(DISCO) and node:
135 if self.nodes.has_key(to) and self.nodes[to].has_key(node) and self.nodes[to][node][0]:
136 self.nodes[to][node][0](el)
137 return
138 else:
139 # If the node we're browsing wasn't found, fall through and display the root disco
140 self.sendDiscoInfoResponse(to=fro, ID=ID, ulang=ulang, jid=to)
141 return
142 elif xmlns == DISCO_INFO:
143 self.sendDiscoInfoResponse(to=fro, ID=ID, ulang=ulang, jid=to)
144 return
145 elif xmlns == DISCO_ITEMS:
146 self.sendDiscoItemsResponse(to=fro, ID=ID, ulang=ulang, jid=to)
147 return
148
149 if to.find('@') > 0:
150 searchjid = "USER"
151 elif config.compjid and to == config.compjid:
152 searchjid = config.jid
153 else:
154 searchjid = to
155 for (feature, handler) in self.features.get(searchjid, []):
156 if feature == xmlns and handler:
157 LogEvent(INFO, "Handler found")
158 handler(el)
159 return
160
161 # Still hasn't been handled
162 LogEvent(WARN, "", "Unknown Iq request")
163 self.sendIqError(to=fro, fro=to, ID=ID, xmlns=DISCO, etype="cancel", condition="feature-not-implemented")
164
165 def sendDiscoInfoResponse(self, to, ID, ulang, jid):
166 """ Send a service discovery disco#info stanza to the given 'to'. 'jid' is the JID that was queried. """
167 LogEvent(INFO)
168 iq = Element((None, "iq"))
169 iq.attributes["type"] = "result"
170 iq.attributes["from"] = jid
171 iq.attributes["to"] = to
172 if(ID):
173 iq.attributes["id"] = ID
174 query = iq.addElement("query")
175 query.attributes["xmlns"] = DISCO_INFO
176
177 searchjid = jid
178 if jid.find('@') > 0: searchjid = "USER"
179 if config.compjid and jid == config.compjid: searchjid = config.jid
180 # Add any identities
181 for (category, ctype, name) in self.identities.get(searchjid, []):
182 identity = query.addElement("identity")
183 identity.attributes["category"] = category
184 identity.attributes["type"] = ctype
185 identity.attributes["name"] = name
186
187 # Add any supported features
188 for (var, handler) in self.features.get(searchjid, []):
189 feature = query.addElement("feature")
190 feature.attributes["var"] = var
191
192 self.pytrans.send(iq)
193
194 def sendDiscoItemsResponse(self, to, ID, ulang, jid):
195 """ Send a service discovery disco#items stanza to the given 'to'. 'jid' is the JID that was queried. """
196 LogEvent(INFO)
197 iq = Element((None, "iq"))
198 iq.attributes["type"] = "result"
199 iq.attributes["from"] = jid
200 iq.attributes["to"] = to
201 if(ID):
202 iq.attributes["id"] = ID
203 query = iq.addElement("query")
204 query.attributes["xmlns"] = DISCO_ITEMS
205
206 searchjid = jid
207 if jid.find('@') > 0: searchjid = "USER"
208 if config.compjid and jid == config.compjid: searchjid = config.jid
209 for node in self.nodes.get(searchjid, []):
210 handler, name, rootnode = self.nodes[jid][node]
211 if rootnode:
212 name = getattr(lang.get(ulang), name)
213 item = query.addElement("item")
214 item.attributes["jid"] = jid
215 item.attributes["node"] = node
216 item.attributes["name"] = name
217
218 self.pytrans.send(iq)
219
220
221 def sendIqError(self, to, fro, ID, xmlns, etype, condition):
222 """ Sends an IQ error response. See the XMPP RFC for details on the fields. """
223 el = Element((None, "iq"))
224 el.attributes["to"] = to
225 el.attributes["from"] = fro
226 if(ID):
227 el.attributes["id"] = ID
228 el.attributes["type"] = "error"
229 error = el.addElement("error")
230 error.attributes["type"] = etype
231 error.attributes["code"] = str(utils.errorCodeMap[condition])
232 cond = error.addElement(condition)
233 self.pytrans.send(el)
234
235
236 class DiscoRequest:
237 def __init__(self, pytrans, jid):
238 LogEvent(INFO)
239 self.pytrans, self.jid = pytrans, jid
240
241 def doDisco(self):
242 ID = self.pytrans.makeMessageID()
243 iq = Element((None, "iq"))
244 iq.attributes["to"] = self.jid
245 iq.attributes["from"] = config.jid
246 iq.attributes["type"] = "get"
247 query = iq.addElement("query")
248 query.attributes["xmlns"] = DISCO_INFO
249
250 d = self.pytrans.discovery.sendIq(iq)
251 d.addCallback(self.discoResponse)
252 d.addErrback(self.discoFail)
253 return d
254
255 def discoResponse(self, el):
256 iqType = el.getAttribute("type")
257 if iqType != "result":
258 return []
259
260 fro = el.getAttribute("from")
261
262 features = []
263
264 for child in el.elements():
265 if child.name == "query":
266 query = child
267 break
268 else:
269 return []
270
271 for child in query.elements():
272 if child.name == "feature":
273 features.append(child.getAttribute("var"))
274
275 return features
276
277 def discoFail(self):
278 return []
279
280
281