go.crypto/ssh: support OpenSSH keepalives
Fixes golang/go#4552.

R=minux.ma, agl
CC=golang-dev
https://golang.org/cl/6948059
diff --git a/ssh/client.go b/ssh/client.go
index a4daa24..e2a143e 100644
--- a/ssh/client.go
+++ b/ssh/client.go
@@ -309,6 +309,12 @@
 					// invalid window update
 					return
 				}
+			case *globalRequestMsg:
+				// This handles keepalive messages and matches
+				// the behaviour of OpenSSH.
+				if msg.WantReply {
+					c.writePacket(marshal(msgRequestFailure, globalRequestFailureMsg{}))
+				}
 			case *globalRequestSuccessMsg, *globalRequestFailureMsg:
 				c.globalRequest.response <- msg
 			case *disconnectMsg:
diff --git a/ssh/session.go b/ssh/session.go
index ac96d52..640d322 100644
--- a/ssh/session.go
+++ b/ssh/session.go
@@ -404,7 +404,13 @@
 				}
 				wm.lang = safeString(string(lang))
 			default:
-				return fmt.Errorf("wait: unexpected channel request: %v", msg)
+				// This handles keepalives and matches
+				// OpenSSH's behaviour.
+				if msg.WantReply {
+					s.writePacket(marshal(msgChannelFailure, channelRequestFailureMsg{
+						PeersId: s.remoteId,
+					}))
+				}
 			}
 		default:
 			return fmt.Errorf("wait: unexpected packet %T received: %v", msg, msg)
diff --git a/ssh/session_test.go b/ssh/session_test.go
index 0c64ed2..218d002 100644
--- a/ssh/session_test.go
+++ b/ssh/session_test.go
@@ -498,6 +498,24 @@
 	}
 }
 
+// Verify the client can handle a keepalive packet from the server.
+func TestClientHandlesKeepalives(t *testing.T) {
+	conn := dial(channelKeepaliveSender, t)
+	defer conn.Close()
+	session, err := conn.NewSession()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer session.Close()
+	if err := session.Shell(); err != nil {
+		t.Fatalf("Unable to execute command: %v", err)
+	}
+	err = session.Wait()
+	if err != nil {
+		t.Fatalf("expected nil but got: %v", err)
+	}
+}
+
 type exitStatusMsg struct {
 	PeersId   uint32
 	Request   string
@@ -687,3 +705,18 @@
 	}
 	return written, nil
 }
+
+func channelKeepaliveSender(ch *serverChan, t *testing.T) {
+	defer ch.Close()
+	shell := newServerShell(ch, "> ")
+	readLine(shell, t)
+	msg := channelRequestMsg{
+		PeersId:   ch.remoteId,
+		Request:   "keepalive@openssh.com",
+		WantReply: true,
+	}
+	if err := ch.writePacket(marshal(msgChannelRequest, msg)); err != nil {
+		t.Errorf("unable to send channel keepalive request: %v", err)
+	}
+	sendStatus(0, ch, t)
+}