easyssh/tcpip.go

173 lines
4.1 KiB
Go

package easyssh
import (
"encoding/binary"
"fmt"
"net"
"strconv"
"golang.org/x/crypto/ssh"
)
// Applicaple SSH Request types for Port Forwarding - RFC 4254 7.X
const (
DirectForwardRequest = "direct-tcpip" // RFC 4254 7.2
RemoteForwardRequest = "tcpip-forward" // RFC 4254 7.1
ForwardedTCPReturnRequest = "forwarded-tcpip" // RFC 4254 7.2
CancelRemoteForwardRequest = "cancel-tcpip-forward" // RFC 4254 7.1
)
// tcpipForward is structure for RFC 4254 7.1 "tcpip-forward" request
type tcpipForward struct {
Host string
Port uint32
}
// directForward is struxture for RFC 4254 7.2 - can be used for "forwarded-tcpip" and "direct-tcpip"
type directForward struct {
Host1 string
Port1 uint32
Host2 string
Port2 uint32
}
func (p directForward) String() string {
return fmt.Sprintf("CONNECT: %s:%d FROM: %s:%d", p.Host1, p.Port1, p.Host2, p.Port2)
}
// TCPIPForwardRequestHandler returns a GlobalRequestHandler that implements remote port forwarding - ssh -R
func TCPIPForwardRequestHandler() GlobalRequestHandler {
return GlobalRequestHandlerFunc(TCPIPForwardRequest)
}
// TCPIPForwardRequest fulfills RFC 4254 7.1 "tcpip-forward" request
//
// TODO: Need to add state to handle "cancel-tcpip-forward"
func TCPIPForwardRequest(req *ssh.Request, sshConn ssh.Conn) {
t := tcpipForward{}
reply := (t.Port == 0) && req.WantReply
ssh.Unmarshal(req.Payload, &t)
addr := fmt.Sprintf("%s:%d", t.Host, t.Port)
ln, err := net.Listen("tcp", addr) //tie to the client connection
if err != nil {
logger.Println("Unable to listen on address: ", addr)
return
}
logger.Println("Listening on address: ", ln.Addr().String())
quit := make(chan bool)
if reply { // Client sent port 0. let them know which port is actually being used
_, port, err := getHostPortFromAddr(ln.Addr())
if err != nil {
return
}
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, uint32(port))
t.Port = uint32(port)
req.Reply(true, b)
} else {
req.Reply(true, nil)
}
go func() { // Handle incoming connections on this new listener
for {
select {
case <-quit:
return
default:
conn, err := ln.Accept()
if err != nil { // Unable to accept new connection - listener likely closed
continue
}
go func(conn net.Conn) {
p := directForward{}
var err error
var portnum int
p.Host1 = t.Host
p.Port1 = t.Port
p.Host2, portnum, err = getHostPortFromAddr(conn.RemoteAddr())
if err != nil {
return
}
p.Port2 = uint32(portnum)
ch, reqs, err := sshConn.OpenChannel(ForwardedTCPReturnRequest, ssh.Marshal(p))
if err != nil {
logger.Println("Open forwarded Channel: ", err.Error())
return
}
ssh.DiscardRequests(reqs)
go func(ch ssh.Channel, conn net.Conn) {
close := func() {
ch.Close()
conn.Close()
// logger.Printf("forwarding closed")
}
go CopyReadWriters(conn, ch, close)
}(ch, conn)
}(conn)
}
}
}()
sshConn.Wait()
logger.Println("Stop forwarding/listening on ", ln.Addr())
ln.Close()
quit <- true
}
func getHostPortFromAddr(addr net.Addr) (host string, port int, err error) {
host, portString, err := net.SplitHostPort(addr.String())
if err != nil {
return
}
port, err = strconv.Atoi(portString)
return
}
// DirectPortForwardHandler returns a ChannelHandler that implements standard SSH direct portforwarding
func DirectPortForwardHandler() ChannelHandler { return ChannelHandlerFunc(DirectPortForwardChannel) }
// DirectPortForwardChannel acts as an SSH Direct Port Forwarder - ssh -L
//
// Should be to channel type - "direct-tcpip" - RFC 4254 7.2
func DirectPortForwardChannel(newChannel ssh.NewChannel, channel ssh.Channel, reqs <-chan *ssh.Request, sshConn ssh.Conn) {
p := directForward{}
ssh.Unmarshal(newChannel.ExtraData(), &p)
logger.Println(p)
go func(ch ssh.Channel, sshConn ssh.Conn) {
addr := fmt.Sprintf("%s:%d", p.Host1, p.Port1)
conn, err := net.Dial("tcp", addr)
if err != nil {
return
}
close := func() {
ch.Close()
conn.Close()
//logger.Printf("forwarding closed")
}
go CopyReadWriters(conn, ch, close)
}(channel, sshConn)
}