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.
4
5package ssh
6
7// Session tests.
8
9import (
10	"bytes"
11	crypto_rand "crypto/rand"
12	"errors"
13	"io"
14	"io/ioutil"
15	"math/rand"
16	"net"
17	"testing"
18
19	"golang.org/x/crypto/ssh/terminal"
20)
21
22type serverType func(Channel, <-chan *Request, *testing.T)
23
24// dial constructs a new test server and returns a *ClientConn.
25func dial(handler serverType, t *testing.T) *Client {
26	c1, c2, err := netPipe()
27	if err != nil {
28		t.Fatalf("netPipe: %v", err)
29	}
30
31	go func() {
32		defer c1.Close()
33		conf := ServerConfig{
34			NoClientAuth: true,
35		}
36		conf.AddHostKey(testSigners["rsa"])
37
38		conn, chans, reqs, err := NewServerConn(c1, &conf)
39		if err != nil {
40			t.Fatalf("Unable to handshake: %v", err)
41		}
42		go DiscardRequests(reqs)
43
44		for newCh := range chans {
45			if newCh.ChannelType() != "session" {
46				newCh.Reject(UnknownChannelType, "unknown channel type")
47				continue
48			}
49
50			ch, inReqs, err := newCh.Accept()
51			if err != nil {
52				t.Errorf("Accept: %v", err)
53				continue
54			}
55			go func() {
56				handler(ch, inReqs, t)
57			}()
58		}
59		if err := conn.Wait(); err != io.EOF {
60			t.Logf("server exit reason: %v", err)
61		}
62	}()
63
64	config := &ClientConfig{
65		User:            "testuser",
66		HostKeyCallback: InsecureIgnoreHostKey(),
67	}
68
69	conn, chans, reqs, err := NewClientConn(c2, "", config)
70	if err != nil {
71		t.Fatalf("unable to dial remote side: %v", err)
72	}
73
74	return NewClient(conn, chans, reqs)
75}
76
77// Test a simple string is returned to session.Stdout.
78func TestSessionShell(t *testing.T) {
79	conn := dial(shellHandler, t)
80	defer conn.Close()
81	session, err := conn.NewSession()
82	if err != nil {
83		t.Fatalf("Unable to request new session: %v", err)
84	}
85	defer session.Close()
86	stdout := new(bytes.Buffer)
87	session.Stdout = stdout
88	if err := session.Shell(); err != nil {
89		t.Fatalf("Unable to execute command: %s", err)
90	}
91	if err := session.Wait(); err != nil {
92		t.Fatalf("Remote command did not exit cleanly: %v", err)
93	}
94	actual := stdout.String()
95	if actual != "golang" {
96		t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual)
97	}
98}
99
100// TODO(dfc) add support for Std{in,err}Pipe when the Server supports it.
101
102// Test a simple string is returned via StdoutPipe.
103func TestSessionStdoutPipe(t *testing.T) {
104	conn := dial(shellHandler, t)
105	defer conn.Close()
106	session, err := conn.NewSession()
107	if err != nil {
108		t.Fatalf("Unable to request new session: %v", err)
109	}
110	defer session.Close()
111	stdout, err := session.StdoutPipe()
112	if err != nil {
113		t.Fatalf("Unable to request StdoutPipe(): %v", err)
114	}
115	var buf bytes.Buffer
116	if err := session.Shell(); err != nil {
117		t.Fatalf("Unable to execute command: %v", err)
118	}
119	done := make(chan bool, 1)
120	go func() {
121		if _, err := io.Copy(&buf, stdout); err != nil {
122			t.Errorf("Copy of stdout failed: %v", err)
123		}
124		done <- true
125	}()
126	if err := session.Wait(); err != nil {
127		t.Fatalf("Remote command did not exit cleanly: %v", err)
128	}
129	<-done
130	actual := buf.String()
131	if actual != "golang" {
132		t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual)
133	}
134}
135
136// Test that a simple string is returned via the Output helper,
137// and that stderr is discarded.
138func TestSessionOutput(t *testing.T) {
139	conn := dial(fixedOutputHandler, t)
140	defer conn.Close()
141	session, err := conn.NewSession()
142	if err != nil {
143		t.Fatalf("Unable to request new session: %v", err)
144	}
145	defer session.Close()
146
147	buf, err := session.Output("") // cmd is ignored by fixedOutputHandler
148	if err != nil {
149		t.Error("Remote command did not exit cleanly:", err)
150	}
151	w := "this-is-stdout."
152	g := string(buf)
153	if g != w {
154		t.Error("Remote command did not return expected string:")
155		t.Logf("want %q", w)
156		t.Logf("got  %q", g)
157	}
158}
159
160// Test that both stdout and stderr are returned
161// via the CombinedOutput helper.
162func TestSessionCombinedOutput(t *testing.T) {
163	conn := dial(fixedOutputHandler, t)
164	defer conn.Close()
165	session, err := conn.NewSession()
166	if err != nil {
167		t.Fatalf("Unable to request new session: %v", err)
168	}
169	defer session.Close()
170
171	buf, err := session.CombinedOutput("") // cmd is ignored by fixedOutputHandler
172	if err != nil {
173		t.Error("Remote command did not exit cleanly:", err)
174	}
175	const stdout = "this-is-stdout."
176	const stderr = "this-is-stderr."
177	g := string(buf)
178	if g != stdout+stderr && g != stderr+stdout {
179		t.Error("Remote command did not return expected string:")
180		t.Logf("want %q, or %q", stdout+stderr, stderr+stdout)
181		t.Logf("got  %q", g)
182	}
183}
184
185// Test non-0 exit status is returned correctly.
186func TestExitStatusNonZero(t *testing.T) {
187	conn := dial(exitStatusNonZeroHandler, t)
188	defer conn.Close()
189	session, err := conn.NewSession()
190	if err != nil {
191		t.Fatalf("Unable to request new session: %v", err)
192	}
193	defer session.Close()
194	if err := session.Shell(); err != nil {
195		t.Fatalf("Unable to execute command: %v", err)
196	}
197	err = session.Wait()
198	if err == nil {
199		t.Fatalf("expected command to fail but it didn't")
200	}
201	e, ok := err.(*ExitError)
202	if !ok {
203		t.Fatalf("expected *ExitError but got %T", err)
204	}
205	if e.ExitStatus() != 15 {
206		t.Fatalf("expected command to exit with 15 but got %v", e.ExitStatus())
207	}
208}
209
210// Test 0 exit status is returned correctly.
211func TestExitStatusZero(t *testing.T) {
212	conn := dial(exitStatusZeroHandler, t)
213	defer conn.Close()
214	session, err := conn.NewSession()
215	if err != nil {
216		t.Fatalf("Unable to request new session: %v", err)
217	}
218	defer session.Close()
219
220	if err := session.Shell(); err != nil {
221		t.Fatalf("Unable to execute command: %v", err)
222	}
223	err = session.Wait()
224	if err != nil {
225		t.Fatalf("expected nil but got %v", err)
226	}
227}
228
229// Test exit signal and status are both returned correctly.
230func TestExitSignalAndStatus(t *testing.T) {
231	conn := dial(exitSignalAndStatusHandler, t)
232	defer conn.Close()
233	session, err := conn.NewSession()
234	if err != nil {
235		t.Fatalf("Unable to request new session: %v", err)
236	}
237	defer session.Close()
238	if err := session.Shell(); err != nil {
239		t.Fatalf("Unable to execute command: %v", err)
240	}
241	err = session.Wait()
242	if err == nil {
243		t.Fatalf("expected command to fail but it didn't")
244	}
245	e, ok := err.(*ExitError)
246	if !ok {
247		t.Fatalf("expected *ExitError but got %T", err)
248	}
249	if e.Signal() != "TERM" || e.ExitStatus() != 15 {
250		t.Fatalf("expected command to exit with signal TERM and status 15 but got signal %s and status %v", e.Signal(), e.ExitStatus())
251	}
252}
253
254// Test exit signal and status are both returned correctly.
255func TestKnownExitSignalOnly(t *testing.T) {
256	conn := dial(exitSignalHandler, t)
257	defer conn.Close()
258	session, err := conn.NewSession()
259	if err != nil {
260		t.Fatalf("Unable to request new session: %v", err)
261	}
262	defer session.Close()
263	if err := session.Shell(); err != nil {
264		t.Fatalf("Unable to execute command: %v", err)
265	}
266	err = session.Wait()
267	if err == nil {
268		t.Fatalf("expected command to fail but it didn't")
269	}
270	e, ok := err.(*ExitError)
271	if !ok {
272		t.Fatalf("expected *ExitError but got %T", err)
273	}
274	if e.Signal() != "TERM" || e.ExitStatus() != 143 {
275		t.Fatalf("expected command to exit with signal TERM and status 143 but got signal %s and status %v", e.Signal(), e.ExitStatus())
276	}
277}
278
279// Test exit signal and status are both returned correctly.
280func TestUnknownExitSignal(t *testing.T) {
281	conn := dial(exitSignalUnknownHandler, t)
282	defer conn.Close()
283	session, err := conn.NewSession()
284	if err != nil {
285		t.Fatalf("Unable to request new session: %v", err)
286	}
287	defer session.Close()
288	if err := session.Shell(); err != nil {
289		t.Fatalf("Unable to execute command: %v", err)
290	}
291	err = session.Wait()
292	if err == nil {
293		t.Fatalf("expected command to fail but it didn't")
294	}
295	e, ok := err.(*ExitError)
296	if !ok {
297		t.Fatalf("expected *ExitError but got %T", err)
298	}
299	if e.Signal() != "SYS" || e.ExitStatus() != 128 {
300		t.Fatalf("expected command to exit with signal SYS and status 128 but got signal %s and status %v", e.Signal(), e.ExitStatus())
301	}
302}
303
304func TestExitWithoutStatusOrSignal(t *testing.T) {
305	conn := dial(exitWithoutSignalOrStatus, t)
306	defer conn.Close()
307	session, err := conn.NewSession()
308	if err != nil {
309		t.Fatalf("Unable to request new session: %v", err)
310	}
311	defer session.Close()
312	if err := session.Shell(); err != nil {
313		t.Fatalf("Unable to execute command: %v", err)
314	}
315	err = session.Wait()
316	if err == nil {
317		t.Fatalf("expected command to fail but it didn't")
318	}
319	if _, ok := err.(*ExitMissingError); !ok {
320		t.Fatalf("got %T want *ExitMissingError", err)
321	}
322}
323
324// windowTestBytes is the number of bytes that we'll send to the SSH server.
325const windowTestBytes = 16000 * 200
326
327// TestServerWindow writes random data to the server. The server is expected to echo
328// the same data back, which is compared against the original.
329func TestServerWindow(t *testing.T) {
330	origBuf := bytes.NewBuffer(make([]byte, 0, windowTestBytes))
331	io.CopyN(origBuf, crypto_rand.Reader, windowTestBytes)
332	origBytes := origBuf.Bytes()
333
334	conn := dial(echoHandler, t)
335	defer conn.Close()
336	session, err := conn.NewSession()
337	if err != nil {
338		t.Fatal(err)
339	}
340	defer session.Close()
341	result := make(chan []byte)
342
343	go func() {
344		defer close(result)
345		echoedBuf := bytes.NewBuffer(make([]byte, 0, windowTestBytes))
346		serverStdout, err := session.StdoutPipe()
347		if err != nil {
348			t.Errorf("StdoutPipe failed: %v", err)
349			return
350		}
351		n, err := copyNRandomly("stdout", echoedBuf, serverStdout, windowTestBytes)
352		if err != nil && err != io.EOF {
353			t.Errorf("Read only %d bytes from server, expected %d: %v", n, windowTestBytes, err)
354		}
355		result <- echoedBuf.Bytes()
356	}()
357
358	serverStdin, err := session.StdinPipe()
359	if err != nil {
360		t.Fatalf("StdinPipe failed: %v", err)
361	}
362	written, err := copyNRandomly("stdin", serverStdin, origBuf, windowTestBytes)
363	if err != nil {
364		t.Errorf("failed to copy origBuf to serverStdin: %v", err)
365	} else if written != windowTestBytes {
366		t.Errorf("Wrote only %d of %d bytes to server", written, windowTestBytes)
367	}
368
369	echoedBytes := <-result
370
371	if !bytes.Equal(origBytes, echoedBytes) {
372		t.Fatalf("Echoed buffer differed from original, orig %d, echoed %d", len(origBytes), len(echoedBytes))
373	}
374}
375
376// Verify the client can handle a keepalive packet from the server.
377func TestClientHandlesKeepalives(t *testing.T) {
378	conn := dial(channelKeepaliveSender, t)
379	defer conn.Close()
380	session, err := conn.NewSession()
381	if err != nil {
382		t.Fatal(err)
383	}
384	defer session.Close()
385	if err := session.Shell(); err != nil {
386		t.Fatalf("Unable to execute command: %v", err)
387	}
388	err = session.Wait()
389	if err != nil {
390		t.Fatalf("expected nil but got: %v", err)
391	}
392}
393
394type exitStatusMsg struct {
395	Status uint32
396}
397
398type exitSignalMsg struct {
399	Signal     string
400	CoreDumped bool
401	Errmsg     string
402	Lang       string
403}
404
405func handleTerminalRequests(in <-chan *Request) {
406	for req := range in {
407		ok := false
408		switch req.Type {
409		case "shell":
410			ok = true
411			if len(req.Payload) > 0 {
412				// We don't accept any commands, only the default shell.
413				ok = false
414			}
415		case "env":
416			ok = true
417		}
418		req.Reply(ok, nil)
419	}
420}
421
422func newServerShell(ch Channel, in <-chan *Request, prompt string) *terminal.Terminal {
423	term := terminal.NewTerminal(ch, prompt)
424	go handleTerminalRequests(in)
425	return term
426}
427
428func exitStatusZeroHandler(ch Channel, in <-chan *Request, t *testing.T) {
429	defer ch.Close()
430	// this string is returned to stdout
431	shell := newServerShell(ch, in, "> ")
432	readLine(shell, t)
433	sendStatus(0, ch, t)
434}
435
436func exitStatusNonZeroHandler(ch Channel, in <-chan *Request, t *testing.T) {
437	defer ch.Close()
438	shell := newServerShell(ch, in, "> ")
439	readLine(shell, t)
440	sendStatus(15, ch, t)
441}
442
443func exitSignalAndStatusHandler(ch Channel, in <-chan *Request, t *testing.T) {
444	defer ch.Close()
445	shell := newServerShell(ch, in, "> ")
446	readLine(shell, t)
447	sendStatus(15, ch, t)
448	sendSignal("TERM", ch, t)
449}
450
451func exitSignalHandler(ch Channel, in <-chan *Request, t *testing.T) {
452	defer ch.Close()
453	shell := newServerShell(ch, in, "> ")
454	readLine(shell, t)
455	sendSignal("TERM", ch, t)
456}
457
458func exitSignalUnknownHandler(ch Channel, in <-chan *Request, t *testing.T) {
459	defer ch.Close()
460	shell := newServerShell(ch, in, "> ")
461	readLine(shell, t)
462	sendSignal("SYS", ch, t)
463}
464
465func exitWithoutSignalOrStatus(ch Channel, in <-chan *Request, t *testing.T) {
466	defer ch.Close()
467	shell := newServerShell(ch, in, "> ")
468	readLine(shell, t)
469}
470
471func shellHandler(ch Channel, in <-chan *Request, t *testing.T) {
472	defer ch.Close()
473	// this string is returned to stdout
474	shell := newServerShell(ch, in, "golang")
475	readLine(shell, t)
476	sendStatus(0, ch, t)
477}
478
479// Ignores the command, writes fixed strings to stderr and stdout.
480// Strings are "this-is-stdout." and "this-is-stderr.".
481func fixedOutputHandler(ch Channel, in <-chan *Request, t *testing.T) {
482	defer ch.Close()
483	_, err := ch.Read(nil)
484
485	req, ok := <-in
486	if !ok {
487		t.Fatalf("error: expected channel request, got: %#v", err)
488		return
489	}
490
491	// ignore request, always send some text
492	req.Reply(true, nil)
493
494	_, err = io.WriteString(ch, "this-is-stdout.")
495	if err != nil {
496		t.Fatalf("error writing on server: %v", err)
497	}
498	_, err = io.WriteString(ch.Stderr(), "this-is-stderr.")
499	if err != nil {
500		t.Fatalf("error writing on server: %v", err)
501	}
502	sendStatus(0, ch, t)
503}
504
505func readLine(shell *terminal.Terminal, t *testing.T) {
506	if _, err := shell.ReadLine(); err != nil && err != io.EOF {
507		t.Errorf("unable to read line: %v", err)
508	}
509}
510
511func sendStatus(status uint32, ch Channel, t *testing.T) {
512	msg := exitStatusMsg{
513		Status: status,
514	}
515	if _, err := ch.SendRequest("exit-status", false, Marshal(&msg)); err != nil {
516		t.Errorf("unable to send status: %v", err)
517	}
518}
519
520func sendSignal(signal string, ch Channel, t *testing.T) {
521	sig := exitSignalMsg{
522		Signal:     signal,
523		CoreDumped: false,
524		Errmsg:     "Process terminated",
525		Lang:       "en-GB-oed",
526	}
527	if _, err := ch.SendRequest("exit-signal", false, Marshal(&sig)); err != nil {
528		t.Errorf("unable to send signal: %v", err)
529	}
530}
531
532func discardHandler(ch Channel, t *testing.T) {
533	defer ch.Close()
534	io.Copy(ioutil.Discard, ch)
535}
536
537func echoHandler(ch Channel, in <-chan *Request, t *testing.T) {
538	defer ch.Close()
539	if n, err := copyNRandomly("echohandler", ch, ch, windowTestBytes); err != nil {
540		t.Errorf("short write, wrote %d, expected %d: %v ", n, windowTestBytes, err)
541	}
542}
543
544// copyNRandomly copies n bytes from src to dst. It uses a variable, and random,
545// buffer size to exercise more code paths.
546func copyNRandomly(title string, dst io.Writer, src io.Reader, n int) (int, error) {
547	var (
548		buf       = make([]byte, 32*1024)
549		written   int
550		remaining = n
551	)
552	for remaining > 0 {
553		l := rand.Intn(1 << 15)
554		if remaining < l {
555			l = remaining
556		}
557		nr, er := src.Read(buf[:l])
558		nw, ew := dst.Write(buf[:nr])
559		remaining -= nw
560		written += nw
561		if ew != nil {
562			return written, ew
563		}
564		if nr != nw {
565			return written, io.ErrShortWrite
566		}
567		if er != nil && er != io.EOF {
568			return written, er
569		}
570	}
571	return written, nil
572}
573
574func channelKeepaliveSender(ch Channel, in <-chan *Request, t *testing.T) {
575	defer ch.Close()
576	shell := newServerShell(ch, in, "> ")
577	readLine(shell, t)
578	if _, err := ch.SendRequest("keepalive@openssh.com", true, nil); err != nil {
579		t.Errorf("unable to send channel keepalive request: %v", err)
580	}
581	sendStatus(0, ch, t)
582}
583
584func TestClientWriteEOF(t *testing.T) {
585	conn := dial(simpleEchoHandler, t)
586	defer conn.Close()
587
588	session, err := conn.NewSession()
589	if err != nil {
590		t.Fatal(err)
591	}
592	defer session.Close()
593	stdin, err := session.StdinPipe()
594	if err != nil {
595		t.Fatalf("StdinPipe failed: %v", err)
596	}
597	stdout, err := session.StdoutPipe()
598	if err != nil {
599		t.Fatalf("StdoutPipe failed: %v", err)
600	}
601
602	data := []byte(`0000`)
603	_, err = stdin.Write(data)
604	if err != nil {
605		t.Fatalf("Write failed: %v", err)
606	}
607	stdin.Close()
608
609	res, err := ioutil.ReadAll(stdout)
610	if err != nil {
611		t.Fatalf("Read failed: %v", err)
612	}
613
614	if !bytes.Equal(data, res) {
615		t.Fatalf("Read differed from write, wrote: %v, read: %v", data, res)
616	}
617}
618
619func simpleEchoHandler(ch Channel, in <-chan *Request, t *testing.T) {
620	defer ch.Close()
621	data, err := ioutil.ReadAll(ch)
622	if err != nil {
623		t.Errorf("handler read error: %v", err)
624	}
625	_, err = ch.Write(data)
626	if err != nil {
627		t.Errorf("handler write error: %v", err)
628	}
629}
630
631func TestSessionID(t *testing.T) {
632	c1, c2, err := netPipe()
633	if err != nil {
634		t.Fatalf("netPipe: %v", err)
635	}
636	defer c1.Close()
637	defer c2.Close()
638
639	serverID := make(chan []byte, 1)
640	clientID := make(chan []byte, 1)
641
642	serverConf := &ServerConfig{
643		NoClientAuth: true,
644	}
645	serverConf.AddHostKey(testSigners["ecdsa"])
646	clientConf := &ClientConfig{
647		HostKeyCallback: InsecureIgnoreHostKey(),
648		User:            "user",
649	}
650
651	go func() {
652		conn, chans, reqs, err := NewServerConn(c1, serverConf)
653		if err != nil {
654			t.Fatalf("server handshake: %v", err)
655		}
656		serverID <- conn.SessionID()
657		go DiscardRequests(reqs)
658		for ch := range chans {
659			ch.Reject(Prohibited, "")
660		}
661	}()
662
663	go func() {
664		conn, chans, reqs, err := NewClientConn(c2, "", clientConf)
665		if err != nil {
666			t.Fatalf("client handshake: %v", err)
667		}
668		clientID <- conn.SessionID()
669		go DiscardRequests(reqs)
670		for ch := range chans {
671			ch.Reject(Prohibited, "")
672		}
673	}()
674
675	s := <-serverID
676	c := <-clientID
677	if bytes.Compare(s, c) != 0 {
678		t.Errorf("server session ID (%x) != client session ID (%x)", s, c)
679	} else if len(s) == 0 {
680		t.Errorf("client and server SessionID were empty.")
681	}
682}
683
684type noReadConn struct {
685	readSeen bool
686	net.Conn
687}
688
689func (c *noReadConn) Close() error {
690	return nil
691}
692
693func (c *noReadConn) Read(b []byte) (int, error) {
694	c.readSeen = true
695	return 0, errors.New("noReadConn error")
696}
697
698func TestInvalidServerConfiguration(t *testing.T) {
699	c1, c2, err := netPipe()
700	if err != nil {
701		t.Fatalf("netPipe: %v", err)
702	}
703	defer c1.Close()
704	defer c2.Close()
705
706	serveConn := noReadConn{Conn: c1}
707	serverConf := &ServerConfig{}
708
709	NewServerConn(&serveConn, serverConf)
710	if serveConn.readSeen {
711		t.Fatalf("NewServerConn attempted to Read() from Conn while configuration is missing host key")
712	}
713
714	serverConf.AddHostKey(testSigners["ecdsa"])
715
716	NewServerConn(&serveConn, serverConf)
717	if serveConn.readSeen {
718		t.Fatalf("NewServerConn attempted to Read() from Conn while configuration is missing authentication method")
719	}
720}
721
722func TestHostKeyAlgorithms(t *testing.T) {
723	serverConf := &ServerConfig{
724		NoClientAuth: true,
725	}
726	serverConf.AddHostKey(testSigners["rsa"])
727	serverConf.AddHostKey(testSigners["ecdsa"])
728
729	connect := func(clientConf *ClientConfig, want string) {
730		var alg string
731		clientConf.HostKeyCallback = func(h string, a net.Addr, key PublicKey) error {
732			alg = key.Type()
733			return nil
734		}
735		c1, c2, err := netPipe()
736		if err != nil {
737			t.Fatalf("netPipe: %v", err)
738		}
739		defer c1.Close()
740		defer c2.Close()
741
742		go NewServerConn(c1, serverConf)
743		_, _, _, err = NewClientConn(c2, "", clientConf)
744		if err != nil {
745			t.Fatalf("NewClientConn: %v", err)
746		}
747		if alg != want {
748			t.Errorf("selected key algorithm %s, want %s", alg, want)
749		}
750	}
751
752	// By default, we get the preferred algorithm, which is ECDSA 256.
753
754	clientConf := &ClientConfig{
755		HostKeyCallback: InsecureIgnoreHostKey(),
756	}
757	connect(clientConf, KeyAlgoECDSA256)
758
759	// Client asks for RSA explicitly.
760	clientConf.HostKeyAlgorithms = []string{KeyAlgoRSA}
761	connect(clientConf, KeyAlgoRSA)
762
763	c1, c2, err := netPipe()
764	if err != nil {
765		t.Fatalf("netPipe: %v", err)
766	}
767	defer c1.Close()
768	defer c2.Close()
769
770	go NewServerConn(c1, serverConf)
771	clientConf.HostKeyAlgorithms = []string{"nonexistent-hostkey-algo"}
772	_, _, _, err = NewClientConn(c2, "", clientConf)
773	if err == nil {
774		t.Fatal("succeeded connecting with unknown hostkey algorithm")
775	}
776}
777