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