]>
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
5 from debug
import LogEvent
, INFO
, WARN
, ERROR
8 from tlib
.xmlw
import Element
14 """ Manager for file transfers going from MSN to Jabber. """
17 Plan of action for this class:
18 * Determine the FT support of the Jabber client.
19 * If we support a common protocol with them, create an
20 FTReceive_Invite object of that type. Either OOB (JEP0066) or SI(JEP0095)
21 * Call doInvite() and wait on the Deferred to send an affirmative or
22 negative to the MSN contact.
23 * The InvitationReceive sends IQ packets to the Jabber user to see if they
24 accept. If they do it creates an appropriate FTReceive_Transport to send
25 the file. Returning a Deferred for success or failure.
29 def __init__(self
, session
, senderJID
, legacyftp
):
30 self
.session
= session
31 self
.toJID
= self
.session
.jabberID
+ "/" + self
.session
.highestResource()
32 self
.senderJID
= senderJID
33 self
.ident
= (self
.toJID
, self
.senderJID
)
34 self
.legacyftp
= legacyftp
38 def checkSupport(self
):
39 def discoDone(features
):
40 enabledS5
= hasattr(self
.session
.pytrans
, "ftSOCKS5")
41 enabledOOB
= hasattr(self
.session
.pytrans
, "ftOOB")
42 hasFT
= features
.count(disco
.FT
)
43 hasS5
= features
.count(disco
.S5
)
44 #hasIBB = features.count(disco.IBB)
45 hasOOB
= features
.count(disco
.IQOOB
)
46 if hasFT
> 0 and hasS5
> 0 and enabledS5
:
48 elif hasOOB
> 0 and enabledOOB
:
54 self
.legacyftp
.reject()
57 def discoFail(ignored
=None):
60 d
= disco
.DiscoRequest(self
.session
.pytrans
, self
.toJID
).doDisco()
61 d
.addCallback(discoDone
)
62 d
.addErrback(discoFail
)
66 if el
.getAttribute("type") != "result":
69 # FIXME, some kind of mixin
70 self
.session
.pytrans
.ftSOCKS5
.addConnection(utils
.socks5Hash(self
.sid
, self
.senderJID
, self
.toJID
), self
.legacyftp
)
71 self
.legacyftp
.accept(self
)
74 self
.legacyftp
.reject()
77 LogEvent(INFO
, self
.ident
)
78 self
.sid
= random
.randint(1000, sys
.maxint
)
79 iq
= Element((None, "iq"))
80 iq
.attributes
["type"] = "set"
81 iq
.attributes
["to"] = self
.toJID
82 iq
.attributes
["from"] = self
.senderJID
83 si
= iq
.addElement("si")
84 si
.attributes
["xmlns"] = disco
.SI
85 si
.attributes
["profile"] = disco
.FT
86 si
.attributes
["id"] = self
.sid
87 file = si
.addElement("file")
88 file.attributes
["profile"] = disco
.FT
89 file.attributes
["size"] = self
.legacyftp
.filesize
90 file.attributes
["name"] = self
.legacyftp
.filename
92 feature
= si
.addElement("feature")
93 feature
.attributes
["xmlns"] = disco
.FEATURE_NEG
94 x
= feature
.addElement("x")
95 x
.attributes
["xmlns"] = "jabber:x:data"
96 x
.attributes
["type"] = "form"
97 field
= x
.addElement("field")
98 field
.attributes
["type"] = "list-single"
99 field
.attributes
["var"] = "stream-method"
100 option
= field
.addElement("option")
101 value
= option
.addElement("value")
102 value
.addContent(disco
.S5B
)
103 d
= self
.pytrans
.discovery
.sendIq(iq
, 60*3)
104 d
.addCallback(ftReply
)
105 d
.addErrback(ftDeclined
)
109 if el
.getAttribute("type") != "result":
110 self
.legacyftp
.reject()
112 self
.session
.pytrans
.ftOOB
.remFile(filename
)
114 def ecb(ignored
=None):
115 self
.legacyftp
.reject()
118 LogEvent(INFO
, self
.ident
)
119 filename
= self
.session
.pytrans
.ftOOB
.putFile(self
, self
.legacyftp
.filename
)
120 iq
= Element((None, "iq"))
121 iq
.attributes
["to"] = self
.toJID
122 iq
.attributes
["from"] = self
.senderJID
123 query
= m
.addElement("query")
124 query
.attributes
["xmlns"] = disco
.IQOOB
125 query
.addElement("url").addContent(config
.ftOOBRoot
+ "/" + filename
)
126 d
= self
.session
.send(iq
)
127 d
.addCallbacks(cb
, ecb
)
129 def messageOobMode(self
):
130 LogEvent(INFO
, self
.ident
)
131 filename
= self
.session
.pytrans
.ftOOB
.putFile(self
, self
.legacyftp
.filename
)
132 m
= Element((None, "message"))
133 m
.attributes
["to"] = self
.session
.jabberID
134 m
.attributes
["from"] = self
.senderJID
135 m
.addElement("body").addContent(config
.ftOOBRoot
+ "/" + filename
)
136 x
= m
.addElement("x")
137 x
.attributes
["xmlns"] = disco
.XOOB
138 x
.addElement("url").addContent(config
.ftOOBRoot
+ "/" + filename
)
139 self
.session
.pytrans
.send(m
)
141 def error(self
, ignored
=None):
149 from tlib
import socks5
151 class JEP65Connection(socks5
.SOCKSv5
):
152 def __init__(self
, listener
):
153 socks5
.SOCKSv5
.__init__(self
)
154 self
.listener
= listener
155 self
.supportedAuthMechs
= [socks5
.AUTHMECH_ANON
]
156 self
.supportedAddrs
= [socks5
.ADDR_DOMAINNAME
]
157 self
.enabledCommands
= [socks5
.CMD_CONNECT
]
160 def connectRequested(self
, addr
, port
):
161 # Check for special connect to the namespace -- this signifies that
162 # the client is just checking that it can connect to the streamhost
163 if addr
== disco
.S5B
:
164 self
.connectCompleted(addr
, 0)
165 self
.transport
.loseConnection()
170 if self
.listener
.isActive(addr
):
171 self
.sendErrorReply(socks5
.REPLY_CONN_NOT_ALLOWED
)
174 if self
.listener
.addConnection(addr
, self
):
175 self
.connectCompleted(addr
, 0)
177 self
.sendErrorReply(socks5
.REPLY_CONN_REFUSED
)
179 def connectionLost(self
, reason
):
180 if self
.state
== socks5
.STATE_CONNECT_PENDING
:
181 self
.listener
.removePendingConnection(self
.addr
, self
)
183 self
.transport
.unregisterProducer()
184 if self
.peersock
!= None:
185 self
.peersock
.peersock
= None
186 self
.peersock
.transport
.unregisterProducer()
188 self
.listener
.removeActiveConnection(self
.addr
)
190 class Proxy65(protocol
.Factory
):
191 def __init__(self
, port
):
192 internet
.TCPServer(port
, self
)
193 self
.pendingConns
= {}
194 self
.activeConns
= {}
196 def buildProtocol(self
, addr
):
197 return JEP65Connection(self
)
199 def isActive(self
, address
):
200 return address
in self
.activeConns
202 def activateStream(self
, address
):
203 if address
in self
.pendingConns
:
204 olist
= self
.pendingConns
[address
]
206 LogEvent(WARN
, '', "Not exactly two!")
209 assert address
not in self
.activeConns
210 self
.activeConns
[address
] = None
212 olist
[0].peersock
= olist
[1]
213 olist
[1].peersock
= olist
[0]
214 olist
[0].transport
.registerProducer(olist
[1], 0)
215 olist
[1].transport
.registerProducer(olist
[0], 0)
217 LogEvent(WARN
, '', "No pending connection.")
219 def addConnection(self
, address
, connection
):
220 olist
= self
.pendingConns
.get(address
, [])
222 olist
.append(connection
)
223 self
.pendingConns
[address
] = olist
225 self
.activateStream(address
)
230 def removePendingConnection(self
, address
, connection
):
231 olist
= self
.pendingConns
[address
]
233 del self
.pendingConns
[address
]
235 olist
.remove(connection
)
237 def removeActiveConnection(self
, address
):
238 del self
.activeConns
[address
]
241 # OOB download server
243 from twisted
.web
import server
, resource
, error
244 from twisted
.internet
import reactor
246 from debug
import LogEvent
, INFO
, WARN
, ERROR
249 def __init__(self
, ftReceive
, ftHttpPush
):
250 self
.ftReceive
, self
.ftHttpPush
= ftReceive
, ftHttpPush
251 self
.ftReceive
.legacyftp
.accept(self
)
253 def write(self
, data
):
254 self
.ftHttpPush
.write(data
)
257 self
.ftHttpPush
.finish()
260 self
.ftHttpPush
.finish()
261 self
.ftReceive
.error()
263 class FileTransferOOB(resource
.Resource
):
264 def __init__(self
, port
):
267 self
.oobSite
= server
.Site(self
)
268 reactor
.listenTCP(port
, self
.oobSite
)
270 def putFile(self
, file, filename
):
271 path
= str(random
.randint(100000000, 999999999))
272 filename
= (path
+ "/" + filename
).replace("//", "/")
273 self
.files
[filename
] = file
276 def remFile(self
, filename
):
277 if self
.files
.has_key(filename
):
278 del self
.files
[filename
]
280 def render_GET(self
, request
):
281 filename
= request
.path
[1:] # Remove the leading /
282 if self
.files
.has_key(filename
):
283 file = self
.files
[filename
]
284 request
.setHeader("Content-Length", str(self
.legacyftp
.filesize
))
285 request
.setHeader("Content-Disposition", "attachment; filename=\"%s\"" % file.legacyftp
.filename
.encode("utf-8"))
286 Connector(file, request
)
287 del self
.files
[filename
]
288 return server
.NOT_DONE_YET
290 page
= error
.NoResource(message
="404 File Not Found")
291 return page
.render(request
)