aboutsummaryrefslogblamecommitdiffstats
path: root/vendor/golang.org/x/crypto/ssh/tcpip.go
blob: acf17175dffb1f504dc64f668e5b2cfe0a6ebb6a (plain) (tree)





















                                                                    
                                              
                                                               










                                                                          
         

























































































                                                                                                  
                                   













                                                                 
                      






                                                                   

                                                                          

 
                                                       


                          

                                             


























                                                                                   












































                                                                                                                                      
                 
                                                           




                                                                                     
 




                                                                
                                             


                                     
                                                                                             
















                                                                             
                                                                          


                                     

                                                                                               
























                                                                    
                         













                                         
                                       














                                                                                   












































                                                                           
         















                                                                                       
                         
































                                                                                          
                                                   
                                                      
                      




                                               
                                         



                                                 
                                          




                                                           
                                                          









                                                                   


                                                              




                                                                    
                                                               

                                                                 
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package ssh

import (
    "errors"
    "fmt"
    "io"
    "math/rand"
    "net"
    "strconv"
    "strings"
    "sync"
    "time"
)

// Listen requests the remote peer open a listening socket on
// addr. Incoming connections will be available by calling Accept on
// the returned net.Listener. The listener must be serviced, or the
// SSH connection may hang.
// N must be "tcp", "tcp4", "tcp6", or "unix".
func (c *Client) Listen(n, addr string) (net.Listener, error) {
    switch n {
    case "tcp", "tcp4", "tcp6":
        laddr, err := net.ResolveTCPAddr(n, addr)
        if err != nil {
            return nil, err
        }
        return c.ListenTCP(laddr)
    case "unix":
        return c.ListenUnix(addr)
    default:
        return nil, fmt.Errorf("ssh: unsupported protocol: %s", n)
    }
}

// Automatic port allocation is broken with OpenSSH before 6.0. See
// also https://bugzilla.mindrot.org/show_bug.cgi?id=2017.  In
// particular, OpenSSH 5.9 sends a channelOpenMsg with port number 0,
// rather than the actual port number. This means you can never open
// two different listeners with auto allocated ports. We work around
// this by trying explicit ports until we succeed.

const openSSHPrefix = "OpenSSH_"

var portRandomizer = rand.New(rand.NewSource(time.Now().UnixNano()))

// isBrokenOpenSSHVersion returns true if the given version string
// specifies a version of OpenSSH that is known to have a bug in port
// forwarding.
func isBrokenOpenSSHVersion(versionStr string) bool {
    i := strings.Index(versionStr, openSSHPrefix)
    if i < 0 {
        return false
    }
    i += len(openSSHPrefix)
    j := i
    for ; j < len(versionStr); j++ {
        if versionStr[j] < '0' || versionStr[j] > '9' {
            break
        }
    }
    version, _ := strconv.Atoi(versionStr[i:j])
    return version < 6
}

// autoPortListenWorkaround simulates automatic port allocation by
// trying random ports repeatedly.
func (c *Client) autoPortListenWorkaround(laddr *net.TCPAddr) (net.Listener, error) {
    var sshListener net.Listener
    var err error
    const tries = 10
    for i := 0; i < tries; i++ {
        addr := *laddr
        addr.Port = 1024 + portRandomizer.Intn(60000)
        sshListener, err = c.ListenTCP(&addr)
        if err == nil {
            laddr.Port = addr.Port
            return sshListener, err
        }
    }
    return nil, fmt.Errorf("ssh: listen on random port failed after %d tries: %v", tries, err)
}

// RFC 4254 7.1
type channelForwardMsg struct {
    addr  string
    rport uint32
}

// ListenTCP requests the remote peer open a listening socket
// on laddr. Incoming connections will be available by calling
// Accept on the returned net.Listener.
func (c *Client) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) {
    if laddr.Port == 0 && isBrokenOpenSSHVersion(string(c.ServerVersion())) {
        return c.autoPortListenWorkaround(laddr)
    }

    m := channelForwardMsg{
        laddr.IP.String(),
        uint32(laddr.Port),
    }
    // send message
    ok, resp, err := c.SendRequest("tcpip-forward", true, Marshal(&m))
    if err != nil {
        return nil, err
    }
    if !ok {
        return nil, errors.New("ssh: tcpip-forward request denied by peer")
    }

    // If the original port was 0, then the remote side will
    // supply a real port number in the response.
    if laddr.Port == 0 {
        var p struct {
            Port uint32
        }
        if err := Unmarshal(resp, &p); err != nil {
            return nil, err
        }
        laddr.Port = int(p.Port)
    }

    // Register this forward, using the port number we obtained.
    ch := c.forwards.add(laddr)

    return &tcpListener{laddr, c, ch}, nil
}

// forwardList stores a mapping between remote
// forward requests and the tcpListeners.
type forwardList struct {
    sync.Mutex
    entries []forwardEntry
}

// forwardEntry represents an established mapping of a laddr on a
// remote ssh server to a channel connected to a tcpListener.
type forwardEntry struct {
    laddr net.Addr
    c     chan forward
}

// forward represents an incoming forwarded tcpip connection. The
// arguments to add/remove/lookup should be address as specified in
// the original forward-request.
type forward struct {
    newCh NewChannel // the ssh client channel underlying this forward
    raddr net.Addr   // the raddr of the incoming connection
}

func (l *forwardList) add(addr net.Addr) chan forward {
    l.Lock()
    defer l.Unlock()
    f := forwardEntry{
        laddr: addr,
        c:     make(chan forward, 1),
    }
    l.entries = append(l.entries, f)
    return f.c
}

// See RFC 4254, section 7.2
type forwardedTCPPayload struct {
    Addr       string
    Port       uint32
    OriginAddr string
    OriginPort uint32
}

// parseTCPAddr parses the originating address from the remote into a *net.TCPAddr.
func parseTCPAddr(addr string, port uint32) (*net.TCPAddr, error) {
    if port == 0 || port > 65535 {
        return nil, fmt.Errorf("ssh: port number out of range: %d", port)
    }
    ip := net.ParseIP(string(addr))
    if ip == nil {
        return nil, fmt.Errorf("ssh: cannot parse IP address %q", addr)
    }
    return &net.TCPAddr{IP: ip, Port: int(port)}, nil
}

func (l *forwardList) handleChannels(in <-chan NewChannel) {
    for ch := range in {
        var (
            laddr net.Addr
            raddr net.Addr
            err   error
        )
        switch channelType := ch.ChannelType(); channelType {
        case "forwarded-tcpip":
            var payload forwardedTCPPayload
            if err = Unmarshal(ch.ExtraData(), &payload); err != nil {
                ch.Reject(ConnectionFailed, "could not parse forwarded-tcpip payload: "+err.Error())
                continue
            }

            // RFC 4254 section 7.2 specifies that incoming
            // addresses should list the address, in string
            // format. It is implied that this should be an IP
            // address, as it would be impossible to connect to it
            // otherwise.
            laddr, err = parseTCPAddr(payload.Addr, payload.Port)
            if err != nil {
                ch.Reject(ConnectionFailed, err.Error())
                continue
            }
            raddr, err = parseTCPAddr(payload.OriginAddr, payload.OriginPort)
            if err != nil {
                ch.Reject(ConnectionFailed, err.Error())
                continue
            }

        case "forwarded-streamlocal@openssh.com":
            var payload forwardedStreamLocalPayload
            if err = Unmarshal(ch.ExtraData(), &payload); err != nil {
                ch.Reject(ConnectionFailed, "could not parse forwarded-streamlocal@openssh.com payload: "+err.Error())
                continue
            }
            laddr = &net.UnixAddr{
                Name: payload.SocketPath,
                Net:  "unix",
            }
            raddr = &net.UnixAddr{
                Name: "@",
                Net:  "unix",
            }
        default:
            panic(fmt.Errorf("ssh: unknown channel type %s", channelType))
        }
        if ok := l.forward(laddr, raddr, ch); !ok {
            // Section 7.2, implementations MUST reject spurious incoming
            // connections.
            ch.Reject(Prohibited, "no forward for address")
            continue
        }

    }
}

// remove removes the forward entry, and the channel feeding its
// listener.
func (l *forwardList) remove(addr net.Addr) {
    l.Lock()
    defer l.Unlock()
    for i, f := range l.entries {
        if addr.Network() == f.laddr.Network() && addr.String() == f.laddr.String() {
            l.entries = append(l.entries[:i], l.entries[i+1:]...)
            close(f.c)
            return
        }
    }
}

// closeAll closes and clears all forwards.
func (l *forwardList) closeAll() {
    l.Lock()
    defer l.Unlock()
    for _, f := range l.entries {
        close(f.c)
    }
    l.entries = nil
}

func (l *forwardList) forward(laddr, raddr net.Addr, ch NewChannel) bool {
    l.Lock()
    defer l.Unlock()
    for _, f := range l.entries {
        if laddr.Network() == f.laddr.Network() && laddr.String() == f.laddr.String() {
            f.c <- forward{newCh: ch, raddr: raddr}
            return true
        }
    }
    return false
}

type tcpListener struct {
    laddr *net.TCPAddr

    conn *Client
    in   <-chan forward
}

// Accept waits for and returns the next connection to the listener.
func (l *tcpListener) Accept() (net.Conn, error) {
    s, ok := <-l.in
    if !ok {
        return nil, io.EOF
    }
    ch, incoming, err := s.newCh.Accept()
    if err != nil {
        return nil, err
    }
    go DiscardRequests(incoming)

    return &chanConn{
        Channel: ch,
        laddr:   l.laddr,
        raddr:   s.raddr,
    }, nil
}

// Close closes the listener.
func (l *tcpListener) Close() error {
    m := channelForwardMsg{
        l.laddr.IP.String(),
        uint32(l.laddr.Port),
    }

    // this also closes the listener.
    l.conn.forwards.remove(l.laddr)
    ok, _, err := l.conn.SendRequest("cancel-tcpip-forward", true, Marshal(&m))
    if err == nil && !ok {
        err = errors.New("ssh: cancel-tcpip-forward failed")
    }
    return err
}

// Addr returns the listener's network address.
func (l *tcpListener) Addr() net.Addr {
    return l.laddr
}

// Dial initiates a connection to the addr from the remote host.
// The resulting connection has a zero LocalAddr() and RemoteAddr().
func (c *Client) Dial(n, addr string) (net.Conn, error) {
    var ch Channel
    switch n {
    case "tcp", "tcp4", "tcp6":
        // Parse the address into host and numeric port.
        host, portString, err := net.SplitHostPort(addr)
        if err != nil {
            return nil, err
        }
        port, err := strconv.ParseUint(portString, 10, 16)
        if err != nil {
            return nil, err
        }
        ch, err = c.dial(net.IPv4zero.String(), 0, host, int(port))
        if err != nil {
            return nil, err
        }
        // Use a zero address for local and remote address.
        zeroAddr := &net.TCPAddr{
            IP:   net.IPv4zero,
            Port: 0,
        }
        return &chanConn{
            Channel: ch,
            laddr:   zeroAddr,
            raddr:   zeroAddr,
        }, nil
    case "unix":
        var err error
        ch, err = c.dialStreamLocal(addr)
        if err != nil {
            return nil, err
        }
        return &chanConn{
            Channel: ch,
            laddr: &net.UnixAddr{
                Name: "@",
                Net:  "unix",
            },
            raddr: &net.UnixAddr{
                Name: addr,
                Net:  "unix",
            },
        }, nil
    default:
        return nil, fmt.Errorf("ssh: unsupported protocol: %s", n)
    }
}

// DialTCP connects to the remote address raddr on the network net,
// which must be "tcp", "tcp4", or "tcp6".  If laddr is not nil, it is used
// as the local address for the connection.
func (c *Client) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error) {
    if laddr == nil {
        laddr = &net.TCPAddr{
            IP:   net.IPv4zero,
            Port: 0,
        }
    }
    ch, err := c.dial(laddr.IP.String(), laddr.Port, raddr.IP.String(), raddr.Port)
    if err != nil {
        return nil, err
    }
    return &chanConn{
        Channel: ch,
        laddr:   laddr,
        raddr:   raddr,
    }, nil
}

// RFC 4254 7.2
type channelOpenDirectMsg struct {
    raddr string
    rport uint32
    laddr string
    lport uint32
}

func (c *Client) dial(laddr string, lport int, raddr string, rport int) (Channel, error) {
    msg := channelOpenDirectMsg{
        raddr: raddr,
        rport: uint32(rport),
        laddr: laddr,
        lport: uint32(lport),
    }
    ch, in, err := c.OpenChannel("direct-tcpip", Marshal(&msg))
    if err != nil {
        return nil, err
    }
    go DiscardRequests(in)
    return ch, err
}

type tcpChan struct {
    Channel // the backing channel
}

// chanConn fulfills the net.Conn interface without
// the tcpChan having to hold laddr or raddr directly.
type chanConn struct {
    Channel
    laddr, raddr net.Addr
}

// LocalAddr returns the local network address.
func (t *chanConn) LocalAddr() net.Addr {
    return t.laddr
}

// RemoteAddr returns the remote network address.
func (t *chanConn) RemoteAddr() net.Addr {
    return t.raddr
}

// SetDeadline sets the read and write deadlines associated
// with the connection.
func (t *chanConn) SetDeadline(deadline time.Time) error {
    if err := t.SetReadDeadline(deadline); err != nil {
        return err
    }
    return t.SetWriteDeadline(deadline)
}

// SetReadDeadline sets the read deadline.
// A zero value for t means Read will not time out.
// After the deadline, the error from Read will implement net.Error
// with Timeout() == true.
func (t *chanConn) SetReadDeadline(deadline time.Time) error {
    // for compatibility with previous version,
    // the error message contains "tcpChan"
    return errors.New("ssh: tcpChan: deadline not supported")
}

// SetWriteDeadline exists to satisfy the net.Conn interface
// but is not implemented by this type.  It always returns an error.
func (t *chanConn) SetWriteDeadline(deadline time.Time) error {
    return errors.New("ssh: tcpChan: deadline not supported")
}