| 1 | # Copyright (c) Twisted Matrix Laboratories. |
|---|
| 2 | # See LICENSE for details. |
|---|
| 3 | |
|---|
| 4 | # |
|---|
| 5 | |
|---|
| 6 | """ |
|---|
| 7 | This module contains the implementation of the TCP forwarding, which allows |
|---|
| 8 | clients and servers to forward arbitrary TCP data across the connection. |
|---|
| 9 | |
|---|
| 10 | Maintainer: Paul Swartz |
|---|
| 11 | """ |
|---|
| 12 | |
|---|
| 13 | import struct |
|---|
| 14 | |
|---|
| 15 | from twisted.internet import protocol, reactor |
|---|
| 16 | from twisted.python import log |
|---|
| 17 | |
|---|
| 18 | import common, channel |
|---|
| 19 | |
|---|
| 20 | class SSHListenForwardingFactory(protocol.Factory): |
|---|
| 21 | def __init__(self, connection, hostport, klass): |
|---|
| 22 | self.conn = connection |
|---|
| 23 | self.hostport = hostport # tuple |
|---|
| 24 | self.klass = klass |
|---|
| 25 | |
|---|
| 26 | def buildProtocol(self, addr): |
|---|
| 27 | channel = self.klass(conn = self.conn) |
|---|
| 28 | client = SSHForwardingClient(channel) |
|---|
| 29 | channel.client = client |
|---|
| 30 | addrTuple = (addr.host, addr.port) |
|---|
| 31 | channelOpenData = packOpen_direct_tcpip(self.hostport, addrTuple) |
|---|
| 32 | self.conn.openChannel(channel, channelOpenData) |
|---|
| 33 | return client |
|---|
| 34 | |
|---|
| 35 | class SSHListenForwardingChannel(channel.SSHChannel): |
|---|
| 36 | |
|---|
| 37 | def channelOpen(self, specificData): |
|---|
| 38 | log.msg('opened forwarding channel %s' % self.id) |
|---|
| 39 | if len(self.client.buf)>1: |
|---|
| 40 | b = self.client.buf[1:] |
|---|
| 41 | self.write(b) |
|---|
| 42 | self.client.buf = '' |
|---|
| 43 | |
|---|
| 44 | def openFailed(self, reason): |
|---|
| 45 | self.closed() |
|---|
| 46 | |
|---|
| 47 | def dataReceived(self, data): |
|---|
| 48 | self.client.transport.write(data) |
|---|
| 49 | |
|---|
| 50 | def eofReceived(self): |
|---|
| 51 | self.client.transport.loseConnection() |
|---|
| 52 | |
|---|
| 53 | def closed(self): |
|---|
| 54 | if hasattr(self, 'client'): |
|---|
| 55 | log.msg('closing local forwarding channel %s' % self.id) |
|---|
| 56 | self.client.transport.loseConnection() |
|---|
| 57 | del self.client |
|---|
| 58 | |
|---|
| 59 | class SSHListenClientForwardingChannel(SSHListenForwardingChannel): |
|---|
| 60 | |
|---|
| 61 | name = 'direct-tcpip' |
|---|
| 62 | |
|---|
| 63 | class SSHListenServerForwardingChannel(SSHListenForwardingChannel): |
|---|
| 64 | |
|---|
| 65 | name = 'forwarded-tcpip' |
|---|
| 66 | |
|---|
| 67 | class SSHConnectForwardingChannel(channel.SSHChannel): |
|---|
| 68 | |
|---|
| 69 | def __init__(self, hostport, *args, **kw): |
|---|
| 70 | channel.SSHChannel.__init__(self, *args, **kw) |
|---|
| 71 | self.hostport = hostport |
|---|
| 72 | self.client = None |
|---|
| 73 | self.clientBuf = '' |
|---|
| 74 | |
|---|
| 75 | def channelOpen(self, specificData): |
|---|
| 76 | cc = protocol.ClientCreator(reactor, SSHForwardingClient, self) |
|---|
| 77 | log.msg("connecting to %s:%i" % self.hostport) |
|---|
| 78 | cc.connectTCP(*self.hostport).addCallbacks(self._setClient, self._close) |
|---|
| 79 | |
|---|
| 80 | def _setClient(self, client): |
|---|
| 81 | self.client = client |
|---|
| 82 | log.msg("connected to %s:%i" % self.hostport) |
|---|
| 83 | if self.clientBuf: |
|---|
| 84 | self.client.transport.write(self.clientBuf) |
|---|
| 85 | self.clientBuf = None |
|---|
| 86 | if self.client.buf[1:]: |
|---|
| 87 | self.write(self.client.buf[1:]) |
|---|
| 88 | self.client.buf = '' |
|---|
| 89 | |
|---|
| 90 | def _close(self, reason): |
|---|
| 91 | log.msg("failed to connect: %s" % reason) |
|---|
| 92 | self.loseConnection() |
|---|
| 93 | |
|---|
| 94 | def dataReceived(self, data): |
|---|
| 95 | if self.client: |
|---|
| 96 | self.client.transport.write(data) |
|---|
| 97 | else: |
|---|
| 98 | self.clientBuf += data |
|---|
| 99 | |
|---|
| 100 | def closed(self): |
|---|
| 101 | if self.client: |
|---|
| 102 | log.msg('closed remote forwarding channel %s' % self.id) |
|---|
| 103 | if self.client.channel: |
|---|
| 104 | self.loseConnection() |
|---|
| 105 | self.client.transport.loseConnection() |
|---|
| 106 | del self.client |
|---|
| 107 | |
|---|
| 108 | def openConnectForwardingClient(remoteWindow, remoteMaxPacket, data, avatar): |
|---|
| 109 | remoteHP, origHP = unpackOpen_direct_tcpip(data) |
|---|
| 110 | return SSHConnectForwardingChannel(remoteHP, |
|---|
| 111 | remoteWindow=remoteWindow, |
|---|
| 112 | remoteMaxPacket=remoteMaxPacket, |
|---|
| 113 | avatar=avatar) |
|---|
| 114 | |
|---|
| 115 | class SSHForwardingClient(protocol.Protocol): |
|---|
| 116 | |
|---|
| 117 | def __init__(self, channel): |
|---|
| 118 | self.channel = channel |
|---|
| 119 | self.buf = '\000' |
|---|
| 120 | |
|---|
| 121 | def dataReceived(self, data): |
|---|
| 122 | if self.buf: |
|---|
| 123 | self.buf += data |
|---|
| 124 | else: |
|---|
| 125 | self.channel.write(data) |
|---|
| 126 | |
|---|
| 127 | def connectionLost(self, reason): |
|---|
| 128 | if self.channel: |
|---|
| 129 | self.channel.loseConnection() |
|---|
| 130 | self.channel = None |
|---|
| 131 | |
|---|
| 132 | |
|---|
| 133 | def packOpen_direct_tcpip((connHost, connPort), (origHost, origPort)): |
|---|
| 134 | """Pack the data suitable for sending in a CHANNEL_OPEN packet. |
|---|
| 135 | """ |
|---|
| 136 | conn = common.NS(connHost) + struct.pack('>L', connPort) |
|---|
| 137 | orig = common.NS(origHost) + struct.pack('>L', origPort) |
|---|
| 138 | return conn + orig |
|---|
| 139 | |
|---|
| 140 | packOpen_forwarded_tcpip = packOpen_direct_tcpip |
|---|
| 141 | |
|---|
| 142 | def unpackOpen_direct_tcpip(data): |
|---|
| 143 | """Unpack the data to a usable format. |
|---|
| 144 | """ |
|---|
| 145 | connHost, rest = common.getNS(data) |
|---|
| 146 | connPort = int(struct.unpack('>L', rest[:4])[0]) |
|---|
| 147 | origHost, rest = common.getNS(rest[4:]) |
|---|
| 148 | origPort = int(struct.unpack('>L', rest[:4])[0]) |
|---|
| 149 | return (connHost, connPort), (origHost, origPort) |
|---|
| 150 | |
|---|
| 151 | unpackOpen_forwarded_tcpip = unpackOpen_direct_tcpip |
|---|
| 152 | |
|---|
| 153 | def packGlobal_tcpip_forward((host, port)): |
|---|
| 154 | return common.NS(host) + struct.pack('>L', port) |
|---|
| 155 | |
|---|
| 156 | def unpackGlobal_tcpip_forward(data): |
|---|
| 157 | host, rest = common.getNS(data) |
|---|
| 158 | port = int(struct.unpack('>L', rest[:4])[0]) |
|---|
| 159 | return host, port |
|---|
| 160 | |
|---|
| 161 | """This is how the data -> eof -> close stuff /should/ work. |
|---|
| 162 | |
|---|
| 163 | debug3: channel 1: waiting for connection |
|---|
| 164 | debug1: channel 1: connected |
|---|
| 165 | debug1: channel 1: read<=0 rfd 7 len 0 |
|---|
| 166 | debug1: channel 1: read failed |
|---|
| 167 | debug1: channel 1: close_read |
|---|
| 168 | debug1: channel 1: input open -> drain |
|---|
| 169 | debug1: channel 1: ibuf empty |
|---|
| 170 | debug1: channel 1: send eof |
|---|
| 171 | debug1: channel 1: input drain -> closed |
|---|
| 172 | debug1: channel 1: rcvd eof |
|---|
| 173 | debug1: channel 1: output open -> drain |
|---|
| 174 | debug1: channel 1: obuf empty |
|---|
| 175 | debug1: channel 1: close_write |
|---|
| 176 | debug1: channel 1: output drain -> closed |
|---|
| 177 | debug1: channel 1: rcvd close |
|---|
| 178 | debug3: channel 1: will not send data after close |
|---|
| 179 | debug1: channel 1: send close |
|---|
| 180 | debug1: channel 1: is dead |
|---|
| 181 | """ |
|---|