]>
code.delx.au - pymsnt/blob - src/disco.py
1 # Copyright 2004-2006 James Bunton <james@delx.cjb.net>
2 # Licensed for distribution under the GPL version 2, check COPYING for details
4 from debug
import LogEvent
, INFO
, WARN
, ERROR
6 from twisted
.internet
.defer
import Deferred
7 from twisted
.internet
import reactor
8 from twisted
.words
.xish
.domish
import Element
9 from twisted
.words
.protocols
.jabber
.jid
import internJID
17 XMPP_STANZAS
= "urn:ietf:params:xml:ns:xmpp-stanzas"
18 DISCO
= "http://jabber.org/protocol/disco"
19 DISCO_ITEMS
= DISCO
+ "#items"
20 DISCO_INFO
= DISCO
+ "#info"
21 COMMANDS
= "http://jabber.org/protocol/commands"
22 CAPS
= "http://jabber.org/protocol/caps"
23 SUBSYNC
= "http://delx.cjb.net/protocol/roster-subsync"
24 MUC
= "http://jabber.org/protocol/muc"
25 MUC_USER
= MUC
+ "#user"
26 FEATURE_NEG
= "http://jabber.org/protocol/feature-neg"
27 SI
= "http://jabber.org/protocol/si"
28 FT
= "http://jabber.org/protocol/si/profile/file-transfer"
29 S5B
= "http://jabber.org/protocol/bytestreams"
30 IBB
= "http://jabber.org/protocol/ibb"
31 IQGATEWAY
= "jabber:iq:gateway"
32 IQVERSION
= "jabber:iq:version"
33 IQREGISTER
= "jabber:iq:register"
34 IQROSTER
= "jabber:iq:roster"
35 IQAVATAR
= "jabber:iq:avatar"
36 IQOOB
= "jabber:iq:oob"
38 XCONFERENCE
= "jabber:x:conference"
39 XEVENT
= "jabber:x:event"
40 XDELAY
= "jabber:x:delay"
41 XAVATAR
= "jabber:x:avatar"
42 XDATA
= "jabber:x:data"
43 STORAGEAVATAR
= "storage:client:avatar"
44 XVCARDUPDATE
= "vcard-temp:x:update"
45 VCARDTEMP
= "vcard-temp"
50 class ServerDiscovery
:
51 """ Handles everything IQ related. You can send IQ stanzas and receive a Deferred
52 to notify you when a response comes, or if there's a timeout.
53 Also manages discovery for server & client """
55 # TODO rename this file & class to something more sensible
57 def __init__ (self
, pytrans
):
59 self
.pytrans
= pytrans
63 self
.deferredIqs
= {} # A dict indexed by (jid, id) of deferreds to fire
65 self
.addFeature(DISCO
, None, config
.jid
)
66 self
.addFeature(DISCO
, None, "USER")
68 def sendIq(self
, el
, timeout
=15):
69 """ Used for sending IQ packets.
70 The id attribute for the IQ will be autogenerated if it is not there yet.
71 Returns a deferred which will fire with the matching IQ response as it's sole argument. """
74 d
.errback(Exception("Timeout"))
75 del self
.deferredIqs
[(jid
, ID
)]
77 jid
= el
.getAttribute("to")
78 ID
= el
.getAttribute("id")
80 ID
= self
.pytrans
.makeMessageID()
81 el
.attributes
["id"] = ID
84 self
.deferredIqs
[(jid
, ID
)] = d
85 reactor
.callLater(timeout
, checkDeferred
)
88 def addIdentity(self
, category
, ctype
, name
, jid
):
89 """ Adds an identity to this JID's discovery profile. If jid == "USER" then MSN users will get this identity. """
91 if not self
.identities
.has_key(jid
):
92 self
.identities
[jid
] = []
93 self
.identities
[jid
].append((category
, ctype
, name
))
95 def addFeature(self
, var
, handler
, jid
):
96 """ Adds a feature to this JID's discovery profile. If jid == "USER" then MSN users will get this feature. """
98 if not self
.features
.has_key(jid
):
99 self
.features
[jid
] = []
100 self
.features
[jid
].append((var
, handler
))
102 def addNode(self
, node
, handler
, name
, jid
, rootnode
):
103 """ Adds a node to this JID's discovery profile. If jid == "USER" then MSN users will get this node. """
105 if not self
.nodes
.has_key(jid
):
107 self
.nodes
[jid
][node
] = (handler
, name
, rootnode
)
110 """ Decides what to do with an IQ """
111 fro
= el
.getAttribute("from")
112 to
= el
.getAttribute("to")
113 ID
= el
.getAttribute("id")
114 iqType
= el
.getAttribute("type")
115 ulang
= utils
.getLang(el
)
117 froj
= internJID(fro
)
118 to
= internJID(to
).full()
120 LogEvent(WARN
, "", "Dropping IQ because of stringprep error")
122 # Check if it's a response to a send IQ
123 if self
.deferredIqs
.has_key((fro
, ID
)) and (iqType
== "error" or iqType
== "result"):
124 LogEvent(INFO
, "", "Doing callback")
125 self
.deferredIqs
[(fro
, ID
)].callback(el
)
126 del self
.deferredIqs
[(fro
, ID
)]
129 if not (iqType
== "get" or iqType
== "set"): return # Not interested
131 LogEvent(INFO
, "", "Looking for handler")
133 for query
in el
.elements():
135 node
= query
.getAttribute("node")
137 if xmlns
.startswith(DISCO
) and node
:
138 if self
.nodes
.has_key(to
) and self
.nodes
[to
].has_key(node
) and self
.nodes
[to
][node
][0]:
139 self
.nodes
[to
][node
][0](el
)
142 # If the node we're browsing wasn't found, fall through and display the root disco
143 self
.sendDiscoInfoResponse(to
=fro
, ID
=ID
, ulang
=ulang
, jid
=to
)
145 elif xmlns
== DISCO_INFO
:
146 self
.sendDiscoInfoResponse(to
=fro
, ID
=ID
, ulang
=ulang
, jid
=to
)
148 elif xmlns
== DISCO_ITEMS
:
149 self
.sendDiscoItemsResponse(to
=fro
, ID
=ID
, ulang
=ulang
, jid
=to
)
154 elif config
.compjid
and to
== config
.compjid
:
155 searchjid
= config
.jid
158 for (feature
, handler
) in self
.features
.get(searchjid
, []):
159 if feature
== xmlns
and handler
:
160 LogEvent(INFO
, "Handler found")
164 # Still hasn't been handled
165 LogEvent(WARN
, "", "Unknown Iq request")
166 self
.sendIqError(to
=fro
, fro
=to
, ID
=ID
, xmlns
=DISCO
, etype
="cancel", condition
="feature-not-implemented")
168 def sendDiscoInfoResponse(self
, to
, ID
, ulang
, jid
):
169 """ Send a service discovery disco#info stanza to the given 'to'. 'jid' is the JID that was queried. """
171 iq
= Element((None, "iq"))
172 iq
.attributes
["type"] = "result"
173 iq
.attributes
["from"] = jid
174 iq
.attributes
["to"] = to
176 iq
.attributes
["id"] = ID
177 query
= iq
.addElement("query")
178 query
.attributes
["xmlns"] = DISCO_INFO
181 if jid
.find('@') > 0: searchjid
= "USER"
182 if config
.compjid
and jid
== config
.compjid
: searchjid
= config
.jid
184 for (category
, ctype
, name
) in self
.identities
.get(searchjid
, []):
185 identity
= query
.addElement("identity")
186 identity
.attributes
["category"] = category
187 identity
.attributes
["type"] = ctype
188 identity
.attributes
["name"] = name
190 # Add any supported features
191 for (var
, handler
) in self
.features
.get(searchjid
, []):
192 feature
= query
.addElement("feature")
193 feature
.attributes
["var"] = var
195 self
.pytrans
.send(iq
)
197 def sendDiscoItemsResponse(self
, to
, ID
, ulang
, jid
):
198 """ Send a service discovery disco#items stanza to the given 'to'. 'jid' is the JID that was queried. """
200 iq
= Element((None, "iq"))
201 iq
.attributes
["type"] = "result"
202 iq
.attributes
["from"] = jid
203 iq
.attributes
["to"] = to
205 iq
.attributes
["id"] = ID
206 query
= iq
.addElement("query")
207 query
.attributes
["xmlns"] = DISCO_ITEMS
210 if jid
.find('@') > 0: searchjid
= "USER"
211 if config
.compjid
and jid
== config
.compjid
: searchjid
= config
.jid
212 for node
in self
.nodes
.get(searchjid
, []):
213 handler
, name
, rootnode
= self
.nodes
[jid
][node
]
215 name
= getattr(lang
.get(ulang
), name
)
216 item
= query
.addElement("item")
217 item
.attributes
["jid"] = jid
218 item
.attributes
["node"] = node
219 item
.attributes
["name"] = name
221 self
.pytrans
.send(iq
)
224 def sendIqError(self
, to
, fro
, ID
, xmlns
, etype
, condition
):
225 """ Sends an IQ error response. See the XMPP RFC for details on the fields. """
226 el
= Element((None, "iq"))
227 el
.attributes
["to"] = to
228 el
.attributes
["from"] = fro
230 el
.attributes
["id"] = ID
231 el
.attributes
["type"] = "error"
232 error
= el
.addElement("error")
233 error
.attributes
["type"] = etype
234 error
.attributes
["code"] = str(utils
.errorCodeMap
[condition
])
235 cond
= error
.addElement(condition
)
236 cond
.attributes
["xmlns"] = XMPP_STANZAS
237 self
.pytrans
.send(el
)
241 def __init__(self
, pytrans
, jid
):
243 self
.pytrans
, self
.jid
= pytrans
, jid
246 ID
= self
.pytrans
.makeMessageID()
247 iq
= Element((None, "iq"))
248 iq
.attributes
["to"] = self
.jid
249 iq
.attributes
["from"] = config
.jid
250 iq
.attributes
["type"] = "get"
251 query
= iq
.addElement("query")
252 query
.attributes
["xmlns"] = DISCO_INFO
254 d
= self
.pytrans
.discovery
.sendIq(iq
)
255 d
.addCallback(self
.discoResponse
)
256 d
.addErrback(self
.discoFail
)
259 def discoResponse(self
, el
):
260 iqType
= el
.getAttribute("type")
261 if iqType
!= "result":
264 fro
= el
.getAttribute("from")
268 for child
in el
.elements():
269 if child
.name
== "query":
275 for child
in query
.elements():
276 if child
.name
== "feature":
277 features
.append(child
.getAttribute("var"))