|
|
// 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") }
|