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