]> code.delx.au - pymsnt/blob - src/disco.py
Trunk is broken. Don't use!
[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 else:
150 searchjid = to
151 for (feature, handler) in self.features.get(searchjid, []):
152 if feature == xmlns and handler:
153 LogEvent(INFO, "Handler found")
154 handler(el)
155 return
156
157 # Still hasn't been handled
158 LogEvent(WARN, "", "Unknown Iq request")
159 self.sendIqError(to=fro, fro=to, ID=ID, xmlns=DISCO, etype="cancel", condition="feature-not-implemented")
160
161 def sendDiscoInfoResponse(self, to, ID, ulang, jid):
162 """ Send a service discovery disco#info stanza to the given 'to'. 'jid' is the JID that was queried. """
163 LogEvent(INFO)
164 iq = Element((None, "iq"))
165 iq.attributes["type"] = "result"
166 iq.attributes["from"] = jid
167 iq.attributes["to"] = to
168 if(ID):
169 iq.attributes["id"] = ID
170 query = iq.addElement("query")
171 query.attributes["xmlns"] = DISCO_INFO
172
173 searchjid = jid
174 if(jid.find('@') > 0): searchjid = "USER"
175 # Add any identities
176 for (category, ctype, name) in self.identities.get(searchjid, []):
177 identity = query.addElement("identity")
178 identity.attributes["category"] = category
179 identity.attributes["type"] = ctype
180 identity.attributes["name"] = name
181
182 # Add any supported features
183 for (var, handler) in self.features.get(searchjid, []):
184 feature = query.addElement("feature")
185 feature.attributes["var"] = var
186
187 self.pytrans.send(iq)
188
189 def sendDiscoItemsResponse(self, to, ID, ulang, jid):
190 """ Send a service discovery disco#items stanza to the given 'to'. 'jid' is the JID that was queried. """
191 LogEvent(INFO)
192 iq = Element((None, "iq"))
193 iq.attributes["type"] = "result"
194 iq.attributes["from"] = jid
195 iq.attributes["to"] = to
196 if(ID):
197 iq.attributes["id"] = ID
198 query = iq.addElement("query")
199 query.attributes["xmlns"] = DISCO_ITEMS
200
201 searchjid = jid
202 if(jid.find('@') > 0): searchjid = "USER"
203 for node in self.nodes.get(searchjid, []):
204 handler, name, rootnode = self.nodes[jid][node]
205 if rootnode:
206 name = getattr(lang.get(ulang), name)
207 item = query.addElement("item")
208 item.attributes["jid"] = jid
209 item.attributes["node"] = node
210 item.attributes["name"] = name
211
212 self.pytrans.send(iq)
213
214
215 def sendIqError(self, to, fro, ID, xmlns, etype, condition):
216 """ Sends an IQ error response. See the XMPP RFC for details on the fields. """
217 el = Element((None, "iq"))
218 el.attributes["to"] = to
219 el.attributes["from"] = fro
220 if(ID):
221 el.attributes["id"] = ID
222 el.attributes["type"] = "error"
223 error = el.addElement("error")
224 error.attributes["type"] = etype
225 error.attributes["code"] = str(utils.errorCodeMap[condition])
226 cond = error.addElement(condition)
227 self.pytrans.send(el)
228
229
230 class DiscoRequest:
231 def __init__(self, pytrans, jid):
232 LogEvent(INFO)
233 self.pytrans, self.jid = pytrans, jid
234
235 def doDisco(self):
236 ID = self.pytrans.makeMessageID()
237 iq = Element((None, "iq"))
238 iq.attributes["to"] = self.jid
239 iq.attributes["from"] = config.jid
240 iq.attributes["type"] = "get"
241 query = iq.addElement("query")
242 query.attributes["xmlns"] = DISCO_INFO
243
244 d = self.pytrans.discovery.sendIq(iq)
245 d.addCallback(self.discoResponse)
246 d.addErrback(self.discoFail)
247 return d
248
249 def discoResponse(self, el):
250 iqType = el.getAttribute("type")
251 if iqType != "result":
252 return []
253
254 fro = el.getAttribute("from")
255
256 features = []
257
258 query = el.getElement("query")
259 if not query:
260 return []
261
262 for child in query.elements():
263 if child.name == "feature":
264 features.append(child.getAttribute("var"))
265
266 return features
267
268 def discoFail(self):
269 return []
270
271
272