go.crypto/ssh: add workaround for broken port forwarding in
OpenSSH 5.

Tested with OpenSSH_5.9

R=agl, dave
CC=golang-dev
https://golang.org/cl/11921043
diff --git a/ssh/tcpip.go b/ssh/tcpip.go
index ad92b43..5abd690 100644
--- a/ssh/tcpip.go
+++ b/ssh/tcpip.go
@@ -8,7 +8,10 @@
 	"errors"
 	"fmt"
 	"io"
+	"math/rand"
 	"net"
+	"strconv"
+	"strings"
 	"sync"
 	"time"
 )
@@ -32,10 +35,60 @@
 	rport     uint32
 }
 
+// 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_"
+
+// 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 *ClientConn) 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 + rand.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)
+}
+
 // 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 *ClientConn) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) {
+	if laddr.Port == 0 && isBrokenOpenSSHVersion(c.serverVersion) {
+		return c.autoPortListenWorkaround(laddr)
+	}
+
 	m := channelForwardMsg{
 		"tcpip-forward",
 		true, // sendGlobalRequest waits for a reply
@@ -59,10 +112,6 @@
 	}
 
 	// Register this forward, using the port number we obtained.
-	//
-	// This does not work on OpenSSH < 6.0, which will send a
-	// channelOpenMsg with port number 0, rather than the actual
-	// port number.
 	ch := c.forwardList.add(*laddr)
 
 	return &tcpListener{laddr, c, ch}, nil