1 // Copyright 2011 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
11 crypto_rand "crypto/rand"
19 "golang.org/x/crypto/ssh/terminal"
22 type serverType func(Channel, <-chan *Request, *testing.T)
24 // dial constructs a new test server and returns a *ClientConn.
25 func dial(handler serverType, t *testing.T) *Client {
26 c1, c2, err := netPipe()
28 t.Fatalf("netPipe: %v", err)
36 conf.AddHostKey(testSigners["rsa"])
38 _, chans, reqs, err := NewServerConn(c1, &conf)
40 t.Fatalf("Unable to handshake: %v", err)
42 go DiscardRequests(reqs)
44 for newCh := range chans {
45 if newCh.ChannelType() != "session" {
46 newCh.Reject(UnknownChannelType, "unknown channel type")
50 ch, inReqs, err := newCh.Accept()
52 t.Errorf("Accept: %v", err)
56 handler(ch, inReqs, t)
61 config := &ClientConfig{
63 HostKeyCallback: InsecureIgnoreHostKey(),
66 conn, chans, reqs, err := NewClientConn(c2, "", config)
68 t.Fatalf("unable to dial remote side: %v", err)
71 return NewClient(conn, chans, reqs)
74 // Test a simple string is returned to session.Stdout.
75 func TestSessionShell(t *testing.T) {
76 conn := dial(shellHandler, t)
78 session, err := conn.NewSession()
80 t.Fatalf("Unable to request new session: %v", err)
83 stdout := new(bytes.Buffer)
84 session.Stdout = stdout
85 if err := session.Shell(); err != nil {
86 t.Fatalf("Unable to execute command: %s", err)
88 if err := session.Wait(); err != nil {
89 t.Fatalf("Remote command did not exit cleanly: %v", err)
91 actual := stdout.String()
92 if actual != "golang" {
93 t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual)
97 // TODO(dfc) add support for Std{in,err}Pipe when the Server supports it.
99 // Test a simple string is returned via StdoutPipe.
100 func TestSessionStdoutPipe(t *testing.T) {
101 conn := dial(shellHandler, t)
103 session, err := conn.NewSession()
105 t.Fatalf("Unable to request new session: %v", err)
107 defer session.Close()
108 stdout, err := session.StdoutPipe()
110 t.Fatalf("Unable to request StdoutPipe(): %v", err)
113 if err := session.Shell(); err != nil {
114 t.Fatalf("Unable to execute command: %v", err)
116 done := make(chan bool, 1)
118 if _, err := io.Copy(&buf, stdout); err != nil {
119 t.Errorf("Copy of stdout failed: %v", err)
123 if err := session.Wait(); err != nil {
124 t.Fatalf("Remote command did not exit cleanly: %v", err)
127 actual := buf.String()
128 if actual != "golang" {
129 t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual)
133 // Test that a simple string is returned via the Output helper,
134 // and that stderr is discarded.
135 func TestSessionOutput(t *testing.T) {
136 conn := dial(fixedOutputHandler, t)
138 session, err := conn.NewSession()
140 t.Fatalf("Unable to request new session: %v", err)
142 defer session.Close()
144 buf, err := session.Output("") // cmd is ignored by fixedOutputHandler
146 t.Error("Remote command did not exit cleanly:", err)
148 w := "this-is-stdout."
151 t.Error("Remote command did not return expected string:")
157 // Test that both stdout and stderr are returned
158 // via the CombinedOutput helper.
159 func TestSessionCombinedOutput(t *testing.T) {
160 conn := dial(fixedOutputHandler, t)
162 session, err := conn.NewSession()
164 t.Fatalf("Unable to request new session: %v", err)
166 defer session.Close()
168 buf, err := session.CombinedOutput("") // cmd is ignored by fixedOutputHandler
170 t.Error("Remote command did not exit cleanly:", err)
172 const stdout = "this-is-stdout."
173 const stderr = "this-is-stderr."
175 if g != stdout+stderr && g != stderr+stdout {
176 t.Error("Remote command did not return expected string:")
177 t.Logf("want %q, or %q", stdout+stderr, stderr+stdout)
182 // Test non-0 exit status is returned correctly.
183 func TestExitStatusNonZero(t *testing.T) {
184 conn := dial(exitStatusNonZeroHandler, t)
186 session, err := conn.NewSession()
188 t.Fatalf("Unable to request new session: %v", err)
190 defer session.Close()
191 if err := session.Shell(); err != nil {
192 t.Fatalf("Unable to execute command: %v", err)
196 t.Fatalf("expected command to fail but it didn't")
198 e, ok := err.(*ExitError)
200 t.Fatalf("expected *ExitError but got %T", err)
202 if e.ExitStatus() != 15 {
203 t.Fatalf("expected command to exit with 15 but got %v", e.ExitStatus())
207 // Test 0 exit status is returned correctly.
208 func TestExitStatusZero(t *testing.T) {
209 conn := dial(exitStatusZeroHandler, t)
211 session, err := conn.NewSession()
213 t.Fatalf("Unable to request new session: %v", err)
215 defer session.Close()
217 if err := session.Shell(); err != nil {
218 t.Fatalf("Unable to execute command: %v", err)
222 t.Fatalf("expected nil but got %v", err)
226 // Test exit signal and status are both returned correctly.
227 func TestExitSignalAndStatus(t *testing.T) {
228 conn := dial(exitSignalAndStatusHandler, t)
230 session, err := conn.NewSession()
232 t.Fatalf("Unable to request new session: %v", err)
234 defer session.Close()
235 if err := session.Shell(); err != nil {
236 t.Fatalf("Unable to execute command: %v", err)
240 t.Fatalf("expected command to fail but it didn't")
242 e, ok := err.(*ExitError)
244 t.Fatalf("expected *ExitError but got %T", err)
246 if e.Signal() != "TERM" || e.ExitStatus() != 15 {
247 t.Fatalf("expected command to exit with signal TERM and status 15 but got signal %s and status %v", e.Signal(), e.ExitStatus())
251 // Test exit signal and status are both returned correctly.
252 func TestKnownExitSignalOnly(t *testing.T) {
253 conn := dial(exitSignalHandler, t)
255 session, err := conn.NewSession()
257 t.Fatalf("Unable to request new session: %v", err)
259 defer session.Close()
260 if err := session.Shell(); err != nil {
261 t.Fatalf("Unable to execute command: %v", err)
265 t.Fatalf("expected command to fail but it didn't")
267 e, ok := err.(*ExitError)
269 t.Fatalf("expected *ExitError but got %T", err)
271 if e.Signal() != "TERM" || e.ExitStatus() != 143 {
272 t.Fatalf("expected command to exit with signal TERM and status 143 but got signal %s and status %v", e.Signal(), e.ExitStatus())
276 // Test exit signal and status are both returned correctly.
277 func TestUnknownExitSignal(t *testing.T) {
278 conn := dial(exitSignalUnknownHandler, t)
280 session, err := conn.NewSession()
282 t.Fatalf("Unable to request new session: %v", err)
284 defer session.Close()
285 if err := session.Shell(); err != nil {
286 t.Fatalf("Unable to execute command: %v", err)
290 t.Fatalf("expected command to fail but it didn't")
292 e, ok := err.(*ExitError)
294 t.Fatalf("expected *ExitError but got %T", err)
296 if e.Signal() != "SYS" || e.ExitStatus() != 128 {
297 t.Fatalf("expected command to exit with signal SYS and status 128 but got signal %s and status %v", e.Signal(), e.ExitStatus())
301 func TestExitWithoutStatusOrSignal(t *testing.T) {
302 conn := dial(exitWithoutSignalOrStatus, t)
304 session, err := conn.NewSession()
306 t.Fatalf("Unable to request new session: %v", err)
308 defer session.Close()
309 if err := session.Shell(); err != nil {
310 t.Fatalf("Unable to execute command: %v", err)
314 t.Fatalf("expected command to fail but it didn't")
316 if _, ok := err.(*ExitMissingError); !ok {
317 t.Fatalf("got %T want *ExitMissingError", err)
321 // windowTestBytes is the number of bytes that we'll send to the SSH server.
322 const windowTestBytes = 16000 * 200
324 // TestServerWindow writes random data to the server. The server is expected to echo
325 // the same data back, which is compared against the original.
326 func TestServerWindow(t *testing.T) {
327 origBuf := bytes.NewBuffer(make([]byte, 0, windowTestBytes))
328 io.CopyN(origBuf, crypto_rand.Reader, windowTestBytes)
329 origBytes := origBuf.Bytes()
331 conn := dial(echoHandler, t)
333 session, err := conn.NewSession()
337 defer session.Close()
338 result := make(chan []byte)
342 echoedBuf := bytes.NewBuffer(make([]byte, 0, windowTestBytes))
343 serverStdout, err := session.StdoutPipe()
345 t.Errorf("StdoutPipe failed: %v", err)
348 n, err := copyNRandomly("stdout", echoedBuf, serverStdout, windowTestBytes)
349 if err != nil && err != io.EOF {
350 t.Errorf("Read only %d bytes from server, expected %d: %v", n, windowTestBytes, err)
352 result <- echoedBuf.Bytes()
355 serverStdin, err := session.StdinPipe()
357 t.Fatalf("StdinPipe failed: %v", err)
359 written, err := copyNRandomly("stdin", serverStdin, origBuf, windowTestBytes)
361 t.Fatalf("failed to copy origBuf to serverStdin: %v", err)
363 if written != windowTestBytes {
364 t.Fatalf("Wrote only %d of %d bytes to server", written, windowTestBytes)
367 echoedBytes := <-result
369 if !bytes.Equal(origBytes, echoedBytes) {
370 t.Fatalf("Echoed buffer differed from original, orig %d, echoed %d", len(origBytes), len(echoedBytes))
374 // Verify the client can handle a keepalive packet from the server.
375 func TestClientHandlesKeepalives(t *testing.T) {
376 conn := dial(channelKeepaliveSender, t)
378 session, err := conn.NewSession()
382 defer session.Close()
383 if err := session.Shell(); err != nil {
384 t.Fatalf("Unable to execute command: %v", err)
388 t.Fatalf("expected nil but got: %v", err)
392 type exitStatusMsg struct {
396 type exitSignalMsg struct {
403 func handleTerminalRequests(in <-chan *Request) {
404 for req := range in {
409 if len(req.Payload) > 0 {
410 // We don't accept any commands, only the default shell.
420 func newServerShell(ch Channel, in <-chan *Request, prompt string) *terminal.Terminal {
421 term := terminal.NewTerminal(ch, prompt)
422 go handleTerminalRequests(in)
426 func exitStatusZeroHandler(ch Channel, in <-chan *Request, t *testing.T) {
428 // this string is returned to stdout
429 shell := newServerShell(ch, in, "> ")
434 func exitStatusNonZeroHandler(ch Channel, in <-chan *Request, t *testing.T) {
436 shell := newServerShell(ch, in, "> ")
438 sendStatus(15, ch, t)
441 func exitSignalAndStatusHandler(ch Channel, in <-chan *Request, t *testing.T) {
443 shell := newServerShell(ch, in, "> ")
445 sendStatus(15, ch, t)
446 sendSignal("TERM", ch, t)
449 func exitSignalHandler(ch Channel, in <-chan *Request, t *testing.T) {
451 shell := newServerShell(ch, in, "> ")
453 sendSignal("TERM", ch, t)
456 func exitSignalUnknownHandler(ch Channel, in <-chan *Request, t *testing.T) {
458 shell := newServerShell(ch, in, "> ")
460 sendSignal("SYS", ch, t)
463 func exitWithoutSignalOrStatus(ch Channel, in <-chan *Request, t *testing.T) {
465 shell := newServerShell(ch, in, "> ")
469 func shellHandler(ch Channel, in <-chan *Request, t *testing.T) {
471 // this string is returned to stdout
472 shell := newServerShell(ch, in, "golang")
477 // Ignores the command, writes fixed strings to stderr and stdout.
478 // Strings are "this-is-stdout." and "this-is-stderr.".
479 func fixedOutputHandler(ch Channel, in <-chan *Request, t *testing.T) {
481 _, err := ch.Read(nil)
485 t.Fatalf("error: expected channel request, got: %#v", err)
489 // ignore request, always send some text
492 _, err = io.WriteString(ch, "this-is-stdout.")
494 t.Fatalf("error writing on server: %v", err)
496 _, err = io.WriteString(ch.Stderr(), "this-is-stderr.")
498 t.Fatalf("error writing on server: %v", err)
503 func readLine(shell *terminal.Terminal, t *testing.T) {
504 if _, err := shell.ReadLine(); err != nil && err != io.EOF {
505 t.Errorf("unable to read line: %v", err)
509 func sendStatus(status uint32, ch Channel, t *testing.T) {
510 msg := exitStatusMsg{
513 if _, err := ch.SendRequest("exit-status", false, Marshal(&msg)); err != nil {
514 t.Errorf("unable to send status: %v", err)
518 func sendSignal(signal string, ch Channel, t *testing.T) {
519 sig := exitSignalMsg{
522 Errmsg: "Process terminated",
525 if _, err := ch.SendRequest("exit-signal", false, Marshal(&sig)); err != nil {
526 t.Errorf("unable to send signal: %v", err)
530 func discardHandler(ch Channel, t *testing.T) {
532 io.Copy(ioutil.Discard, ch)
535 func echoHandler(ch Channel, in <-chan *Request, t *testing.T) {
537 if n, err := copyNRandomly("echohandler", ch, ch, windowTestBytes); err != nil {
538 t.Errorf("short write, wrote %d, expected %d: %v ", n, windowTestBytes, err)
542 // copyNRandomly copies n bytes from src to dst. It uses a variable, and random,
543 // buffer size to exercise more code paths.
544 func copyNRandomly(title string, dst io.Writer, src io.Reader, n int) (int, error) {
546 buf = make([]byte, 32*1024)
551 l := rand.Intn(1 << 15)
555 nr, er := src.Read(buf[:l])
556 nw, ew := dst.Write(buf[:nr])
563 return written, io.ErrShortWrite
565 if er != nil && er != io.EOF {
572 func channelKeepaliveSender(ch Channel, in <-chan *Request, t *testing.T) {
574 shell := newServerShell(ch, in, "> ")
576 if _, err := ch.SendRequest("keepalive@openssh.com", true, nil); err != nil {
577 t.Errorf("unable to send channel keepalive request: %v", err)
582 func TestClientWriteEOF(t *testing.T) {
583 conn := dial(simpleEchoHandler, t)
586 session, err := conn.NewSession()
590 defer session.Close()
591 stdin, err := session.StdinPipe()
593 t.Fatalf("StdinPipe failed: %v", err)
595 stdout, err := session.StdoutPipe()
597 t.Fatalf("StdoutPipe failed: %v", err)
600 data := []byte(`0000`)
601 _, err = stdin.Write(data)
603 t.Fatalf("Write failed: %v", err)
607 res, err := ioutil.ReadAll(stdout)
609 t.Fatalf("Read failed: %v", err)
612 if !bytes.Equal(data, res) {
613 t.Fatalf("Read differed from write, wrote: %v, read: %v", data, res)
617 func simpleEchoHandler(ch Channel, in <-chan *Request, t *testing.T) {
619 data, err := ioutil.ReadAll(ch)
621 t.Errorf("handler read error: %v", err)
623 _, err = ch.Write(data)
625 t.Errorf("handler write error: %v", err)
629 func TestSessionID(t *testing.T) {
630 c1, c2, err := netPipe()
632 t.Fatalf("netPipe: %v", err)
637 serverID := make(chan []byte, 1)
638 clientID := make(chan []byte, 1)
640 serverConf := &ServerConfig{
643 serverConf.AddHostKey(testSigners["ecdsa"])
644 clientConf := &ClientConfig{
645 HostKeyCallback: InsecureIgnoreHostKey(),
650 conn, chans, reqs, err := NewServerConn(c1, serverConf)
652 t.Fatalf("server handshake: %v", err)
654 serverID <- conn.SessionID()
655 go DiscardRequests(reqs)
656 for ch := range chans {
657 ch.Reject(Prohibited, "")
662 conn, chans, reqs, err := NewClientConn(c2, "", clientConf)
664 t.Fatalf("client handshake: %v", err)
666 clientID <- conn.SessionID()
667 go DiscardRequests(reqs)
668 for ch := range chans {
669 ch.Reject(Prohibited, "")
675 if bytes.Compare(s, c) != 0 {
676 t.Errorf("server session ID (%x) != client session ID (%x)", s, c)
677 } else if len(s) == 0 {
678 t.Errorf("client and server SessionID were empty.")
682 type noReadConn struct {
687 func (c *noReadConn) Close() error {
691 func (c *noReadConn) Read(b []byte) (int, error) {
693 return 0, errors.New("noReadConn error")
696 func TestInvalidServerConfiguration(t *testing.T) {
697 c1, c2, err := netPipe()
699 t.Fatalf("netPipe: %v", err)
704 serveConn := noReadConn{Conn: c1}
705 serverConf := &ServerConfig{}
707 NewServerConn(&serveConn, serverConf)
708 if serveConn.readSeen {
709 t.Fatalf("NewServerConn attempted to Read() from Conn while configuration is missing host key")
712 serverConf.AddHostKey(testSigners["ecdsa"])
714 NewServerConn(&serveConn, serverConf)
715 if serveConn.readSeen {
716 t.Fatalf("NewServerConn attempted to Read() from Conn while configuration is missing authentication method")
720 func TestHostKeyAlgorithms(t *testing.T) {
721 serverConf := &ServerConfig{
724 serverConf.AddHostKey(testSigners["rsa"])
725 serverConf.AddHostKey(testSigners["ecdsa"])
727 connect := func(clientConf *ClientConfig, want string) {
729 clientConf.HostKeyCallback = func(h string, a net.Addr, key PublicKey) error {
733 c1, c2, err := netPipe()
735 t.Fatalf("netPipe: %v", err)
740 go NewServerConn(c1, serverConf)
741 _, _, _, err = NewClientConn(c2, "", clientConf)
743 t.Fatalf("NewClientConn: %v", err)
746 t.Errorf("selected key algorithm %s, want %s", alg, want)
750 // By default, we get the preferred algorithm, which is ECDSA 256.
752 clientConf := &ClientConfig{
753 HostKeyCallback: InsecureIgnoreHostKey(),
755 connect(clientConf, KeyAlgoECDSA256)
757 // Client asks for RSA explicitly.
758 clientConf.HostKeyAlgorithms = []string{KeyAlgoRSA}
759 connect(clientConf, KeyAlgoRSA)
761 c1, c2, err := netPipe()
763 t.Fatalf("netPipe: %v", err)
768 go NewServerConn(c1, serverConf)
769 clientConf.HostKeyAlgorithms = []string{"nonexistent-hostkey-algo"}
770 _, _, _, err = NewClientConn(c2, "", clientConf)
772 t.Fatal("succeeded connecting with unknown hostkey algorithm")