]>
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
16 """ Manager for file transfers going from MSN to Jabber. """
19 Plan of action for this class:
20 * Determine the FT support of the Jabber client.
21 * If we find a common protocol, then send the invitation.
22 * Tell the legacyftp object the result of the invitation.
23 * If it was accepted, then start the transfer.
27 def __init__(self
, session
, senderJID
, legacyftp
):
28 self
.session
= session
29 self
.toJID
= self
.session
.jabberID
+ "/" + self
.session
.highestResource()
30 self
.senderJID
= senderJID
31 self
.ident
= (self
.toJID
, self
.senderJID
)
32 self
.legacyftp
= legacyftp
36 def checkSupport(self
):
37 def discoDone(features
):
38 LogEvent(INFO
, self
.ident
)
39 enabledS5B
= hasattr(self
.session
.pytrans
, "ftSOCKS5")
40 enabledOOB
= hasattr(self
.session
.pytrans
, "ftOOB")
41 hasFT
= features
.count(disco
.FT
)
42 hasS5B
= features
.count(disco
.S5B
)
43 hasOOB
= features
.count(disco
.IQOOB
)
44 LogEvent(INFO
, self
.ident
, "Choosing transfer mode.")
45 if hasFT
> 0 and hasS5B
> 0 and enabledS5B
:
47 elif hasOOB
> 0 and enabledOOB
:
53 self
.legacyftp
.reject()
56 def discoFail(err
=None):
57 LogEvent(INFO
, self
.ident
, str(err
))
60 d
= disco
.DiscoRequest(self
.session
.pytrans
, self
.toJID
).doDisco()
61 d
.addCallbacks(discoDone
, discoFail
)
65 if el
.getAttribute("type") != "result":
68 self
.session
.pytrans
.ftSOCKS5
.addConnection(utils
.socks5Hash(self
.sid
, self
.senderJID
, self
.toJID
), self
.legacyftp
)
69 LogEvent(INFO
, self
.ident
)
70 iq
= Element((None, "iq"))
71 iq
.attributes
["type"] = "set"
72 iq
.attributes
["to"] = self
.toJID
73 iq
.attributes
["from"] = self
.senderJID
74 query
= iq
.addElement("query")
75 query
.attributes
["xmlns"] = disco
.S5B
76 query
.attributes
["sid"] = self
.sid
77 query
.attributes
["mode"] = "tcp"
78 streamhost
= query
.addElement("streamhost")
79 streamhost
.attributes
["jid"] = self
.senderJID
80 streamhost
.attributes
["host"] = config
.ip
81 streamhost
.attributes
["port"] = config
.ftJabberPort
82 d
= self
.session
.pytrans
.discovery
.sendIq(iq
)
83 d
.addErrback(ftDeclined
) # Timeout
86 self
.legacyftp
.reject()
89 LogEvent(INFO
, self
.ident
)
90 self
.sid
= str(random
.randint(1000, sys
.maxint
))
91 iq
= Element((None, "iq"))
92 iq
.attributes
["type"] = "set"
93 iq
.attributes
["to"] = self
.toJID
94 iq
.attributes
["from"] = self
.senderJID
95 si
= iq
.addElement("si")
96 si
.attributes
["xmlns"] = disco
.SI
97 si
.attributes
["profile"] = disco
.FT
98 si
.attributes
["id"] = self
.sid
99 file = si
.addElement("file")
100 file.attributes
["profile"] = disco
.FT
101 file.attributes
["size"] = str(self
.legacyftp
.filesize
)
102 file.attributes
["name"] = self
.legacyftp
.filename
103 # Feature negotiation
104 feature
= si
.addElement("feature")
105 feature
.attributes
["xmlns"] = disco
.FEATURE_NEG
106 x
= feature
.addElement("x")
107 x
.attributes
["xmlns"] = "jabber:x:data"
108 x
.attributes
["type"] = "form"
109 field
= x
.addElement("field")
110 field
.attributes
["type"] = "list-single"
111 field
.attributes
["var"] = "stream-method"
112 option
= field
.addElement("option")
113 value
= option
.addElement("value")
114 value
.addContent(disco
.S5B
)
115 d
= self
.session
.pytrans
.discovery
.sendIq(iq
, 60*3)
116 d
.addCallback(ftReply
)
117 d
.addErrback(ftDeclined
)
121 if el
.getAttribute("type") != "result":
122 self
.legacyftp
.reject()
124 self
.session
.pytrans
.ftOOB
.remFile(filename
)
126 def ecb(ignored
=None):
127 self
.legacyftp
.reject()
130 LogEvent(INFO
, self
.ident
)
131 filename
= self
.session
.pytrans
.ftOOB
.putFile(self
, self
.legacyftp
.filename
)
132 iq
= Element((None, "iq"))
133 iq
.attributes
["to"] = self
.toJID
134 iq
.attributes
["from"] = self
.senderJID
135 query
= m
.addElement("query")
136 query
.attributes
["xmlns"] = disco
.IQOOB
137 query
.addElement("url").addContent(config
.ftOOBRoot
+ "/" + filename
)
138 d
= self
.session
.send(iq
)
139 d
.addCallbacks(cb
, ecb
)
141 def messageOobMode(self
):
142 LogEvent(INFO
, self
.ident
)
143 filename
= self
.session
.pytrans
.ftOOB
.putFile(self
, self
.legacyftp
.filename
)
144 m
= Element((None, "message"))
145 m
.attributes
["to"] = self
.session
.jabberID
146 m
.attributes
["from"] = self
.senderJID
147 m
.addElement("body").addContent(config
.ftOOBRoot
+ "/" + filename
)
148 x
= m
.addElement("x")
149 x
.attributes
["xmlns"] = disco
.XOOB
150 x
.addElement("url").addContent(config
.ftOOBRoot
+ "/" + filename
)
151 self
.session
.pytrans
.send(m
)
153 def error(self
, ignored
=None):
161 from tlib
import socks5
163 class JEP65Connection(socks5
.SOCKSv5
):
164 def __init__(self
, listener
):
165 socks5
.SOCKSv5
.__init__(self
)
166 self
.listener
= listener
167 self
.supportedAuthMechs
= [socks5
.AUTHMECH_ANON
]
168 self
.supportedAddrs
= [socks5
.ADDR_DOMAINNAME
]
169 self
.enabledCommands
= [socks5
.CMD_CONNECT
]
172 def connectRequested(self
, addr
, port
):
173 # So that the legacyftp can close the connection
174 self
.transport
.close
= self
.transport
.loseConnection
176 # Check for special connect to the namespace -- this signifies that
177 # the client is just checking that it can connect to the streamhost
178 if addr
== disco
.S5B
:
179 self
.connectCompleted(addr
, 0)
180 self
.transport
.loseConnection()
185 if self
.listener
.isActive(addr
):
186 self
.sendErrorReply(socks5
.REPLY_CONN_NOT_ALLOWED
)
189 if self
.listener
.addConnection(addr
, self
):
190 self
.connectCompleted(addr
, 0)
192 self
.sendErrorReply(socks5
.REPLY_CONN_REFUSED
)
194 def connectionLost(self
, reason
):
195 if self
.state
== socks5
.STATE_CONNECT_PENDING
:
196 self
.listener
.removePendingConnection(self
.addr
, self
)
198 self
.transport
.unregisterProducer()
199 if self
.peersock
!= None:
200 self
.peersock
.peersock
= None
201 self
.peersock
.transport
.unregisterProducer()
203 self
.listener
.removeActiveConnection(self
.addr
)
205 class Proxy65(protocol
.Factory
):
206 def __init__(self
, port
):
208 reactor
.listenTCP(port
, self
)
209 self
.pendingConns
= {}
210 self
.activeConns
= {}
212 def buildProtocol(self
, addr
):
213 return JEP65Connection(self
)
215 def isActive(self
, address
):
216 return address
in self
.activeConns
218 def activateStream(self
, address
):
219 if address
in self
.pendingConns
:
220 olist
= self
.pendingConns
[address
]
222 LogEvent(WARN
, '', "Not exactly two!")
225 assert address
not in self
.activeConns
226 self
.activeConns
[address
] = None
228 if not isinstance(olist
[0], JEP65Connection
):
230 connection
= olist
[1]
231 elif not isinstance(olist
[1], JEP65Connection
):
233 connection
= olist
[0]
235 LogEvent(WARN
, '', "No legacyftp")
238 legacyftp
.accept(connection
.transport
)
240 LogEvent(WARN
, '', "No pending connection.")
242 def addConnection(self
, address
, connection
):
243 olist
= self
.pendingConns
.get(address
, [])
245 olist
.append(connection
)
246 self
.pendingConns
[address
] = olist
248 self
.activateStream(address
)
253 def removePendingConnection(self
, address
, connection
):
254 olist
= self
.pendingConns
[address
]
256 del self
.pendingConns
[address
]
258 olist
.remove(connection
)
260 def removeActiveConnection(self
, address
):
261 del self
.activeConns
[address
]
264 # OOB download server
266 from twisted
.web
import server
, resource
, error
267 from twisted
.internet
import reactor
269 from debug
import LogEvent
, INFO
, WARN
, ERROR
272 def __init__(self
, ftReceive
, ftHttpPush
):
273 self
.ftReceive
, self
.ftHttpPush
= ftReceive
, ftHttpPush
274 self
.ftReceive
.legacyftp
.accept(self
)
276 def write(self
, data
):
277 self
.ftHttpPush
.write(data
)
280 self
.ftHttpPush
.finish()
283 self
.ftHttpPush
.finish()
284 self
.ftReceive
.error()
286 class FileTransferOOB(resource
.Resource
):
287 def __init__(self
, port
):
291 self
.oobSite
= server
.Site(self
)
292 reactor
.listenTCP(port
, self
.oobSite
)
294 def putFile(self
, file, filename
):
295 path
= str(random
.randint(100000000, 999999999))
296 filename
= (path
+ "/" + filename
).replace("//", "/")
297 self
.files
[filename
] = file
300 def remFile(self
, filename
):
301 if self
.files
.has_key(filename
):
302 del self
.files
[filename
]
304 def render_GET(self
, request
):
305 filename
= request
.path
[1:] # Remove the leading /
306 if self
.files
.has_key(filename
):
307 file = self
.files
[filename
]
308 request
.setHeader("Content-Length", str(file.legacyftp
.filesize
))
309 request
.setHeader("Content-Disposition", "attachment; filename=\"%s\"" % file.legacyftp
.filename
.encode("utf-8"))
310 Connector(file, request
)
311 del self
.files
[filename
]
312 return server
.NOT_DONE_YET
314 page
= error
.NoResource(message
="404 File Not Found")
315 return page
.render(request
)