// Copyright 2012 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. //go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || plan9 || solaris package test // functional test harness for unix. import ( "bytes" "crypto/rand" "encoding/base64" "fmt" "net" "os" "os/exec" "os/user" "path/filepath" "testing" "golang.org/x/crypto/internal/testenv" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/testdata" ) // unixConnection creates two halves of a connected net.UnixConn. It // is used for connecting the Go SSH client with sshd without opening // ports. func unixConnection() (*net.UnixConn, *net.UnixConn, error) { dir, err := os.MkdirTemp("", "unixConnection") if err != nil { return nil, nil, err } defer os.Remove(dir) addr := filepath.Join(dir, "ssh") listener, err := net.Listen("unix", addr) if err != nil { return nil, nil, err } defer listener.Close() c1, err := net.Dial("unix", addr) if err != nil { return nil, nil, err } c2, err := listener.Accept() if err != nil { c1.Close() return nil, nil, err } return c1.(*net.UnixConn), c2.(*net.UnixConn), nil } func (s *server) TryDial(config *ssh.ClientConfig) (*ssh.Client, error) { return s.TryDialWithAddr(config, "") } // addr is the user specified host:port. While we don't actually dial it, // we need to know this for host key matching func (s *server) TryDialWithAddr(config *ssh.ClientConfig, addr string) (client *ssh.Client, err error) { sshd, err := exec.LookPath("sshd") if err != nil { s.t.Skipf("skipping test: %v", err) } c1, c2, err := unixConnection() if err != nil { s.t.Fatalf("unixConnection: %v", err) } defer func() { // Close c2 after we've started the sshd command so that it won't prevent c1 // from returning EOF when the sshd command exits. c2.Close() // Leave c1 open if we're returning a client that wraps it. // (The client is responsible for closing it.) // Otherwise, close it to free up the socket. if client == nil { c1.Close() } }() f, err := c2.File() if err != nil { s.t.Fatalf("UnixConn.File: %v", err) } defer f.Close() cmd := testenv.Command(s.t, sshd, "-f", s.configfile, "-i", "-e") cmd.Stdin = f cmd.Stdout = f cmd.Stderr = new(bytes.Buffer) if s.sshdTestPwSo != "" { if s.testUser == "" { s.t.Fatal("user missing from sshd_test_pw.so config") } if s.testPasswd == "" { s.t.Fatal("password missing from sshd_test_pw.so config") } cmd.Env = append(os.Environ(), fmt.Sprintf("LD_PRELOAD=%s", s.sshdTestPwSo), fmt.Sprintf("TEST_USER=%s", s.testUser), fmt.Sprintf("TEST_PASSWD=%s", s.testPasswd)) } if err := cmd.Start(); err != nil { s.t.Fatalf("s.cmd.Start: %v", err) } s.lastDialConn = c1 s.t.Cleanup(func() { // Don't check for errors; if it fails it's most // likely "os: process already finished", and we don't // care about that. Use os.Interrupt, so child // processes are killed too. cmd.Process.Signal(os.Interrupt) cmd.Wait() if s.t.Failed() || testing.Verbose() { // log any output from sshd process s.t.Logf("sshd:\n%s", cmd.Stderr) } }) conn, chans, reqs, err := ssh.NewClientConn(c1, addr, config) if err != nil { return nil, err } return ssh.NewClient(conn, chans, reqs), nil } func (s *server) Dial(config *ssh.ClientConfig) *ssh.Client { conn, err := s.TryDial(config) if err != nil { s.t.Fatalf("ssh.Client: %v", err) } return conn } // generate random password func randomPassword() (string, error) { b := make([]byte, 12) _, err := rand.Read(b) if err != nil { return "", err } return base64.RawURLEncoding.EncodeToString(b), nil } // setTestPassword is used for setting user and password data for sshd_test_pw.so // This function also checks that ./sshd_test_pw.so exists and if not calls s.t.Skip() func (s *server) setTestPassword(user, passwd string) error { wd, _ := os.Getwd() wrapper := filepath.Join(wd, "sshd_test_pw.so") if _, err := os.Stat(wrapper); err != nil { s.t.Skip(fmt.Errorf("sshd_test_pw.so is not available")) return err } s.sshdTestPwSo = wrapper s.testUser = user s.testPasswd = passwd return nil } // newServer returns a new mock ssh server. func newServer(t *testing.T) *server { return newServerForConfig(t, "default", map[string]string{}) } // newServerForConfig returns a new mock ssh server. func newServerForConfig(t *testing.T, config string, configVars map[string]string) *server { if testing.Short() { t.Skip("skipping test due to -short") } u, err := user.Current() if err != nil { t.Fatalf("user.Current: %v", err) } uname := u.Name if uname == "" { // Check the value of u.Username as u.Name // can be "" on some OSes like AIX. uname = u.Username } if uname == "root" { t.Skip("skipping test because current user is root") } dir, err := os.MkdirTemp("", "sshtest") if err != nil { t.Fatal(err) } f, err := os.Create(filepath.Join(dir, "sshd_config")) if err != nil { t.Fatal(err) } if _, ok := configTmpl[config]; ok == false { t.Fatal(fmt.Errorf("Invalid server config '%s'", config)) } configVars["Dir"] = dir err = configTmpl[config].Execute(f, configVars) if err != nil { t.Fatal(err) } f.Close() writeFile(filepath.Join(dir, "banner"), []byte("Server Banner")) for k, v := range testdata.PEMBytes { filename := "id_" + k writeFile(filepath.Join(dir, filename), v) writeFile(filepath.Join(dir, filename+".pub"), ssh.MarshalAuthorizedKey(testPublicKeys[k])) } for k, v := range testdata.SSHCertificates { filename := "id_" + k + "-cert.pub" writeFile(filepath.Join(dir, filename), v) } var authkeys bytes.Buffer for k := range testdata.PEMBytes { authkeys.Write(ssh.MarshalAuthorizedKey(testPublicKeys[k])) } writeFile(filepath.Join(dir, "authorized_keys"), authkeys.Bytes()) t.Cleanup(func() { if err := os.RemoveAll(dir); err != nil { t.Error(err) } }) return &server{ t: t, configfile: f.Name(), } } func newTempSocket(t *testing.T) (string, func()) { dir, err := os.MkdirTemp("", "socket") if err != nil { t.Fatal(err) } deferFunc := func() { os.RemoveAll(dir) } addr := filepath.Join(dir, "sock") return addr, deferFunc }