Commit 792a55f5 authored by Dave Cheney's avatar Dave Cheney Committed by Adam Langley

exp/ssh: add experimental ssh client

Requires CL 5285044

client.go:
* add Dial, ClientConn, ClientChan, ClientConfig and Cmd.

doc.go:
* add Client documentation.

server.go:
* adjust for readVersion change.

transport.go:
* return an os.Error not a bool from readVersion.

R=rsc, agl, n13m3y3r
CC=golang-dev
https://golang.org/cl/5162047
parent e8a426ae
...@@ -7,6 +7,7 @@ include ../../../Make.inc ...@@ -7,6 +7,7 @@ include ../../../Make.inc
TARG=exp/ssh TARG=exp/ssh
GOFILES=\ GOFILES=\
channel.go\ channel.go\
client.go\
common.go\ common.go\
messages.go\ messages.go\
transport.go\ transport.go\
......
This diff is collapsed.
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
/* /*
Package ssh implements an SSH server. Package ssh implements an SSH client and server.
SSH is a transport security protocol, an authentication protocol and a SSH is a transport security protocol, an authentication protocol and a
family of application protocols. The most typical application level family of application protocols. The most typical application level
...@@ -75,5 +75,27 @@ present a simple terminal interface. ...@@ -75,5 +75,27 @@ present a simple terminal interface.
} }
return return
}() }()
An SSH client is represented with a ClientConn. Currently only the "password"
authentication method is supported.
config := &ClientConfig{
User: "username",
Password: "123456",
}
client, err := Dial("yourserver.com:22", config)
Each ClientConn can support multiple channels, represented by ClientChan. Each
channel should be of a type specified in rfc4250, 4.9.1.
ch, err := client.OpenChan("session")
Once the ClientChan is opened, you can execute a single command on the remote side
using the Exec method.
cmd, err := ch.Exec("/usr/bin/whoami")
reader := bufio.NewReader(cmd.Stdin)
line, _, _ := reader.ReadLine()
fmt.Println(line)
*/ */
package ssh package ssh
...@@ -267,9 +267,9 @@ func (s *ServerConnection) Handshake(conn net.Conn) os.Error { ...@@ -267,9 +267,9 @@ func (s *ServerConnection) Handshake(conn net.Conn) os.Error {
} }
magics.serverVersion = serverVersion[:len(serverVersion)-2] magics.serverVersion = serverVersion[:len(serverVersion)-2]
version, ok := readVersion(s.transport) version, err := readVersion(s.transport)
if !ok { if err != nil {
return os.NewError("failed to read version string from client") return err
} }
magics.clientVersion = version magics.clientVersion = version
......
...@@ -332,16 +332,15 @@ func (t truncatingMAC) Size() int { ...@@ -332,16 +332,15 @@ func (t truncatingMAC) Size() int {
const maxVersionStringBytes = 1024 const maxVersionStringBytes = 1024
// Read version string as specified by RFC 4253, section 4.2. // Read version string as specified by RFC 4253, section 4.2.
func readVersion(r io.Reader) (versionString []byte, ok bool) { func readVersion(r io.Reader) ([]byte, os.Error) {
versionString = make([]byte, 0, 64) versionString := make([]byte, 0, 64)
seenCR := false var ok, seenCR bool
var buf [1]byte var buf [1]byte
forEachByte: forEachByte:
for len(versionString) < maxVersionStringBytes { for len(versionString) < maxVersionStringBytes {
_, err := io.ReadFull(r, buf[:]) _, err := io.ReadFull(r, buf[:])
if err != nil { if err != nil {
return return nil, err
} }
b := buf[0] b := buf[0]
...@@ -360,10 +359,10 @@ forEachByte: ...@@ -360,10 +359,10 @@ forEachByte:
versionString = append(versionString, b) versionString = append(versionString, b)
} }
if ok { if !ok {
// We need to remove the CR from versionString return nil, os.NewError("failed to read version string")
versionString = versionString[:len(versionString)-1]
} }
return // We need to remove the CR from versionString
return versionString[:len(versionString)-1], nil
} }
...@@ -12,9 +12,9 @@ import ( ...@@ -12,9 +12,9 @@ import (
func TestReadVersion(t *testing.T) { func TestReadVersion(t *testing.T) {
buf := []byte(serverVersion) buf := []byte(serverVersion)
result, ok := readVersion(bufio.NewReader(bytes.NewBuffer(buf))) result, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf)))
if !ok { if err != nil {
t.Error("readVersion didn't read version correctly") t.Errorf("readVersion didn't read version correctly: %s", err)
} }
if !bytes.Equal(buf[:len(buf)-2], result) { if !bytes.Equal(buf[:len(buf)-2], result) {
t.Error("version read did not match expected") t.Error("version read did not match expected")
...@@ -23,7 +23,7 @@ func TestReadVersion(t *testing.T) { ...@@ -23,7 +23,7 @@ func TestReadVersion(t *testing.T) {
func TestReadVersionTooLong(t *testing.T) { func TestReadVersionTooLong(t *testing.T) {
buf := make([]byte, maxVersionStringBytes+1) buf := make([]byte, maxVersionStringBytes+1)
if _, ok := readVersion(bufio.NewReader(bytes.NewBuffer(buf))); ok { if _, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf))); err == nil {
t.Errorf("readVersion consumed %d bytes without error", len(buf)) t.Errorf("readVersion consumed %d bytes without error", len(buf))
} }
} }
...@@ -31,7 +31,7 @@ func TestReadVersionTooLong(t *testing.T) { ...@@ -31,7 +31,7 @@ func TestReadVersionTooLong(t *testing.T) {
func TestReadVersionWithoutCRLF(t *testing.T) { func TestReadVersionWithoutCRLF(t *testing.T) {
buf := []byte(serverVersion) buf := []byte(serverVersion)
buf = buf[:len(buf)-1] buf = buf[:len(buf)-1]
if _, ok := readVersion(bufio.NewReader(bytes.NewBuffer(buf))); ok { if _, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf))); err == nil {
t.Error("readVersion did not notice \\n was missing") t.Error("readVersion did not notice \\n was missing")
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment