mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 11:28:24 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			262 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			262 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
		
			Executable File
		
	
	
	
	
| // 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.
 | |
| 
 | |
| // +build darwin dragonfly freebsd linux netbsd openbsd plan9
 | |
| 
 | |
| package test
 | |
| 
 | |
| // functional test harness for unix.
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"log"
 | |
| 	"net"
 | |
| 	"os"
 | |
| 	"os/exec"
 | |
| 	"os/user"
 | |
| 	"path/filepath"
 | |
| 	"testing"
 | |
| 	"text/template"
 | |
| 
 | |
| 	"golang.org/x/crypto/ssh"
 | |
| 	"golang.org/x/crypto/ssh/testdata"
 | |
| )
 | |
| 
 | |
| const sshd_config = `
 | |
| Protocol 2
 | |
| HostKey {{.Dir}}/id_rsa
 | |
| HostKey {{.Dir}}/id_dsa
 | |
| HostKey {{.Dir}}/id_ecdsa
 | |
| Pidfile {{.Dir}}/sshd.pid
 | |
| #UsePrivilegeSeparation no
 | |
| KeyRegenerationInterval 3600
 | |
| ServerKeyBits 768
 | |
| SyslogFacility AUTH
 | |
| LogLevel DEBUG2
 | |
| LoginGraceTime 120
 | |
| PermitRootLogin no
 | |
| StrictModes no
 | |
| RSAAuthentication yes
 | |
| PubkeyAuthentication yes
 | |
| AuthorizedKeysFile	{{.Dir}}/id_user.pub
 | |
| TrustedUserCAKeys {{.Dir}}/id_ecdsa.pub
 | |
| IgnoreRhosts yes
 | |
| RhostsRSAAuthentication no
 | |
| HostbasedAuthentication no
 | |
| `
 | |
| 
 | |
| var configTmpl = template.Must(template.New("").Parse(sshd_config))
 | |
| 
 | |
| type server struct {
 | |
| 	t          *testing.T
 | |
| 	cleanup    func() // executed during Shutdown
 | |
| 	configfile string
 | |
| 	cmd        *exec.Cmd
 | |
| 	output     bytes.Buffer // holds stderr from sshd process
 | |
| 
 | |
| 	// Client half of the network connection.
 | |
| 	clientConn net.Conn
 | |
| }
 | |
| 
 | |
| func username() string {
 | |
| 	var username string
 | |
| 	if user, err := user.Current(); err == nil {
 | |
| 		username = user.Username
 | |
| 	} else {
 | |
| 		// user.Current() currently requires cgo. If an error is
 | |
| 		// returned attempt to get the username from the environment.
 | |
| 		log.Printf("user.Current: %v; falling back on $USER", err)
 | |
| 		username = os.Getenv("USER")
 | |
| 	}
 | |
| 	if username == "" {
 | |
| 		panic("Unable to get username")
 | |
| 	}
 | |
| 	return username
 | |
| }
 | |
| 
 | |
| type storedHostKey struct {
 | |
| 	// keys map from an algorithm string to binary key data.
 | |
| 	keys map[string][]byte
 | |
| 
 | |
| 	// checkCount counts the Check calls. Used for testing
 | |
| 	// rekeying.
 | |
| 	checkCount int
 | |
| }
 | |
| 
 | |
| func (k *storedHostKey) Add(key ssh.PublicKey) {
 | |
| 	if k.keys == nil {
 | |
| 		k.keys = map[string][]byte{}
 | |
| 	}
 | |
| 	k.keys[key.Type()] = key.Marshal()
 | |
| }
 | |
| 
 | |
| func (k *storedHostKey) Check(addr string, remote net.Addr, key ssh.PublicKey) error {
 | |
| 	k.checkCount++
 | |
| 	algo := key.Type()
 | |
| 
 | |
| 	if k.keys == nil || bytes.Compare(key.Marshal(), k.keys[algo]) != 0 {
 | |
| 		return fmt.Errorf("host key mismatch. Got %q, want %q", key, k.keys[algo])
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func hostKeyDB() *storedHostKey {
 | |
| 	keyChecker := &storedHostKey{}
 | |
| 	keyChecker.Add(testPublicKeys["ecdsa"])
 | |
| 	keyChecker.Add(testPublicKeys["rsa"])
 | |
| 	keyChecker.Add(testPublicKeys["dsa"])
 | |
| 	return keyChecker
 | |
| }
 | |
| 
 | |
| func clientConfig() *ssh.ClientConfig {
 | |
| 	config := &ssh.ClientConfig{
 | |
| 		User: username(),
 | |
| 		Auth: []ssh.AuthMethod{
 | |
| 			ssh.PublicKeys(testSigners["user"]),
 | |
| 		},
 | |
| 		HostKeyCallback: hostKeyDB().Check,
 | |
| 	}
 | |
| 	return config
 | |
| }
 | |
| 
 | |
| // 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 := ioutil.TempDir("", "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) {
 | |
| 	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)
 | |
| 	}
 | |
| 
 | |
| 	s.cmd = exec.Command(sshd, "-f", s.configfile, "-i", "-e")
 | |
| 	f, err := c2.File()
 | |
| 	if err != nil {
 | |
| 		s.t.Fatalf("UnixConn.File: %v", err)
 | |
| 	}
 | |
| 	defer f.Close()
 | |
| 	s.cmd.Stdin = f
 | |
| 	s.cmd.Stdout = f
 | |
| 	s.cmd.Stderr = &s.output
 | |
| 	if err := s.cmd.Start(); err != nil {
 | |
| 		s.t.Fail()
 | |
| 		s.Shutdown()
 | |
| 		s.t.Fatalf("s.cmd.Start: %v", err)
 | |
| 	}
 | |
| 	s.clientConn = c1
 | |
| 	conn, chans, reqs, err := ssh.NewClientConn(c1, "", 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.Fail()
 | |
| 		s.Shutdown()
 | |
| 		s.t.Fatalf("ssh.Client: %v", err)
 | |
| 	}
 | |
| 	return conn
 | |
| }
 | |
| 
 | |
| func (s *server) Shutdown() {
 | |
| 	if s.cmd != nil && s.cmd.Process != nil {
 | |
| 		// 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.
 | |
| 		s.cmd.Process.Signal(os.Interrupt)
 | |
| 		s.cmd.Wait()
 | |
| 	}
 | |
| 	if s.t.Failed() {
 | |
| 		// log any output from sshd process
 | |
| 		s.t.Logf("sshd: %s", s.output.String())
 | |
| 	}
 | |
| 	s.cleanup()
 | |
| }
 | |
| 
 | |
| func writeFile(path string, contents []byte) {
 | |
| 	f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
 | |
| 	if err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 	defer f.Close()
 | |
| 	if _, err := f.Write(contents); err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // newServer returns a new mock ssh server.
 | |
| func newServer(t *testing.T) *server {
 | |
| 	if testing.Short() {
 | |
| 		t.Skip("skipping test due to -short")
 | |
| 	}
 | |
| 	dir, err := ioutil.TempDir("", "sshtest")
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	f, err := os.Create(filepath.Join(dir, "sshd_config"))
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	err = configTmpl.Execute(f, map[string]string{
 | |
| 		"Dir": dir,
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	f.Close()
 | |
| 
 | |
| 	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]))
 | |
| 	}
 | |
| 
 | |
| 	return &server{
 | |
| 		t:          t,
 | |
| 		configfile: f.Name(),
 | |
| 		cleanup: func() {
 | |
| 			if err := os.RemoveAll(dir); err != nil {
 | |
| 				t.Error(err)
 | |
| 			}
 | |
| 		},
 | |
| 	}
 | |
| }
 |