Mini Shell
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
This module contains the implementation of the TCP forwarding, which allows
clients and servers to forward arbitrary TCP data across the connection.
Maintainer: Paul Swartz
"""
import struct
from twisted.conch.ssh import channel, common
from twisted.internet import protocol, reactor
from twisted.internet.endpoints import HostnameEndpoint, connectProtocol
class SSHListenForwardingFactory(protocol.Factory):
def __init__(self, connection, hostport, klass):
self.conn = connection
self.hostport = hostport # tuple
self.klass = klass
def buildProtocol(self, addr):
channel = self.klass(conn=self.conn)
client = SSHForwardingClient(channel)
channel.client = client
addrTuple = (addr.host, addr.port)
channelOpenData = packOpen_direct_tcpip(self.hostport, addrTuple)
self.conn.openChannel(channel, channelOpenData)
return client
class SSHListenForwardingChannel(channel.SSHChannel):
def channelOpen(self, specificData):
self._log.info("opened forwarding channel {id}", id=self.id)
if len(self.client.buf) > 1:
b = self.client.buf[1:]
self.write(b)
self.client.buf = b""
def openFailed(self, reason):
self.closed()
def dataReceived(self, data):
self.client.transport.write(data)
def eofReceived(self):
self.client.transport.loseConnection()
def closed(self):
if hasattr(self, "client"):
self._log.info("closing local forwarding channel {id}", id=self.id)
self.client.transport.loseConnection()
del self.client
class SSHListenClientForwardingChannel(SSHListenForwardingChannel):
name = b"direct-tcpip"
class SSHListenServerForwardingChannel(SSHListenForwardingChannel):
name = b"forwarded-tcpip"
class SSHConnectForwardingChannel(channel.SSHChannel):
"""
Channel used for handling server side forwarding request.
It acts as a client for the remote forwarding destination.
@ivar hostport: C{(host, port)} requested by client as forwarding
destination.
@type hostport: L{tuple} or a C{sequence}
@ivar client: Protocol connected to the forwarding destination.
@type client: L{protocol.Protocol}
@ivar clientBuf: Data received while forwarding channel is not yet
connected.
@type clientBuf: L{bytes}
@var _reactor: Reactor used for TCP connections.
@type _reactor: A reactor.
@ivar _channelOpenDeferred: Deferred used in testing to check the
result of C{channelOpen}.
@type _channelOpenDeferred: L{twisted.internet.defer.Deferred}
"""
_reactor = reactor
def __init__(self, hostport, *args, **kw):
channel.SSHChannel.__init__(self, *args, **kw)
self.hostport = hostport
self.client = None
self.clientBuf = b""
def channelOpen(self, specificData):
"""
See: L{channel.SSHChannel}
"""
self._log.info(
"connecting to {host}:{port}", host=self.hostport[0], port=self.hostport[1]
)
ep = HostnameEndpoint(self._reactor, self.hostport[0], self.hostport[1])
d = connectProtocol(ep, SSHForwardingClient(self))
d.addCallbacks(self._setClient, self._close)
self._channelOpenDeferred = d
def _setClient(self, client):
"""
Called when the connection was established to the forwarding
destination.
@param client: Client protocol connected to the forwarding destination.
@type client: L{protocol.Protocol}
"""
self.client = client
self._log.info(
"connected to {host}:{port}", host=self.hostport[0], port=self.hostport[1]
)
if self.clientBuf:
self.client.transport.write(self.clientBuf)
self.clientBuf = None
if self.client.buf[1:]:
self.write(self.client.buf[1:])
self.client.buf = b""
def _close(self, reason):
"""
Called when failed to connect to the forwarding destination.
@param reason: Reason why connection failed.
@type reason: L{twisted.python.failure.Failure}
"""
self._log.error(
"failed to connect to {host}:{port}: {reason}",
host=self.hostport[0],
port=self.hostport[1],
reason=reason,
)
self.loseConnection()
def dataReceived(self, data):
"""
See: L{channel.SSHChannel}
"""
if self.client:
self.client.transport.write(data)
else:
self.clientBuf += data
def closed(self):
"""
See: L{channel.SSHChannel}
"""
if self.client:
self._log.info("closed remote forwarding channel {id}", id=self.id)
if self.client.channel:
self.loseConnection()
self.client.transport.loseConnection()
del self.client
def openConnectForwardingClient(remoteWindow, remoteMaxPacket, data, avatar):
remoteHP, origHP = unpackOpen_direct_tcpip(data)
return SSHConnectForwardingChannel(
remoteHP,
remoteWindow=remoteWindow,
remoteMaxPacket=remoteMaxPacket,
avatar=avatar,
)
class SSHForwardingClient(protocol.Protocol):
def __init__(self, channel):
self.channel = channel
self.buf = b"\000"
def dataReceived(self, data):
if self.buf:
self.buf += data
else:
self.channel.write(data)
def connectionLost(self, reason):
if self.channel:
self.channel.loseConnection()
self.channel = None
def packOpen_direct_tcpip(destination, source):
"""
Pack the data suitable for sending in a CHANNEL_OPEN packet.
@type destination: L{tuple}
@param destination: A tuple of the (host, port) of the destination host.
@type source: L{tuple}
@param source: A tuple of the (host, port) of the source host.
"""
(connHost, connPort) = destination
(origHost, origPort) = source
if isinstance(connHost, str):
connHost = connHost.encode("utf-8")
if isinstance(origHost, str):
origHost = origHost.encode("utf-8")
conn = common.NS(connHost) + struct.pack(">L", connPort)
orig = common.NS(origHost) + struct.pack(">L", origPort)
return conn + orig
packOpen_forwarded_tcpip = packOpen_direct_tcpip
def unpackOpen_direct_tcpip(data):
"""Unpack the data to a usable format."""
connHost, rest = common.getNS(data)
if isinstance(connHost, bytes):
connHost = connHost.decode("utf-8")
connPort = int(struct.unpack(">L", rest[:4])[0])
origHost, rest = common.getNS(rest[4:])
if isinstance(origHost, bytes):
origHost = origHost.decode("utf-8")
origPort = int(struct.unpack(">L", rest[:4])[0])
return (connHost, connPort), (origHost, origPort)
unpackOpen_forwarded_tcpip = unpackOpen_direct_tcpip
def packGlobal_tcpip_forward(peer):
"""
Pack the data for tcpip forwarding.
@param peer: A tuple of the (host, port) .
@type peer: L{tuple}
"""
(host, port) = peer
return common.NS(host) + struct.pack(">L", port)
def unpackGlobal_tcpip_forward(data):
host, rest = common.getNS(data)
if isinstance(host, bytes):
host = host.decode("utf-8")
port = int(struct.unpack(">L", rest[:4])[0])
return host, port
"""This is how the data -> eof -> close stuff /should/ work.
debug3: channel 1: waiting for connection
debug1: channel 1: connected
debug1: channel 1: read<=0 rfd 7 len 0
debug1: channel 1: read failed
debug1: channel 1: close_read
debug1: channel 1: input open -> drain
debug1: channel 1: ibuf empty
debug1: channel 1: send eof
debug1: channel 1: input drain -> closed
debug1: channel 1: rcvd eof
debug1: channel 1: output open -> drain
debug1: channel 1: obuf empty
debug1: channel 1: close_write
debug1: channel 1: output drain -> closed
debug1: channel 1: rcvd close
debug3: channel 1: will not send data after close
debug1: channel 1: send close
debug1: channel 1: is dead
"""
Zerion Mini Shell 1.0