LEFT | RIGHT |
| 1 // Copyright 2012 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 |
| 5 // +build darwin freebsd linux netbsd openbsd |
| 6 |
| 7 package test |
| 8 |
| 9 // functional test harness for unix. |
| 10 |
| 11 import ( |
| 12 "bytes" |
| 13 "fmt" |
| 14 "io" |
| 15 "io/ioutil" |
| 16 "log" |
| 17 "net" |
| 18 "os" |
| 19 "os/exec" |
| 20 "os/user" |
| 21 "path/filepath" |
| 22 "testing" |
| 23 "text/template" |
| 24 |
| 25 "code.google.com/p/go.crypto/ssh" |
| 26 ) |
| 27 |
| 28 const sshd_config = ` |
| 29 Protocol 2 |
| 30 HostKey {{.Dir}}/ssh_host_rsa_key |
| 31 HostKey {{.Dir}}/ssh_host_dsa_key |
| 32 HostKey {{.Dir}}/ssh_host_ecdsa_key |
| 33 Pidfile {{.Dir}}/sshd.pid |
| 34 #UsePrivilegeSeparation no |
| 35 KeyRegenerationInterval 3600 |
| 36 ServerKeyBits 768 |
| 37 SyslogFacility AUTH |
| 38 LogLevel DEBUG2 |
| 39 LoginGraceTime 120 |
| 40 PermitRootLogin no |
| 41 StrictModes no |
| 42 RSAAuthentication yes |
| 43 PubkeyAuthentication yes |
| 44 AuthorizedKeysFile {{.Dir}}/authorized_keys |
| 45 IgnoreRhosts yes |
| 46 RhostsRSAAuthentication no |
| 47 HostbasedAuthentication no |
| 48 ` |
| 49 |
| 50 var ( |
| 51 configTmpl template.Template |
| 52 privateKey ssh.Signer |
| 53 hostKeyRSA ssh.Signer |
| 54 hostKeyECDSA ssh.Signer |
| 55 hostKeyDSA ssh.Signer |
| 56 ) |
| 57 |
| 58 func init() { |
| 59 template.Must(configTmpl.Parse(sshd_config)) |
| 60 |
| 61 for n, k := range map[string]*ssh.Signer{ |
| 62 "ssh_host_ecdsa_key": &hostKeyECDSA, |
| 63 "ssh_host_rsa_key": &hostKeyRSA, |
| 64 "ssh_host_dsa_key": &hostKeyDSA, |
| 65 } { |
| 66 var err error |
| 67 *k, err = ssh.ParsePrivateKey([]byte(keys[n])) |
| 68 if err != nil { |
| 69 panic(fmt.Sprintf("ParsePrivateKey(%q): %v", n, err)) |
| 70 } |
| 71 } |
| 72 |
| 73 var err error |
| 74 privateKey, err = ssh.ParsePrivateKey([]byte(testClientPrivateKey)) |
| 75 if err != nil { |
| 76 panic(fmt.Sprintf("ParsePrivateKey: %v", err)) |
| 77 } |
| 78 } |
| 79 |
| 80 type server struct { |
| 81 t *testing.T |
| 82 cleanup func() // executed during Shutdown |
| 83 configfile string |
| 84 cmd *exec.Cmd |
| 85 output bytes.Buffer // holds stderr from sshd process |
| 86 |
| 87 // Client half of the network connection. |
| 88 clientConn net.Conn |
| 89 } |
| 90 |
| 91 func username() string { |
| 92 var username string |
| 93 if user, err := user.Current(); err == nil { |
| 94 username = user.Username |
| 95 } else { |
| 96 // user.Current() currently requires cgo. If an error is |
| 97 // returned attempt to get the username from the environment. |
| 98 log.Printf("user.Current: %v; falling back on $USER", err) |
| 99 username = os.Getenv("USER") |
| 100 } |
| 101 if username == "" { |
| 102 panic("Unable to get username") |
| 103 } |
| 104 return username |
| 105 } |
1 | 106 |
2 type storedHostKey struct { | 107 type storedHostKey struct { |
3 // keys map from an algorithm string to binary key data. | 108 // keys map from an algorithm string to binary key data. |
4 keys map[string][]byte | 109 keys map[string][]byte |
| 110 |
| 111 // checkCount counts the Check calls. Used for testing |
| 112 // rekeying. |
| 113 checkCount int |
5 } | 114 } |
6 | 115 |
7 func (k *storedHostKey) Add(key ssh.PublicKey) { | 116 func (k *storedHostKey) Add(key ssh.PublicKey) { |
| 117 if k.keys == nil { |
| 118 k.keys = map[string][]byte{} |
| 119 } |
| 120 k.keys[key.PublicKeyAlgo()] = ssh.MarshalPublicKey(key) |
| 121 } |
| 122 |
| 123 func (k *storedHostKey) Check(addr string, remote net.Addr, algo string, key []b
yte) error { |
| 124 k.checkCount++ |
| 125 if k.keys == nil || bytes.Compare(key, k.keys[algo]) != 0 { |
| 126 return fmt.Errorf("host key mismatch. Got %q, want %q", key, k.k
eys[algo]) |
| 127 } |
| 128 return nil |
| 129 } |
| 130 |
| 131 func clientConfig() *ssh.ClientConfig { |
| 132 keyChecker := storedHostKey{} |
| 133 keyChecker.Add(hostKeyECDSA.PublicKey()) |
| 134 keyChecker.Add(hostKeyRSA.PublicKey()) |
| 135 keyChecker.Add(hostKeyDSA.PublicKey()) |
| 136 |
| 137 kc := new(keychain) |
| 138 kc.keys = append(kc.keys, privateKey) |
| 139 config := &ssh.ClientConfig{ |
| 140 User: username(), |
| 141 Auth: []ssh.ClientAuth{ |
| 142 ssh.ClientAuthKeyring(kc), |
| 143 }, |
| 144 HostKeyChecker: &keyChecker, |
| 145 } |
| 146 return config |
| 147 } |
| 148 |
| 149 // unixConnection creates two halves of a connected net.UnixConn. It |
| 150 // is used for connecting the Go SSH client with sshd without opening |
| 151 // ports. |
| 152 func unixConnection() (*net.UnixConn, *net.UnixConn, error) { |
| 153 dir, err := ioutil.TempDir("", "unixConnection") |
| 154 if err != nil { |
| 155 return nil, nil, err |
| 156 } |
| 157 defer os.Remove(dir) |
| 158 |
| 159 addr := filepath.Join(dir, "ssh") |
| 160 listener, err := net.Listen("unix", addr) |
| 161 if err != nil { |
| 162 return nil, nil, err |
| 163 } |
| 164 defer listener.Close() |
| 165 c1, err := net.Dial("unix", addr) |
| 166 if err != nil { |
| 167 return nil, nil, err |
| 168 } |
| 169 |
| 170 c2, err := listener.Accept() |
| 171 if err != nil { |
| 172 c1.Close() |
| 173 return nil, nil, err |
| 174 } |
| 175 |
| 176 return c1.(*net.UnixConn), c2.(*net.UnixConn), nil |
| 177 } |
| 178 |
| 179 func (s *server) TryDial(config *ssh.ClientConfig) (*ssh.ClientConn, error) { |
| 180 sshd, err := exec.LookPath("sshd") |
| 181 if err != nil { |
| 182 s.t.Skipf("skipping test: %v", err) |
| 183 } |
| 184 |
| 185 c1, c2, err := unixConnection() |
| 186 if err != nil { |
| 187 s.t.Fatalf("unixConnection: %v", err) |
| 188 } |
| 189 |
| 190 s.cmd = exec.Command(sshd, "-f", s.configfile, "-i", "-e") |
| 191 f, err := c2.File() |
| 192 if err != nil { |
| 193 s.t.Fatalf("UnixConn.File: %v", err) |
| 194 } |
| 195 defer f.Close() |
| 196 s.cmd.Stdin = f |
| 197 s.cmd.Stdout = f |
| 198 s.cmd.Stderr = &s.output |
| 199 if err := s.cmd.Start(); err != nil { |
| 200 s.t.Fail() |
| 201 s.Shutdown() |
| 202 s.t.Fatalf("s.cmd.Start: %v", err) |
| 203 } |
| 204 s.clientConn = c1 |
| 205 return ssh.Client(c1, config) |
| 206 } |
| 207 |
| 208 func (s *server) Dial(config *ssh.ClientConfig) *ssh.ClientConn { |
| 209 conn, err := s.TryDial(config) |
| 210 if err != nil { |
| 211 s.t.Fail() |
| 212 s.Shutdown() |
| 213 s.t.Fatalf("ssh.Client: %v", err) |
| 214 } |
| 215 return conn |
| 216 } |
| 217 |
| 218 func (s *server) Shutdown() { |
| 219 if s.cmd != nil && s.cmd.Process != nil { |
| 220 // Don't check for errors; if it fails it's most |
| 221 // likely "os: process already finished", and we don't |
| 222 // care about that. Use os.Interrupt, so child |
| 223 // processes are killed too. |
| 224 s.cmd.Process.Signal(os.Interrupt) |
| 225 s.cmd.Wait() |
| 226 } |
| 227 if s.t.Failed() { |
| 228 // log any output from sshd process |
| 229 s.t.Logf("sshd: %s", s.output.String()) |
| 230 } |
| 231 s.cleanup() |
| 232 } |
| 233 |
| 234 // newServer returns a new mock ssh server. |
| 235 func newServer(t *testing.T) *server { |
| 236 dir, err := ioutil.TempDir("", "sshtest") |
| 237 if err != nil { |
| 238 t.Fatal(err) |
| 239 } |
| 240 f, err := os.Create(filepath.Join(dir, "sshd_config")) |
| 241 if err != nil { |
| 242 t.Fatal(err) |
| 243 } |
| 244 err = configTmpl.Execute(f, map[string]string{ |
| 245 "Dir": dir, |
| 246 }) |
| 247 if err != nil { |
| 248 t.Fatal(err) |
| 249 } |
| 250 f.Close() |
| 251 |
| 252 for k, v := range keys { |
| 253 f, err := os.OpenFile(filepath.Join(dir, k), os.O_WRONLY|os.O_TR
UNC|os.O_CREATE, 0600) |
| 254 if err != nil { |
| 255 t.Fatal(err) |
| 256 } |
| 257 if _, err := f.Write([]byte(v)); err != nil { |
| 258 t.Fatal(err) |
| 259 } |
| 260 f.Close() |
| 261 } |
| 262 |
| 263 return &server{ |
| 264 t: t, |
| 265 configfile: f.Name(), |
| 266 cleanup: func() { |
| 267 if err := os.RemoveAll(dir); err != nil { |
| 268 t.Error(err) |
| 269 } |
| 270 }, |
| 271 } |
| 272 } |
| 273 |
| 274 // keychain implements the ClientKeyring interface. |
| 275 type keychain struct { |
| 276 keys []ssh.Signer |
| 277 } |
| 278 |
| 279 func (k *keychain) Key(i int) (ssh.PublicKey, error) { |
| 280 if i < 0 || i >= len(k.keys) { |
| 281 return nil, nil |
| 282 } |
| 283 return k.keys[i].PublicKey(), nil |
| 284 } |
| 285 |
| 286 func (k *keychain) Sign(i int, rand io.Reader, data []byte) (sig []byte, err err
or) { |
| 287 return k.keys[i].Sign(rand, data) |
| 288 } |
| 289 |
| 290 func (k *keychain) loadPEM(file string) error { |
| 291 buf, err := ioutil.ReadFile(file) |
| 292 if err != nil { |
| 293 return err |
| 294 } |
| 295 key, err := ssh.ParsePrivateKey(buf) |
| 296 if err != nil { |
| 297 return err |
| 298 } |
| 299 k.keys = append(k.keys, key) |
| 300 return nil |
| 301 } |
LEFT | RIGHT |