| // 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 |
| } |
| |
| // handleForwards starts goroutines handling forwarded connections. |
| // It's called on first use by (*Client).ListenTCP to not launch |
| // goroutines until needed. |
| func (c *Client) handleForwards() { |
| go c.forwards.handleChannels(c.HandleChannelOpen("forwarded-tcpip")) |
| go c.forwards.handleChannels(c.HandleChannelOpen("forwarded-streamlocal@openssh.com")) |
| } |
| |
| // 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) { |
| c.handleForwardsOnce.Do(c.handleForwards) |
| 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") |
| } |