110 lines
2.3 KiB
Go
110 lines
2.3 KiB
Go
package ssh
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
|
|
"github.com/gliderlabs/ssh"
|
|
"github.com/pkg/errors"
|
|
gossh "golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
// direct-tcpip data struct as specified in RFC4254, Section 7.2
|
|
type localForwardChannelData struct {
|
|
DestAddr string
|
|
DestPort uint32
|
|
|
|
OriginAddr string
|
|
OriginPort uint32
|
|
}
|
|
|
|
func (s *Server) handleDirectTCP(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx ssh.Context) {
|
|
d := localForwardChannelData{}
|
|
if err := gossh.Unmarshal(newChan.ExtraData(), &d); err != nil {
|
|
s.log("[ERROR] %+v", errors.WithStack(err))
|
|
newChan.Reject(gossh.ConnectionFailed, "error parsing forward data: "+err.Error())
|
|
return
|
|
}
|
|
|
|
sessionID := SessionID(ctx.SessionID())
|
|
|
|
s.log("(%s): opening direct tcp tunnel", sessionID)
|
|
|
|
s.sessionManager.Set(sessionID, SessionData{
|
|
Type: TypeServiceConsumer,
|
|
})
|
|
|
|
sessionToken := ctx.User()
|
|
|
|
remoteSessionID := s.sessionManager.FindByToken(sessionToken)
|
|
if remoteSessionID == "" {
|
|
newChan.Reject(gossh.ConnectionFailed, fmt.Sprintf("could not find session associated with token '%s'", sessionToken))
|
|
conn.Close()
|
|
return
|
|
}
|
|
|
|
addr := s.getSocketPath(remoteSessionID)
|
|
|
|
s.log("(%s): using sock '%s'", sessionID, addr)
|
|
|
|
if _, err := os.Stat(addr); err != nil {
|
|
newChan.Reject(gossh.ConnectionFailed, fmt.Sprintf("could not find session associated with token '%s'", sessionToken))
|
|
conn.Close()
|
|
return
|
|
}
|
|
|
|
var dialer net.Dialer
|
|
dconn, err := dialer.DialContext(ctx, "unix", addr)
|
|
if err != nil {
|
|
s.log("[ERROR] %+v", errors.WithStack(err))
|
|
newChan.Reject(gossh.ConnectionFailed, err.Error())
|
|
return
|
|
}
|
|
|
|
ch, reqs, err := newChan.Accept()
|
|
if err != nil {
|
|
s.log("[ERROR] %+v", errors.WithStack(err))
|
|
dconn.Close()
|
|
|
|
return
|
|
}
|
|
|
|
go gossh.DiscardRequests(reqs)
|
|
|
|
go func() {
|
|
defer dconn.Close()
|
|
defer ch.Close()
|
|
|
|
reader := &instrumentedReader{
|
|
internal: dconn,
|
|
stats: s.opts.Stats,
|
|
name: StatTotalRxBytes,
|
|
}
|
|
|
|
if _, err := io.Copy(ch, reader); err != nil {
|
|
if errors.Is(err, net.ErrClosed) {
|
|
return
|
|
}
|
|
|
|
s.log("[ERROR] %+v", errors.WithStack(err))
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
defer dconn.Close()
|
|
defer ch.Close()
|
|
|
|
writer := &instrumentedWriter{
|
|
internal: dconn,
|
|
stats: s.opts.Stats,
|
|
name: StatTotalTxBytes,
|
|
}
|
|
|
|
if _, err := io.Copy(writer, ch); err != nil {
|
|
s.log("[ERROR] %+v", errors.WithStack(err))
|
|
}
|
|
}()
|
|
}
|