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