]>
code.delx.au - pymsnt/blob - src/ft.py
1 # Copyright 2005 James Bunton <james@delx.cjb.net>
2 # Licensed for distribution under the GPL version 2, check COPYING for details
4 from tlib
.xmlw
import Element
5 from twisted
.internet
import protocol
8 from debug
import LogEvent
, INFO
, WARN
, ERROR
21 """ For file transfers going from Jabber to MSN. """
22 def __init__(self
, startTransfer
, cancelTransfer
, filename
, filesize
):
23 self
.startTransfer
= startTransfer
24 self
.cancelTransfer
= cancelTransfer
25 self
.filename
= filename
26 self
.filesize
= filesize
28 def accept(self
, legacyFileSend
):
29 self
.startTransfer(legacyFileSend
)
32 del self
.startTransfer
37 from twisted
.web
import http
40 from twisted
.protocols
import http
42 print "Couldn't find http.HTTPClient. If you're using Twisted 2.0, make sure that you've installed twisted.web"
46 class OOBHeaderHelper(http
.HTTPClient
):
47 """ Makes a HEAD request and grabs the length """
48 def connectionMade(self
):
49 self
.sendCommand("HEAD", self
.factory
.path
.encode("utf-8"))
50 self
.sendHeader("Host", (self
.factory
.host
+ ":" + str(self
.factory
.port
)).encode("utf-8"))
53 def handleEndHeaders(self
):
54 self
.factory
.gotLength(self
.length
)
56 def handleResponse(self
, data
):
60 class OOBSendConnector(http
.HTTPClient
):
61 def connectionMade(self
):
62 self
.sendCommand("GET", self
.factory
.path
.encode("utf-8"))
63 self
.sendHeader("Host", (self
.factory
.host
+ ":" + str(self
.factory
.port
)).encode("utf-8"))
67 def handleResponsePart(self
, data
):
68 self
.factory
.consumer
.write(data
)
70 def handleResponseEnd(self
):
71 # This is called once before writing is finished, and once when the
72 # connection closes. We only consumer.close() on the second.
76 self
.factory
.consumer
.close()
77 self
.factory
.consumer
= None
78 self
.factory
.finished()
89 """ For file transfers going from MSN to Jabber. """
92 Plan of action for this class:
93 * Determine the FT support of the Jabber client.
94 * If we find a common protocol, then send the invitation.
95 * Tell the legacyftp object the result of the invitation.
96 * If it was accepted, then start the transfer.
100 def __init__(self
, session
, senderJID
, legacyftp
):
101 self
.session
= session
102 self
.toJID
= self
.session
.jabberID
+ "/" + self
.session
.highestResource()
103 self
.senderJID
= senderJID
104 self
.ident
= (self
.toJID
, self
.senderJID
)
105 self
.legacyftp
= legacyftp
109 def checkSupport(self
):
110 def discoDone(features
):
111 LogEvent(INFO
, self
.ident
)
112 enabledS5B
= hasattr(self
.session
.pytrans
, "ftSOCKS5")
113 enabledOOB
= hasattr(self
.session
.pytrans
, "ftOOB")
114 hasFT
= features
.count(disco
.FT
)
115 hasS5B
= features
.count(disco
.S5B
)
116 hasOOB
= features
.count(disco
.IQOOB
)
117 LogEvent(INFO
, self
.ident
, "Choosing transfer mode.")
118 if hasFT
> 0 and hasS5B
> 0 and enabledS5B
:
120 elif hasOOB
> 0 and enabledOOB
:
123 self
.messageOobMode()
126 self
.legacyftp
.reject()
129 def discoFail(err
=None):
130 LogEvent(INFO
, self
.ident
, str(err
))
131 self
.messageOobMode()
133 d
= disco
.DiscoRequest(self
.session
.pytrans
, self
.toJID
).doDisco()
134 d
.addCallbacks(discoDone
, discoFail
)
138 if el
.getAttribute("type") != "result":
141 self
.session
.pytrans
.ftSOCKS5
.addConnection(utils
.socks5Hash(self
.sid
, self
.senderJID
, self
.toJID
), self
.legacyftp
)
142 LogEvent(INFO
, self
.ident
)
143 iq
= Element((None, "iq"))
144 iq
.attributes
["type"] = "set"
145 iq
.attributes
["to"] = self
.toJID
146 iq
.attributes
["from"] = self
.senderJID
147 query
= iq
.addElement("query")
148 query
.attributes
["xmlns"] = disco
.S5B
149 query
.attributes
["sid"] = self
.sid
150 query
.attributes
["mode"] = "tcp"
151 streamhost
= query
.addElement("streamhost")
152 streamhost
.attributes
["jid"] = self
.senderJID
153 streamhost
.attributes
["host"] = config
.ip
154 streamhost
.attributes
["port"] = config
.ftJabberPort
155 d
= self
.session
.pytrans
.discovery
.sendIq(iq
)
156 d
.addErrback(ftDeclined
) # Timeout
159 self
.legacyftp
.reject()
162 LogEvent(INFO
, self
.ident
)
163 self
.sid
= str(random
.randint(1000, sys
.maxint
))
164 iq
= Element((None, "iq"))
165 iq
.attributes
["type"] = "set"
166 iq
.attributes
["to"] = self
.toJID
167 iq
.attributes
["from"] = self
.senderJID
168 si
= iq
.addElement("si")
169 si
.attributes
["xmlns"] = disco
.SI
170 si
.attributes
["profile"] = disco
.FT
171 si
.attributes
["id"] = self
.sid
172 file = si
.addElement("file")
173 file.attributes
["xmlns"] = disco
.FT
174 file.attributes
["size"] = str(self
.legacyftp
.filesize
)
175 file.attributes
["name"] = self
.legacyftp
.filename
176 # Feature negotiation
177 feature
= si
.addElement("feature")
178 feature
.attributes
["xmlns"] = disco
.FEATURE_NEG
179 x
= feature
.addElement("x")
180 x
.attributes
["xmlns"] = "jabber:x:data"
181 x
.attributes
["type"] = "form"
182 field
= x
.addElement("field")
183 field
.attributes
["type"] = "list-single"
184 field
.attributes
["var"] = "stream-method"
185 option
= field
.addElement("option")
186 value
= option
.addElement("value")
187 value
.addContent(disco
.S5B
)
188 d
= self
.session
.pytrans
.discovery
.sendIq(iq
, 60*3)
189 d
.addCallback(ftReply
)
190 d
.addErrback(ftDeclined
)
194 if el
.getAttribute("type") != "result":
195 self
.legacyftp
.reject()
197 self
.session
.pytrans
.ftOOB
.remFile(filename
)
199 def ecb(ignored
=None):
200 self
.legacyftp
.reject()
203 LogEvent(INFO
, self
.ident
)
204 filename
= self
.session
.pytrans
.ftOOB
.putFile(self
, self
.legacyftp
.filename
)
205 iq
= Element((None, "iq"))
206 iq
.attributes
["to"] = self
.toJID
207 iq
.attributes
["from"] = self
.senderJID
208 query
= m
.addElement("query")
209 query
.attributes
["xmlns"] = disco
.IQOOB
210 query
.addElement("url").addContent(config
.ftOOBRoot
+ "/" + filename
)
211 d
= self
.session
.send(iq
)
212 d
.addCallbacks(cb
, ecb
)
214 def messageOobMode(self
):
215 LogEvent(INFO
, self
.ident
)
216 filename
= self
.session
.pytrans
.ftOOB
.putFile(self
, self
.legacyftp
.filename
)
217 m
= Element((None, "message"))
218 m
.attributes
["to"] = self
.session
.jabberID
219 m
.attributes
["from"] = self
.senderJID
220 m
.addElement("body").addContent(config
.ftOOBRoot
+ "/" + filename
)
221 x
= m
.addElement("x")
222 x
.attributes
["xmlns"] = disco
.XOOB
223 x
.addElement("url").addContent(config
.ftOOBRoot
+ "/" + filename
)
224 self
.session
.pytrans
.send(m
)
226 def error(self
, ignored
=None):
234 from tlib
import socks5
236 class JEP65Connection(socks5
.SOCKSv5
):
237 def __init__(self
, listener
):
238 socks5
.SOCKSv5
.__init__(self
)
239 self
.listener
= listener
240 self
.supportedAuthMechs
= [socks5
.AUTHMECH_ANON
]
241 self
.supportedAddrs
= [socks5
.ADDR_DOMAINNAME
]
242 self
.enabledCommands
= [socks5
.CMD_CONNECT
]
245 def connectRequested(self
, addr
, port
):
246 # So that the legacyftp can close the connection
247 self
.transport
.close
= self
.transport
.loseConnection
249 # Check for special connect to the namespace -- this signifies that
250 # the client is just checking that it can connect to the streamhost
251 if addr
== disco
.S5B
:
252 self
.connectCompleted(addr
, 0)
253 self
.transport
.loseConnection()
258 if self
.listener
.isActive(addr
):
259 self
.sendErrorReply(socks5
.REPLY_CONN_NOT_ALLOWED
)
262 if self
.listener
.addConnection(addr
, self
):
263 self
.connectCompleted(addr
, 0)
265 self
.sendErrorReply(socks5
.REPLY_CONN_REFUSED
)
267 def connectionLost(self
, reason
):
268 if self
.state
== socks5
.STATE_CONNECT_PENDING
:
269 self
.listener
.removePendingConnection(self
.addr
, self
)
271 self
.transport
.unregisterProducer()
272 if self
.peersock
!= None:
273 self
.peersock
.peersock
= None
274 self
.peersock
.transport
.unregisterProducer()
276 self
.listener
.removeActiveConnection(self
.addr
)
278 class Proxy65(protocol
.Factory
):
279 def __init__(self
, port
):
281 reactor
.listenTCP(port
, self
)
282 self
.pendingConns
= {}
283 self
.activeConns
= {}
285 def buildProtocol(self
, addr
):
286 return JEP65Connection(self
)
288 def isActive(self
, address
):
289 return address
in self
.activeConns
291 def activateStream(self
, address
):
292 if address
in self
.pendingConns
:
293 olist
= self
.pendingConns
[address
]
295 LogEvent(WARN
, '', "Not exactly two!")
298 assert address
not in self
.activeConns
299 self
.activeConns
[address
] = None
301 if not isinstance(olist
[0], JEP65Connection
):
303 connection
= olist
[1]
304 elif not isinstance(olist
[1], JEP65Connection
):
306 connection
= olist
[0]
308 LogEvent(WARN
, '', "No legacyftp")
311 legacyftp
.accept(connection
.transport
)
313 LogEvent(WARN
, '', "No pending connection.")
315 def addConnection(self
, address
, connection
):
316 olist
= self
.pendingConns
.get(address
, [])
318 olist
.append(connection
)
319 self
.pendingConns
[address
] = olist
321 self
.activateStream(address
)
326 def removePendingConnection(self
, address
, connection
):
327 olist
= self
.pendingConns
[address
]
329 del self
.pendingConns
[address
]
331 olist
.remove(connection
)
333 def removeActiveConnection(self
, address
):
334 del self
.activeConns
[address
]
337 # OOB download server
339 from twisted
.web
import server
, resource
, error
340 from twisted
.internet
import reactor
342 from debug
import LogEvent
, INFO
, WARN
, ERROR
344 class OOBReceiveConnector
:
345 def __init__(self
, ftReceive
, ftHttpPush
):
346 self
.ftReceive
, self
.ftHttpPush
= ftReceive
, ftHttpPush
347 self
.ftReceive
.legacyftp
.accept(self
)
349 def write(self
, data
):
350 self
.ftHttpPush
.write(data
)
353 self
.ftHttpPush
.finish()
356 self
.ftHttpPush
.finish()
357 self
.ftReceive
.error()
359 class FileTransferOOBReceive(resource
.Resource
):
360 def __init__(self
, port
):
364 self
.oobSite
= server
.Site(self
)
365 reactor
.listenTCP(port
, self
.oobSite
)
367 def putFile(self
, file, filename
):
368 path
= str(random
.randint(100000000, 999999999))
369 filename
= (path
+ "/" + filename
).replace("//", "/")
370 self
.files
[filename
] = file
373 def remFile(self
, filename
):
374 if self
.files
.has_key(filename
):
375 del self
.files
[filename
]
377 def render_GET(self
, request
):
378 filename
= request
.path
[1:] # Remove the leading /
379 if self
.files
.has_key(filename
):
380 file = self
.files
[filename
]
381 request
.setHeader("Content-Length", str(file.legacyftp
.filesize
))
382 request
.setHeader("Content-Disposition", "attachment; filename=\"%s\"" % file.legacyftp
.filename
.encode("utf-8"))
383 OOBReceiveConnector(file, request
)
384 del self
.files
[filename
]
385 return server
.NOT_DONE_YET
387 page
= error
.NoResource(message
="404 File Not Found")
388 return page
.render(request
)
390 def render_HEAD(self
, request
):
391 filename
= request
.path
[1:] # Remove the leading /
392 if self
.files
.has_key(filename
):
393 file = self
.files
[filename
]
394 request
.setHeader("Content-Length", str(file.legacyftp
.filesize
))
395 request
.setHeader("Content-Disposition", "attachment; filename=\"%s\"" % file.legacyftp
.filename
.encode("utf-8"))
398 page
= error
.NoResource(message
="404 File Not Found")
399 return page
.render(request
)