From f5e7b143b9a91a31fb5e903a5da2f07f74289f30 Mon Sep 17 00:00:00 2001 From: jamesbunton Date: Mon, 23 Jan 2006 01:07:49 +0000 Subject: [PATCH] Socks5 sending supported git-svn-id: http://delx.cjb.net/svn/pymsnt/trunk@97 55fbd22a-6204-0410-b2f0-b6c764c7e90a committer: jamesbunton --- src/disco.py | 1 + src/ft.py | 68 ++++++++++++++++++--- src/main.py | 4 +- src/misciq.py | 146 +++++++++++++++++++++++++++++++++++++++++++-- src/tlib/socks5.py | 5 +- 5 files changed, 208 insertions(+), 16 deletions(-) diff --git a/src/disco.py b/src/disco.py index cf5ac66..286ce3f 100644 --- a/src/disco.py +++ b/src/disco.py @@ -36,6 +36,7 @@ XCONFERENCE = "jabber:x:conference" XEVENT = "jabber:x:event" XDELAY = "jabber:x:delay" XAVATAR = "jabber:x:avatar" +XDATA = "jabber:x:data" STORAGEAVATAR = "storage:client:avatar" XVCARDUPDATE = "vcard-temp:x:update" VCARDTEMP = "vcard-temp" diff --git a/src/ft.py b/src/ft.py index 1dfa6e8..f87f55c 100644 --- a/src/ft.py +++ b/src/ft.py @@ -30,7 +30,7 @@ class FTSend: def reject(self): del self.startTransfer - self.cancelTransfer + self.cancelTransfer() try: @@ -177,7 +177,7 @@ class FTReceive: 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" @@ -232,8 +232,62 @@ class FTReceive: # 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 @@ -283,7 +337,7 @@ class Proxy65(protocol.Factory): self.activeConns = {} def buildProtocol(self, addr): - return JEP65Connection(self) + return JEP65ConnectionReceive(self) def isActive(self, address): return address in self.activeConns @@ -298,14 +352,14 @@ class Proxy65(protocol.Factory): 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) diff --git a/src/main.py b/src/main.py index 76ca796..1a7803e 100644 --- a/src/main.py +++ b/src/main.py @@ -60,7 +60,9 @@ class PyTransport(component.Service): self.vCardFactory = misciq.VCardFactory(self) self.iqAvatarFactor = misciq.IqAvatarFactory(self) self.connectUsers = misciq.ConnectUsers(self) - if config.ftJabberPort: self.ftSOCKS5 = ft.Proxy65(int(config.ftJabberPort)) + if config.ftJabberPort: + self.ftSOCKS5Receive = ft.Proxy65(int(config.ftJabberPort)) + self.ftSOCKS5Send = misciq.Socks5FileTransfer(self) if config.ftOOBPort: self.ftOOBReceive = ft.FileTransferOOBReceive(int(config.ftOOBPort)) self.ftOOBSend = misciq.FileTransferOOBSend(self) diff --git a/src/misciq.py b/src/misciq.py index 79a9182..078208a 100644 --- a/src/misciq.py +++ b/src/misciq.py @@ -2,7 +2,7 @@ # 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 @@ -49,7 +49,7 @@ class ConnectUsers: 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") @@ -93,7 +93,7 @@ class Statistics: 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") @@ -531,11 +531,147 @@ class Socks5FileTransfer: 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)) + diff --git a/src/tlib/socks5.py b/src/tlib/socks5.py index 8639def..b55481d 100644 --- a/src/tlib/socks5.py +++ b/src/tlib/socks5.py @@ -17,10 +17,9 @@ ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 021-1307 ## USA ## -## Copyright (C) 2002-2003 Dave Smith (dizzyd@jabber.org) -## Copyright (C) 2005 James Bunton (james@delx.cjb.net) +## Copyright (C) 2002-2003 Dave Smith (dizzyd@jabber.org) +## Copyright (C) 2005-2006 James Bunton (james@delx.cjb.net) ## -## $Id: socks5.py,v 1.1.1.1 2003/07/15 04:18:05 dizzyd Exp $ ##============================================================================ from twisted.internet import protocol, reactor -- 2.39.2