def reject(self):
del self.startTransfer
- self.cancelTransfer
+ self.cancelTransfer()
try:
feature = si.addElement("feature")
feature.attributes["xmlns"] = disco.FEATURE_NEG
x = feature.addElement("x")
- x.attributes["xmlns"] = "jabber:x:data"
+ x.attributes["xmlns"] = disco.XDATA
x.attributes["type"] = "form"
field = x.addElement("field")
field.attributes["type"] = "list-single"
# SOCKS5
from tlib import socks5
+import struct
+
+class JEP65ConnectionSend(protocol.Protocol):
+# TODO, clean up and move this to tlib.socks5
+ STATE_INITIAL = 1
+ STATE_WAIT_AUTHOK = 2
+ STATE_WAIT_CONNECTOK = 3
+ STATE_READY = 4
+
+ def __init__(self):
+ self.state = self.STATE_INITIAL
+ self.buf = ""
+
+ def connectionMade(self):
+ self.transport.write(struct.pack("!BBB", 5, 1, 0))
+ self.state = self.STATE_WAIT_AUTHOK
+
+ def connectionLost(self, reason):
+ if self.state == self.STATE_READY:
+ self.factory.consumer.close()
+
+ def _waitAuthOk(self):
+ ver, method = struct.unpack("!BB", self.buf[:2])
+ if ver != 5 or method != 0:
+ self.transport.loseConnection()
+ return
+ self.buf = self.buf[2:] # chop
+
+ # Send CONNECT request
+ length = len(self.factory.hash)
+ self.transport.write(struct.pack("!BBBBB", 5, 1, 0, 3, length))
+ self.transport.write("".join([struct.pack("!B" , ord(x))[0] for x in self.factory.hash]))
+ self.transport.write(struct.pack("!H", 0))
+ self.state = self.STATE_WAIT_CONNECTOK
+
+ def _waitConnectOk(self):
+ ver, rep, rsv, atyp = struct.unpack("!BBBB", self.buf[:4])
+ if not (ver == 5 and rep == 0):
+ self.transport.loseConnection()
+ return
+
+ self.state = self.STATE_READY
+ self.factory.madeConnection(self.transport.addr[0])
+
+ def dataReceived(self, buf):
+ if self.state == self.STATE_READY:
+ self.factory.consumer.write(buf)
+
+ self.buf += buf
+ if self.state == self.STATE_WAIT_AUTHOK:
+ self._waitAuthOk()
+ elif self.state == self.STATE_WAIT_CONNECTOK:
+ self._waitConnectOk()
+
-class JEP65Connection(socks5.SOCKSv5):
+class JEP65ConnectionReceive(socks5.SOCKSv5):
def __init__(self, listener):
socks5.SOCKSv5.__init__(self)
self.listener = listener
self.activeConns = {}
def buildProtocol(self, addr):
- return JEP65Connection(self)
+ return JEP65ConnectionReceive(self)
def isActive(self, address):
return address in self.activeConns
assert address not in self.activeConns
self.activeConns[address] = None
- if not isinstance(olist[0], JEP65Connection):
+ if not isinstance(olist[0], (JEP65ConnectionReceive, JEP65ConnectionSend)):
legacyftp = olist[0]
connection = olist[1]
- elif not isinstance(olist[1], JEP65Connection):
+ elif not isinstance(olist[1], (JEP65ConnectionReceive, JEP65ConnectionSend)):
legacyftp = olist[1]
connection = olist[0]
else:
- LogEvent(WARN, '', "No legacyftp")
+ LogEvent(WARN, '', "No JEP65Connection")
return
legacyftp.accept(connection.transport)
# Licensed for distribution under the GPL version 2, check COPYING for details
import utils
-from twisted.internet import reactor, task, protocol
+from twisted.internet import reactor, task, protocol, error
from tlib.xmlw import Element, jid
from debug import LogEvent, INFO, WARN, ERROR
import jabw
command.attributes["status"] = "completed"
x = command.addElement("x")
- x.attributes["xmlns"] = "jabber:x:data"
+ x.attributes["xmlns"] = disco.XDATA
x.attributes["type"] = "result"
title = x.addElement("title")
command.attributes["status"] = "completed"
x = command.addElement("x")
- x.attributes["xmlns"] = "jabber:x:data"
+ x.attributes["xmlns"] = disco.XDATA
x.attributes["type"] = "result"
title = x.addElement("title")
def __init__(self, pytrans):
self.pytrans = pytrans
self.pytrans.discovery.addFeature(disco.SI, self.incomingSI, "USER")
+ self.pytrans.discovery.addFeature(disco.FT, lambda: None, "USER")
self.pytrans.discovery.addFeature(disco.S5B, self.incomingS5B, "USER")
+ self.sessions = {}
def incomingSI(self, el):
- pass
+ ID = el.getAttribute("id")
+ def errOut():
+ self.pytrans.discovery.sendIqError(to=el.getAttribute("from"), fro=el.getAttribute("to"), ID=ID, xmlns=disco.SI, etype="cancel", condition="bad-request")
+
+ toj = jid.intern(el.getAttribute("to"))
+ froj = jid.intern(el.getAttribute("from"))
+ session = self.pytrans.sessions.get(froj.userhost(), None)
+ if not session:
+ return errOut()
+
+ si = el.si
+ if not (si and si.getAttribute("profile") == disco.FT):
+ return errOut()
+ file = si.file
+ if not (file and file.defaultUri == disco.FT):
+ return errOut()
+ try:
+ sid = si["id"]
+ filename = file["name"]
+ filesize = int(file["size"])
+ except KeyError:
+ return errOut()
+ except ValueError:
+ return errOut()
+
+ # Check that we can use socks5 bytestreams
+ feature = si.feature
+ if not (feature and feature.defaultUri == disco.FEATURE_NEG):
+ return errOut()
+ x = feature.x
+ if not (x and x.defaultUri == disco.XDATA):
+ return errOut()
+ field = x.field
+ if not (field and field.getAttribute("var") == "stream-method"):
+ return errOut()
+ for option in field.elements():
+ value = option.value
+ if not value:
+ continue
+ value = value.__str__()
+ if value == disco.S5B:
+ break
+ else:
+ return errOut() # Socks5 bytestreams not supported :(
+
+
+ def startTransfer(consumer):
+ iq = Element((None, "iq"))
+ iq["type"] = "result"
+ iq["to"] = froj.full()
+ iq["from"] = toj.full()
+ iq["id"] = ID
+ si = iq.addElement("si")
+ si["xmlns"] = disco.SI
+ feature = si.addElement("feature")
+ feature["xmlns"] = disco.FEATURE_NEG
+ x = feature.addElement("x")
+ x["xmlns"] = disco.XDATA
+ x["type"] = "submit"
+ field = x.addElement("field")
+ field["var"] = "stream-method"
+ value = field.addElement("value")
+ value.addContent(disco.S5B)
+ self.pytrans.send(iq)
+ self.sessions[(froj.full(), sid)] = consumer
+
+ session.legacycon.sendFile(toj.userhost(), ft.FTSend(startTransfer, errOut, filename, filesize))
def incomingS5B(self, el):
- pass
+ ID = el.getAttribute("id")
+ def errOut():
+ self.pytrans.discovery.sendIqError(to=el.getAttribute("from"), fro=el.getAttribute("to"), ID=ID, xmlns=disco.S5B, etype="cancel", condition="item-not-found")
+
+ if el.getAttribute("type") != "set":
+ return errOut()
+
+ toj = jid.intern(el.getAttribute("to"))
+ froj = jid.intern(el.getAttribute("from"))
+
+ query = el.query
+ if not (query and query.getAttribute("mode") == "tcp"):
+ return errOut()
+ sid = query.getAttribute("sid")
+ consumer = self.sessions.pop((froj.full(), sid), None)
+ if not consumer:
+ return errOut()
+ streamhosts = []
+ for streamhost in query.elements():
+ if streamhost.name == "streamhost":
+ try:
+ JID = streamhost["jid"]
+ host = streamhost["host"]
+ port = int(streamhost["port"])
+ except ValueError:
+ return errOut()
+ except KeyError:
+ continue
+ streamhosts.append((JID, host, port))
+
+
+ def gotStreamhost(host):
+ for streamhost in streamhosts:
+ if streamhost[1] == host:
+ jid = streamhost[0]
+ break
+ else:
+ LogEvent(WARN)
+ return errOut()
+
+ for connector in factory.connectors:
+ # Stop any other connections
+ try:
+ connector.stopConnecting()
+ except error.NotConnectingError:
+ pass
+
+ iq = Element((None, "iq"))
+ iq["type"] = "result"
+ iq["from"] = toj.full()
+ iq["to"] = froj.full()
+ iq["id"] = ID
+ query = iq.addElement("query")
+ query["xmlns"] = disco.S5B
+ streamhost = query.addElement("streamhost-used")
+ streamhost["jid"] = jid
+ self.pytrans.send(iq)
+
+
+ # Try the streamhosts
+ factory = protocol.ClientFactory()
+ factory.protocol = ft.JEP65ConnectionSend
+ factory.consumer = consumer
+ factory.hash = utils.socks5Hash(sid, froj.full(), toj.full())
+ factory.madeConnection = gotStreamhost
+ factory.connectors = []
+ for streamhost in streamhosts:
+ factory.connectors.append(reactor.connectTCP(streamhost[1], streamhost[2], factory))
+