aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/exp
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/exp')
-rw-r--r--libgo/go/exp/inotify/inotify_linux.go288
-rw-r--r--libgo/go/exp/inotify/inotify_linux_test.go96
-rw-r--r--libgo/go/exp/ssh/channel.go2
-rw-r--r--libgo/go/exp/ssh/client.go490
-rw-r--r--libgo/go/exp/ssh/doc.go48
-rw-r--r--libgo/go/exp/ssh/messages.go2
-rw-r--r--libgo/go/exp/ssh/server.go151
-rw-r--r--libgo/go/exp/ssh/session.go132
-rw-r--r--libgo/go/exp/ssh/transport.go17
-rw-r--r--libgo/go/exp/ssh/transport_test.go10
-rw-r--r--libgo/go/exp/types/gcimporter.go5
-rw-r--r--libgo/go/exp/winfsnotify/winfsnotify_test.go10
12 files changed, 1166 insertions, 85 deletions
diff --git a/libgo/go/exp/inotify/inotify_linux.go b/libgo/go/exp/inotify/inotify_linux.go
new file mode 100644
index 0000000..ee3c75f
--- /dev/null
+++ b/libgo/go/exp/inotify/inotify_linux.go
@@ -0,0 +1,288 @@
+// Copyright 2010 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.
+
+/*
+Package inotify implements a wrapper for the Linux inotify system.
+
+Example:
+ watcher, err := inotify.NewWatcher()
+ if err != nil {
+ log.Fatal(err)
+ }
+ err = watcher.Watch("/tmp")
+ if err != nil {
+ log.Fatal(err)
+ }
+ for {
+ select {
+ case ev := <-watcher.Event:
+ log.Println("event:", ev)
+ case err := <-watcher.Error:
+ log.Println("error:", err)
+ }
+ }
+
+*/
+package inotify
+
+import (
+ "fmt"
+ "os"
+ "strings"
+ "syscall"
+ "unsafe"
+)
+
+type Event struct {
+ Mask uint32 // Mask of events
+ Cookie uint32 // Unique cookie associating related events (for rename(2))
+ Name string // File name (optional)
+}
+
+type watch struct {
+ wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
+ flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
+}
+
+type Watcher struct {
+ fd int // File descriptor (as returned by the inotify_init() syscall)
+ watches map[string]*watch // Map of inotify watches (key: path)
+ paths map[int]string // Map of watched paths (key: watch descriptor)
+ Error chan os.Error // Errors are sent on this channel
+ Event chan *Event // Events are returned on this channel
+ done chan bool // Channel for sending a "quit message" to the reader goroutine
+ isClosed bool // Set to true when Close() is first called
+}
+
+// NewWatcher creates and returns a new inotify instance using inotify_init(2)
+func NewWatcher() (*Watcher, os.Error) {
+ fd, errno := syscall.InotifyInit()
+ if fd == -1 {
+ return nil, os.NewSyscallError("inotify_init", errno)
+ }
+ w := &Watcher{
+ fd: fd,
+ watches: make(map[string]*watch),
+ paths: make(map[int]string),
+ Event: make(chan *Event),
+ Error: make(chan os.Error),
+ done: make(chan bool, 1),
+ }
+
+ go w.readEvents()
+ return w, nil
+}
+
+// Close closes an inotify watcher instance
+// It sends a message to the reader goroutine to quit and removes all watches
+// associated with the inotify instance
+func (w *Watcher) Close() os.Error {
+ if w.isClosed {
+ return nil
+ }
+ w.isClosed = true
+
+ // Send "quit" message to the reader goroutine
+ w.done <- true
+ for path := range w.watches {
+ w.RemoveWatch(path)
+ }
+
+ return nil
+}
+
+// AddWatch adds path to the watched file set.
+// The flags are interpreted as described in inotify_add_watch(2).
+func (w *Watcher) AddWatch(path string, flags uint32) os.Error {
+ if w.isClosed {
+ return os.NewError("inotify instance already closed")
+ }
+
+ watchEntry, found := w.watches[path]
+ if found {
+ watchEntry.flags |= flags
+ flags |= syscall.IN_MASK_ADD
+ }
+ wd, errno := syscall.InotifyAddWatch(w.fd, path, flags)
+ if wd == -1 {
+ return &os.PathError{"inotify_add_watch", path, os.Errno(errno)}
+ }
+
+ if !found {
+ w.watches[path] = &watch{wd: uint32(wd), flags: flags}
+ w.paths[wd] = path
+ }
+ return nil
+}
+
+// Watch adds path to the watched file set, watching all events.
+func (w *Watcher) Watch(path string) os.Error {
+ return w.AddWatch(path, IN_ALL_EVENTS)
+}
+
+// RemoveWatch removes path from the watched file set.
+func (w *Watcher) RemoveWatch(path string) os.Error {
+ watch, ok := w.watches[path]
+ if !ok {
+ return os.NewError(fmt.Sprintf("can't remove non-existent inotify watch for: %s", path))
+ }
+ success, errno := syscall.InotifyRmWatch(w.fd, watch.wd)
+ if success == -1 {
+ return os.NewSyscallError("inotify_rm_watch", errno)
+ }
+ delete(w.watches, path)
+ return nil
+}
+
+// readEvents reads from the inotify file descriptor, converts the
+// received events into Event objects and sends them via the Event channel
+func (w *Watcher) readEvents() {
+ var (
+ buf [syscall.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
+ n int // Number of bytes read with read()
+ errno int // Syscall errno
+ )
+
+ for {
+ n, errno = syscall.Read(w.fd, buf[0:])
+ // See if there is a message on the "done" channel
+ var done bool
+ select {
+ case done = <-w.done:
+ default:
+ }
+
+ // If EOF or a "done" message is received
+ if n == 0 || done {
+ errno := syscall.Close(w.fd)
+ if errno == -1 {
+ w.Error <- os.NewSyscallError("close", errno)
+ }
+ close(w.Event)
+ close(w.Error)
+ return
+ }
+ if n < 0 {
+ w.Error <- os.NewSyscallError("read", errno)
+ continue
+ }
+ if n < syscall.SizeofInotifyEvent {
+ w.Error <- os.NewError("inotify: short read in readEvents()")
+ continue
+ }
+
+ var offset uint32 = 0
+ // We don't know how many events we just read into the buffer
+ // While the offset points to at least one whole event...
+ for offset <= uint32(n-syscall.SizeofInotifyEvent) {
+ // Point "raw" to the event in the buffer
+ raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset]))
+ event := new(Event)
+ event.Mask = uint32(raw.Mask)
+ event.Cookie = uint32(raw.Cookie)
+ nameLen := uint32(raw.Len)
+ // If the event happened to the watched directory or the watched file, the kernel
+ // doesn't append the filename to the event, but we would like to always fill the
+ // the "Name" field with a valid filename. We retrieve the path of the watch from
+ // the "paths" map.
+ event.Name = w.paths[int(raw.Wd)]
+ if nameLen > 0 {
+ // Point "bytes" at the first byte of the filename
+ bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent]))
+ // The filename is padded with NUL bytes. TrimRight() gets rid of those.
+ event.Name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
+ }
+ // Send the event on the events channel
+ w.Event <- event
+
+ // Move to the next event in the buffer
+ offset += syscall.SizeofInotifyEvent + nameLen
+ }
+ }
+}
+
+// String formats the event e in the form
+// "filename: 0xEventMask = IN_ACCESS|IN_ATTRIB_|..."
+func (e *Event) String() string {
+ var events string = ""
+
+ m := e.Mask
+ for _, b := range eventBits {
+ if m&b.Value != 0 {
+ m &^= b.Value
+ events += "|" + b.Name
+ }
+ }
+
+ if m != 0 {
+ events += fmt.Sprintf("|%#x", m)
+ }
+ if len(events) > 0 {
+ events = " == " + events[1:]
+ }
+
+ return fmt.Sprintf("%q: %#x%s", e.Name, e.Mask, events)
+}
+
+const (
+ // Options for inotify_init() are not exported
+ // IN_CLOEXEC uint32 = syscall.IN_CLOEXEC
+ // IN_NONBLOCK uint32 = syscall.IN_NONBLOCK
+
+ // Options for AddWatch
+ IN_DONT_FOLLOW uint32 = syscall.IN_DONT_FOLLOW
+ IN_ONESHOT uint32 = syscall.IN_ONESHOT
+ IN_ONLYDIR uint32 = syscall.IN_ONLYDIR
+
+ // The "IN_MASK_ADD" option is not exported, as AddWatch
+ // adds it automatically, if there is already a watch for the given path
+ // IN_MASK_ADD uint32 = syscall.IN_MASK_ADD
+
+ // Events
+ IN_ACCESS uint32 = syscall.IN_ACCESS
+ IN_ALL_EVENTS uint32 = syscall.IN_ALL_EVENTS
+ IN_ATTRIB uint32 = syscall.IN_ATTRIB
+ IN_CLOSE uint32 = syscall.IN_CLOSE
+ IN_CLOSE_NOWRITE uint32 = syscall.IN_CLOSE_NOWRITE
+ IN_CLOSE_WRITE uint32 = syscall.IN_CLOSE_WRITE
+ IN_CREATE uint32 = syscall.IN_CREATE
+ IN_DELETE uint32 = syscall.IN_DELETE
+ IN_DELETE_SELF uint32 = syscall.IN_DELETE_SELF
+ IN_MODIFY uint32 = syscall.IN_MODIFY
+ IN_MOVE uint32 = syscall.IN_MOVE
+ IN_MOVED_FROM uint32 = syscall.IN_MOVED_FROM
+ IN_MOVED_TO uint32 = syscall.IN_MOVED_TO
+ IN_MOVE_SELF uint32 = syscall.IN_MOVE_SELF
+ IN_OPEN uint32 = syscall.IN_OPEN
+
+ // Special events
+ IN_ISDIR uint32 = syscall.IN_ISDIR
+ IN_IGNORED uint32 = syscall.IN_IGNORED
+ IN_Q_OVERFLOW uint32 = syscall.IN_Q_OVERFLOW
+ IN_UNMOUNT uint32 = syscall.IN_UNMOUNT
+)
+
+var eventBits = []struct {
+ Value uint32
+ Name string
+}{
+ {IN_ACCESS, "IN_ACCESS"},
+ {IN_ATTRIB, "IN_ATTRIB"},
+ {IN_CLOSE, "IN_CLOSE"},
+ {IN_CLOSE_NOWRITE, "IN_CLOSE_NOWRITE"},
+ {IN_CLOSE_WRITE, "IN_CLOSE_WRITE"},
+ {IN_CREATE, "IN_CREATE"},
+ {IN_DELETE, "IN_DELETE"},
+ {IN_DELETE_SELF, "IN_DELETE_SELF"},
+ {IN_MODIFY, "IN_MODIFY"},
+ {IN_MOVE, "IN_MOVE"},
+ {IN_MOVED_FROM, "IN_MOVED_FROM"},
+ {IN_MOVED_TO, "IN_MOVED_TO"},
+ {IN_MOVE_SELF, "IN_MOVE_SELF"},
+ {IN_OPEN, "IN_OPEN"},
+ {IN_ISDIR, "IN_ISDIR"},
+ {IN_IGNORED, "IN_IGNORED"},
+ {IN_Q_OVERFLOW, "IN_Q_OVERFLOW"},
+ {IN_UNMOUNT, "IN_UNMOUNT"},
+}
diff --git a/libgo/go/exp/inotify/inotify_linux_test.go b/libgo/go/exp/inotify/inotify_linux_test.go
new file mode 100644
index 0000000..a6bb46f
--- /dev/null
+++ b/libgo/go/exp/inotify/inotify_linux_test.go
@@ -0,0 +1,96 @@
+// Copyright 2010 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.
+
+package inotify
+
+import (
+ "os"
+ "testing"
+ "time"
+)
+
+func TestInotifyEvents(t *testing.T) {
+ // Create an inotify watcher instance and initialize it
+ watcher, err := NewWatcher()
+ if err != nil {
+ t.Fatalf("NewWatcher() failed: %s", err)
+ }
+
+ // Add a watch for "_test"
+ err = watcher.Watch("_test")
+ if err != nil {
+ t.Fatalf("Watcher.Watch() failed: %s", err)
+ }
+
+ // Receive errors on the error channel on a separate goroutine
+ go func() {
+ for err := range watcher.Error {
+ t.Fatalf("error received: %s", err)
+ }
+ }()
+
+ const testFile string = "_test/TestInotifyEvents.testfile"
+
+ // Receive events on the event channel on a separate goroutine
+ eventstream := watcher.Event
+ var eventsReceived = 0
+ done := make(chan bool)
+ go func() {
+ for event := range eventstream {
+ // Only count relevant events
+ if event.Name == testFile {
+ eventsReceived++
+ t.Logf("event received: %s", event)
+ } else {
+ t.Logf("unexpected event received: %s", event)
+ }
+ }
+ done <- true
+ }()
+
+ // Create a file
+ // This should add at least one event to the inotify event queue
+ _, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
+ if err != nil {
+ t.Fatalf("creating test file failed: %s", err)
+ }
+
+ // We expect this event to be received almost immediately, but let's wait 1 s to be sure
+ time.Sleep(1000e6) // 1000 ms
+ if eventsReceived == 0 {
+ t.Fatal("inotify event hasn't been received after 1 second")
+ }
+
+ // Try closing the inotify instance
+ t.Log("calling Close()")
+ watcher.Close()
+ t.Log("waiting for the event channel to become closed...")
+ select {
+ case <-done:
+ t.Log("event channel closed")
+ case <-time.After(1e9):
+ t.Fatal("event stream was not closed after 1 second")
+ }
+}
+
+func TestInotifyClose(t *testing.T) {
+ watcher, _ := NewWatcher()
+ watcher.Close()
+
+ done := false
+ go func() {
+ watcher.Close()
+ done = true
+ }()
+
+ time.Sleep(50e6) // 50 ms
+ if !done {
+ t.Fatal("double Close() test failed: second Close() call didn't return")
+ }
+
+ err := watcher.Watch("_test")
+ if err == nil {
+ t.Fatal("expected error on Watch() after Close(), got nil")
+ }
+}
diff --git a/libgo/go/exp/ssh/channel.go b/libgo/go/exp/ssh/channel.go
index 922584f..f69b735 100644
--- a/libgo/go/exp/ssh/channel.go
+++ b/libgo/go/exp/ssh/channel.go
@@ -68,7 +68,7 @@ type channel struct {
weClosed bool
dead bool
- serverConn *ServerConnection
+ serverConn *ServerConn
myId, theirId uint32
myWindow, theirWindow uint32
maxPacketSize uint32
diff --git a/libgo/go/exp/ssh/client.go b/libgo/go/exp/ssh/client.go
new file mode 100644
index 0000000..3311385
--- /dev/null
+++ b/libgo/go/exp/ssh/client.go
@@ -0,0 +1,490 @@
+// Copyright 2011 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.
+
+package ssh
+
+import (
+ "big"
+ "crypto"
+ "crypto/rand"
+ "fmt"
+ "io"
+ "net"
+ "os"
+ "sync"
+)
+
+// clientVersion is the fixed identification string that the client will use.
+var clientVersion = []byte("SSH-2.0-Go\r\n")
+
+// ClientConn represents the client side of an SSH connection.
+type ClientConn struct {
+ *transport
+ config *ClientConfig
+ chanlist
+}
+
+// Client returns a new SSH client connection using c as the underlying transport.
+func Client(c net.Conn, config *ClientConfig) (*ClientConn, os.Error) {
+ conn := &ClientConn{
+ transport: newTransport(c, config.rand()),
+ config: config,
+ }
+ if err := conn.handshake(); err != nil {
+ conn.Close()
+ return nil, err
+ }
+ if err := conn.authenticate(); err != nil {
+ conn.Close()
+ return nil, err
+ }
+ go conn.mainLoop()
+ return conn, nil
+}
+
+// handshake performs the client side key exchange. See RFC 4253 Section 7.
+func (c *ClientConn) handshake() os.Error {
+ var magics handshakeMagics
+
+ if _, err := c.Write(clientVersion); err != nil {
+ return err
+ }
+ if err := c.Flush(); err != nil {
+ return err
+ }
+ magics.clientVersion = clientVersion[:len(clientVersion)-2]
+
+ // read remote server version
+ version, err := readVersion(c)
+ if err != nil {
+ return err
+ }
+ magics.serverVersion = version
+ clientKexInit := kexInitMsg{
+ KexAlgos: supportedKexAlgos,
+ ServerHostKeyAlgos: supportedHostKeyAlgos,
+ CiphersClientServer: supportedCiphers,
+ CiphersServerClient: supportedCiphers,
+ MACsClientServer: supportedMACs,
+ MACsServerClient: supportedMACs,
+ CompressionClientServer: supportedCompressions,
+ CompressionServerClient: supportedCompressions,
+ }
+ kexInitPacket := marshal(msgKexInit, clientKexInit)
+ magics.clientKexInit = kexInitPacket
+
+ if err := c.writePacket(kexInitPacket); err != nil {
+ return err
+ }
+ packet, err := c.readPacket()
+ if err != nil {
+ return err
+ }
+
+ magics.serverKexInit = packet
+
+ var serverKexInit kexInitMsg
+ if err = unmarshal(&serverKexInit, packet, msgKexInit); err != nil {
+ return err
+ }
+
+ kexAlgo, hostKeyAlgo, ok := findAgreedAlgorithms(c.transport, &clientKexInit, &serverKexInit)
+ if !ok {
+ return os.NewError("ssh: no common algorithms")
+ }
+
+ if serverKexInit.FirstKexFollows && kexAlgo != serverKexInit.KexAlgos[0] {
+ // The server sent a Kex message for the wrong algorithm,
+ // which we have to ignore.
+ if _, err := c.readPacket(); err != nil {
+ return err
+ }
+ }
+
+ var H, K []byte
+ var hashFunc crypto.Hash
+ switch kexAlgo {
+ case kexAlgoDH14SHA1:
+ hashFunc = crypto.SHA1
+ dhGroup14Once.Do(initDHGroup14)
+ H, K, err = c.kexDH(dhGroup14, hashFunc, &magics, hostKeyAlgo)
+ default:
+ err = fmt.Errorf("ssh: unexpected key exchange algorithm %v", kexAlgo)
+ }
+ if err != nil {
+ return err
+ }
+
+ if err = c.writePacket([]byte{msgNewKeys}); err != nil {
+ return err
+ }
+ if err = c.transport.writer.setupKeys(clientKeys, K, H, H, hashFunc); err != nil {
+ return err
+ }
+ if packet, err = c.readPacket(); err != nil {
+ return err
+ }
+ if packet[0] != msgNewKeys {
+ return UnexpectedMessageError{msgNewKeys, packet[0]}
+ }
+ return c.transport.reader.setupKeys(serverKeys, K, H, H, hashFunc)
+}
+
+// authenticate authenticates with the remote server. See RFC 4252.
+// Only "password" authentication is supported.
+func (c *ClientConn) authenticate() os.Error {
+ if err := c.writePacket(marshal(msgServiceRequest, serviceRequestMsg{serviceUserAuth})); err != nil {
+ return err
+ }
+ packet, err := c.readPacket()
+ if err != nil {
+ return err
+ }
+
+ var serviceAccept serviceAcceptMsg
+ if err = unmarshal(&serviceAccept, packet, msgServiceAccept); err != nil {
+ return err
+ }
+
+ // TODO(dfc) support proper authentication method negotation
+ method := "none"
+ if c.config.Password != "" {
+ method = "password"
+ }
+ if err := c.sendUserAuthReq(method); err != nil {
+ return err
+ }
+
+ if packet, err = c.readPacket(); err != nil {
+ return err
+ }
+
+ if packet[0] != msgUserAuthSuccess {
+ return UnexpectedMessageError{msgUserAuthSuccess, packet[0]}
+ }
+ return nil
+}
+
+func (c *ClientConn) sendUserAuthReq(method string) os.Error {
+ length := stringLength([]byte(c.config.Password)) + 1
+ payload := make([]byte, length)
+ // always false for password auth, see RFC 4252 Section 8.
+ payload[0] = 0
+ marshalString(payload[1:], []byte(c.config.Password))
+
+ return c.writePacket(marshal(msgUserAuthRequest, userAuthRequestMsg{
+ User: c.config.User,
+ Service: serviceSSH,
+ Method: method,
+ Payload: payload,
+ }))
+}
+
+// kexDH performs Diffie-Hellman key agreement on a ClientConn. The
+// returned values are given the same names as in RFC 4253, section 8.
+func (c *ClientConn) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handshakeMagics, hostKeyAlgo string) ([]byte, []byte, os.Error) {
+ x, err := rand.Int(c.config.rand(), group.p)
+ if err != nil {
+ return nil, nil, err
+ }
+ X := new(big.Int).Exp(group.g, x, group.p)
+ kexDHInit := kexDHInitMsg{
+ X: X,
+ }
+ if err := c.writePacket(marshal(msgKexDHInit, kexDHInit)); err != nil {
+ return nil, nil, err
+ }
+
+ packet, err := c.readPacket()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var kexDHReply = new(kexDHReplyMsg)
+ if err = unmarshal(kexDHReply, packet, msgKexDHReply); err != nil {
+ return nil, nil, err
+ }
+
+ if kexDHReply.Y.Sign() == 0 || kexDHReply.Y.Cmp(group.p) >= 0 {
+ return nil, nil, os.NewError("server DH parameter out of bounds")
+ }
+
+ kInt := new(big.Int).Exp(kexDHReply.Y, x, group.p)
+ h := hashFunc.New()
+ writeString(h, magics.clientVersion)
+ writeString(h, magics.serverVersion)
+ writeString(h, magics.clientKexInit)
+ writeString(h, magics.serverKexInit)
+ writeString(h, kexDHReply.HostKey)
+ writeInt(h, X)
+ writeInt(h, kexDHReply.Y)
+ K := make([]byte, intLength(kInt))
+ marshalInt(K, kInt)
+ h.Write(K)
+
+ H := h.Sum()
+
+ return H, K, nil
+}
+
+// openChan opens a new client channel. The most common session type is "session".
+// The full set of valid session types are listed in RFC 4250 4.9.1.
+func (c *ClientConn) openChan(typ string) (*clientChan, os.Error) {
+ ch := c.newChan(c.transport)
+ if err := c.writePacket(marshal(msgChannelOpen, channelOpenMsg{
+ ChanType: typ,
+ PeersId: ch.id,
+ PeersWindow: 1 << 14,
+ MaxPacketSize: 1 << 15, // RFC 4253 6.1
+ })); err != nil {
+ c.chanlist.remove(ch.id)
+ return nil, err
+ }
+ // wait for response
+ switch msg := (<-ch.msg).(type) {
+ case *channelOpenConfirmMsg:
+ ch.peersId = msg.MyId
+ case *channelOpenFailureMsg:
+ c.chanlist.remove(ch.id)
+ return nil, os.NewError(msg.Message)
+ default:
+ c.chanlist.remove(ch.id)
+ return nil, os.NewError("Unexpected packet")
+ }
+ return ch, nil
+}
+
+// mainloop reads incoming messages and routes channel messages
+// to their respective ClientChans.
+func (c *ClientConn) mainLoop() {
+ for {
+ packet, err := c.readPacket()
+ if err != nil {
+ // TODO(dfc) signal the underlying close to all channels
+ c.Close()
+ return
+ }
+ // TODO(dfc) A note on blocking channel use.
+ // The msg, win, data and dataExt channels of a clientChan can
+ // cause this loop to block indefinately if the consumer does
+ // not service them.
+ switch msg := decode(packet).(type) {
+ case *channelOpenMsg:
+ c.getChan(msg.PeersId).msg <- msg
+ case *channelOpenConfirmMsg:
+ c.getChan(msg.PeersId).msg <- msg
+ case *channelOpenFailureMsg:
+ c.getChan(msg.PeersId).msg <- msg
+ case *channelCloseMsg:
+ ch := c.getChan(msg.PeersId)
+ close(ch.win)
+ close(ch.data)
+ close(ch.dataExt)
+ c.chanlist.remove(msg.PeersId)
+ case *channelEOFMsg:
+ c.getChan(msg.PeersId).msg <- msg
+ case *channelRequestSuccessMsg:
+ c.getChan(msg.PeersId).msg <- msg
+ case *channelRequestFailureMsg:
+ c.getChan(msg.PeersId).msg <- msg
+ case *channelRequestMsg:
+ c.getChan(msg.PeersId).msg <- msg
+ case *windowAdjustMsg:
+ c.getChan(msg.PeersId).win <- int(msg.AdditionalBytes)
+ case *channelData:
+ c.getChan(msg.PeersId).data <- msg.Payload
+ case *channelExtendedData:
+ // RFC 4254 5.2 defines data_type_code 1 to be data destined
+ // for stderr on interactive sessions. Other data types are
+ // silently discarded.
+ if msg.Datatype == 1 {
+ c.getChan(msg.PeersId).dataExt <- msg.Payload
+ }
+ default:
+ fmt.Printf("mainLoop: unhandled %#v\n", msg)
+ }
+ }
+}
+
+// Dial connects to the given network address using net.Dial and
+// then initiates a SSH handshake, returning the resulting client connection.
+func Dial(network, addr string, config *ClientConfig) (*ClientConn, os.Error) {
+ conn, err := net.Dial(network, addr)
+ if err != nil {
+ return nil, err
+ }
+ return Client(conn, config)
+}
+
+// A ClientConfig structure is used to configure a ClientConn. After one has
+// been passed to an SSH function it must not be modified.
+type ClientConfig struct {
+ // Rand provides the source of entropy for key exchange. If Rand is
+ // nil, the cryptographic random reader in package crypto/rand will
+ // be used.
+ Rand io.Reader
+
+ // The username to authenticate.
+ User string
+
+ // Used for "password" method authentication.
+ Password string
+}
+
+func (c *ClientConfig) rand() io.Reader {
+ if c.Rand == nil {
+ return rand.Reader
+ }
+ return c.Rand
+}
+
+// A clientChan represents a single RFC 4254 channel that is multiplexed
+// over a single SSH connection.
+type clientChan struct {
+ packetWriter
+ id, peersId uint32
+ data chan []byte // receives the payload of channelData messages
+ dataExt chan []byte // receives the payload of channelExtendedData messages
+ win chan int // receives window adjustments
+ msg chan interface{} // incoming messages
+}
+
+func newClientChan(t *transport, id uint32) *clientChan {
+ return &clientChan{
+ packetWriter: t,
+ id: id,
+ data: make(chan []byte, 16),
+ dataExt: make(chan []byte, 16),
+ win: make(chan int, 16),
+ msg: make(chan interface{}, 16),
+ }
+}
+
+// Close closes the channel. This does not close the underlying connection.
+func (c *clientChan) Close() os.Error {
+ return c.writePacket(marshal(msgChannelClose, channelCloseMsg{
+ PeersId: c.id,
+ }))
+}
+
+func (c *clientChan) sendChanReq(req channelRequestMsg) os.Error {
+ if err := c.writePacket(marshal(msgChannelRequest, req)); err != nil {
+ return err
+ }
+ msg := <-c.msg
+ if _, ok := msg.(*channelRequestSuccessMsg); ok {
+ return nil
+ }
+ return fmt.Errorf("failed to complete request: %s, %#v", req.Request, msg)
+}
+
+// Thread safe channel list.
+type chanlist struct {
+ // protects concurrent access to chans
+ sync.Mutex
+ // chans are indexed by the local id of the channel, clientChan.id.
+ // The PeersId value of messages received by ClientConn.mainloop is
+ // used to locate the right local clientChan in this slice.
+ chans []*clientChan
+}
+
+// Allocate a new ClientChan with the next avail local id.
+func (c *chanlist) newChan(t *transport) *clientChan {
+ c.Lock()
+ defer c.Unlock()
+ for i := range c.chans {
+ if c.chans[i] == nil {
+ ch := newClientChan(t, uint32(i))
+ c.chans[i] = ch
+ return ch
+ }
+ }
+ i := len(c.chans)
+ ch := newClientChan(t, uint32(i))
+ c.chans = append(c.chans, ch)
+ return ch
+}
+
+func (c *chanlist) getChan(id uint32) *clientChan {
+ c.Lock()
+ defer c.Unlock()
+ return c.chans[int(id)]
+}
+
+func (c *chanlist) remove(id uint32) {
+ c.Lock()
+ defer c.Unlock()
+ c.chans[int(id)] = nil
+}
+
+// A chanWriter represents the stdin of a remote process.
+type chanWriter struct {
+ win chan int // receives window adjustments
+ id uint32 // this channel's id
+ rwin int // current rwin size
+ packetWriter // for sending channelDataMsg
+}
+
+// Write writes data to the remote process's standard input.
+func (w *chanWriter) Write(data []byte) (n int, err os.Error) {
+ for {
+ if w.rwin == 0 {
+ win, ok := <-w.win
+ if !ok {
+ return 0, os.EOF
+ }
+ w.rwin += win
+ continue
+ }
+ n = len(data)
+ packet := make([]byte, 0, 9+n)
+ packet = append(packet, msgChannelData,
+ byte(w.id)>>24, byte(w.id)>>16, byte(w.id)>>8, byte(w.id),
+ byte(n)>>24, byte(n)>>16, byte(n)>>8, byte(n))
+ err = w.writePacket(append(packet, data...))
+ w.rwin -= n
+ return
+ }
+ panic("unreachable")
+}
+
+func (w *chanWriter) Close() os.Error {
+ return w.writePacket(marshal(msgChannelEOF, channelEOFMsg{w.id}))
+}
+
+// A chanReader represents stdout or stderr of a remote process.
+type chanReader struct {
+ // TODO(dfc) a fixed size channel may not be the right data structure.
+ // If writes to this channel block, they will block mainLoop, making
+ // it unable to receive new messages from the remote side.
+ data chan []byte // receives data from remote
+ id uint32
+ packetWriter // for sending windowAdjustMsg
+ buf []byte
+}
+
+// Read reads data from the remote process's stdout or stderr.
+func (r *chanReader) Read(data []byte) (int, os.Error) {
+ var ok bool
+ for {
+ if len(r.buf) > 0 {
+ n := copy(data, r.buf)
+ r.buf = r.buf[n:]
+ msg := windowAdjustMsg{
+ PeersId: r.id,
+ AdditionalBytes: uint32(n),
+ }
+ return n, r.writePacket(marshal(msgChannelWindowAdjust, msg))
+ }
+ r.buf, ok = <-r.data
+ if !ok {
+ return 0, os.EOF
+ }
+ }
+ panic("unreachable")
+}
+
+func (r *chanReader) Close() os.Error {
+ return r.writePacket(marshal(msgChannelEOF, channelEOFMsg{r.id}))
+}
diff --git a/libgo/go/exp/ssh/doc.go b/libgo/go/exp/ssh/doc.go
index 54a7ba9..fc842b0 100644
--- a/libgo/go/exp/ssh/doc.go
+++ b/libgo/go/exp/ssh/doc.go
@@ -3,7 +3,7 @@
// 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
family of application protocols. The most typical application level
@@ -11,26 +11,29 @@ protocol is a remote shell and this is specifically implemented. However,
the multiplexed nature of SSH is exposed to users that wish to support
others.
-An SSH server is represented by a Server, which manages a number of
-ServerConnections and handles authentication.
+An SSH server is represented by a ServerConfig, which holds certificate
+details and handles authentication of ServerConns.
- var s Server
- s.PubKeyCallback = pubKeyAuth
- s.PasswordCallback = passwordAuth
+ config := new(ServerConfig)
+ config.PubKeyCallback = pubKeyAuth
+ config.PasswordCallback = passwordAuth
pemBytes, err := ioutil.ReadFile("id_rsa")
if err != nil {
panic("Failed to load private key")
}
- err = s.SetRSAPrivateKey(pemBytes)
+ err = config.SetRSAPrivateKey(pemBytes)
if err != nil {
panic("Failed to parse private key")
}
-Once a Server has been set up, connections can be attached.
+Once a ServerConfig has been configured, connections can be accepted.
- var sConn ServerConnection
- sConn.Server = &s
+ listener := Listen("tcp", "0.0.0.0:2022", config)
+ sConn, err := listener.Accept()
+ if err != nil {
+ panic("failed to accept incoming connection")
+ }
err = sConn.Handshake(conn)
if err != nil {
panic("failed to handshake")
@@ -38,7 +41,6 @@ Once a Server has been set up, connections can be attached.
An SSH connection multiplexes several channels, which must be accepted themselves:
-
for {
channel, err := sConn.Accept()
if err != nil {
@@ -75,5 +77,29 @@ present a simple terminal interface.
}
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 interactive sessions, represented by a Session.
+
+ session, err := client.NewSession()
+
+Once a Session is created, you can execute a single command on the remote side
+using the Exec method.
+
+ if err := session.Exec("/usr/bin/whoami"); err != nil {
+ panic("Failed to exec: " + err.String())
+ }
+ reader := bufio.NewReader(session.Stdin)
+ line, _, _ := reader.ReadLine()
+ fmt.Println(line)
+ session.Close()
*/
package ssh
diff --git a/libgo/go/exp/ssh/messages.go b/libgo/go/exp/ssh/messages.go
index 1d0bc57..7771f2b 100644
--- a/libgo/go/exp/ssh/messages.go
+++ b/libgo/go/exp/ssh/messages.go
@@ -154,7 +154,7 @@ type channelData struct {
type channelExtendedData struct {
PeersId uint32
Datatype uint32
- Data string
+ Payload []byte `ssh:"rest"`
}
type channelRequestMsg struct {
diff --git a/libgo/go/exp/ssh/server.go b/libgo/go/exp/ssh/server.go
index 410cafc..3a640fc 100644
--- a/libgo/go/exp/ssh/server.go
+++ b/libgo/go/exp/ssh/server.go
@@ -10,19 +10,23 @@ import (
"crypto"
"crypto/rand"
"crypto/rsa"
- _ "crypto/sha1"
"crypto/x509"
"encoding/pem"
+ "io"
"net"
"os"
"sync"
)
-// Server represents an SSH server. A Server may have several ServerConnections.
-type Server struct {
+type ServerConfig struct {
rsa *rsa.PrivateKey
rsaSerialized []byte
+ // Rand provides the source of entropy for key exchange. If Rand is
+ // nil, the cryptographic random reader in package crypto/rand will
+ // be used.
+ Rand io.Reader
+
// NoClientAuth is true if clients are allowed to connect without
// authenticating.
NoClientAuth bool
@@ -38,11 +42,18 @@ type Server struct {
PubKeyCallback func(user, algo string, pubkey []byte) bool
}
+func (c *ServerConfig) rand() io.Reader {
+ if c.Rand == nil {
+ return rand.Reader
+ }
+ return c.Rand
+}
+
// SetRSAPrivateKey sets the private key for a Server. A Server must have a
// private key configured in order to accept connections. The private key must
// be in the form of a PEM encoded, PKCS#1, RSA private key. The file "id_rsa"
// typically contains such a key.
-func (s *Server) SetRSAPrivateKey(pemBytes []byte) os.Error {
+func (s *ServerConfig) SetRSAPrivateKey(pemBytes []byte) os.Error {
block, _ := pem.Decode(pemBytes)
if block == nil {
return os.NewError("ssh: no key found")
@@ -109,7 +120,7 @@ func parseRSASig(in []byte) (sig []byte, ok bool) {
}
// cachedPubKey contains the results of querying whether a public key is
-// acceptable for a user. The cache only applies to a single ServerConnection.
+// acceptable for a user. The cache only applies to a single ServerConn.
type cachedPubKey struct {
user, algo string
pubKey []byte
@@ -118,11 +129,10 @@ type cachedPubKey struct {
const maxCachedPubKeys = 16
-// ServerConnection represents an incomming connection to a Server.
-type ServerConnection struct {
- Server *Server
-
+// A ServerConn represents an incomming connection.
+type ServerConn struct {
*transport
+ config *ServerConfig
channels map[uint32]*channel
nextChanId uint32
@@ -139,9 +149,20 @@ type ServerConnection struct {
cachedPubKeys []cachedPubKey
}
+// Server returns a new SSH server connection
+// using c as the underlying transport.
+func Server(c net.Conn, config *ServerConfig) *ServerConn {
+ conn := &ServerConn{
+ transport: newTransport(c, config.rand()),
+ channels: make(map[uint32]*channel),
+ config: config,
+ }
+ return conn
+}
+
// kexDH performs Diffie-Hellman key agreement on a ServerConnection. The
// returned values are given the same names as in RFC 4253, section 8.
-func (s *ServerConnection) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handshakeMagics, hostKeyAlgo string) (H, K []byte, err os.Error) {
+func (s *ServerConn) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handshakeMagics, hostKeyAlgo string) (H, K []byte, err os.Error) {
packet, err := s.readPacket()
if err != nil {
return
@@ -155,7 +176,7 @@ func (s *ServerConnection) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *h
return nil, nil, os.NewError("client DH parameter out of bounds")
}
- y, err := rand.Int(rand.Reader, group.p)
+ y, err := rand.Int(s.config.rand(), group.p)
if err != nil {
return
}
@@ -166,7 +187,7 @@ func (s *ServerConnection) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *h
var serializedHostKey []byte
switch hostKeyAlgo {
case hostAlgoRSA:
- serializedHostKey = s.Server.rsaSerialized
+ serializedHostKey = s.config.rsaSerialized
default:
return nil, nil, os.NewError("internal error")
}
@@ -192,7 +213,7 @@ func (s *ServerConnection) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *h
var sig []byte
switch hostKeyAlgo {
case hostAlgoRSA:
- sig, err = rsa.SignPKCS1v15(rand.Reader, s.Server.rsa, hashFunc, hh)
+ sig, err = rsa.SignPKCS1v15(s.config.rand(), s.config.rsa, hashFunc, hh)
if err != nil {
return
}
@@ -257,19 +278,20 @@ func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubK
return ret
}
-// Handshake performs an SSH transport and client authentication on the given ServerConnection.
-func (s *ServerConnection) Handshake(conn net.Conn) os.Error {
+// Handshake performs an SSH transport and client authentication on the given ServerConn.
+func (s *ServerConn) Handshake() os.Error {
var magics handshakeMagics
- s.transport = newTransport(conn, rand.Reader)
-
- if _, err := conn.Write(serverVersion); err != nil {
+ if _, err := s.Write(serverVersion); err != nil {
+ return err
+ }
+ if err := s.Flush(); err != nil {
return err
}
magics.serverVersion = serverVersion[:len(serverVersion)-2]
- version, ok := readVersion(s.transport)
- if !ok {
- return os.NewError("failed to read version string from client")
+ version, err := readVersion(s)
+ if err != nil {
+ return err
}
magics.clientVersion = version
@@ -310,8 +332,7 @@ func (s *ServerConnection) Handshake(conn net.Conn) os.Error {
if clientKexInit.FirstKexFollows && kexAlgo != clientKexInit.KexAlgos[0] {
// The client sent a Kex message for the wrong algorithm,
// which we have to ignore.
- _, err := s.readPacket()
- if err != nil {
+ if _, err := s.readPacket(); err != nil {
return err
}
}
@@ -324,32 +345,27 @@ func (s *ServerConnection) Handshake(conn net.Conn) os.Error {
dhGroup14Once.Do(initDHGroup14)
H, K, err = s.kexDH(dhGroup14, hashFunc, &magics, hostKeyAlgo)
default:
- err = os.NewError("ssh: internal error")
+ err = os.NewError("ssh: unexpected key exchange algorithm " + kexAlgo)
}
-
if err != nil {
return err
}
- packet = []byte{msgNewKeys}
- if err = s.writePacket(packet); err != nil {
+ if err = s.writePacket([]byte{msgNewKeys}); err != nil {
return err
}
if err = s.transport.writer.setupKeys(serverKeys, K, H, H, hashFunc); err != nil {
return err
}
-
if packet, err = s.readPacket(); err != nil {
return err
}
+
if packet[0] != msgNewKeys {
return UnexpectedMessageError{msgNewKeys, packet[0]}
}
-
s.transport.reader.setupKeys(clientKeys, K, H, H, hashFunc)
-
- packet, err = s.readPacket()
- if err != nil {
+ if packet, err = s.readPacket(); err != nil {
return err
}
@@ -360,20 +376,16 @@ func (s *ServerConnection) Handshake(conn net.Conn) os.Error {
if serviceRequest.Service != serviceUserAuth {
return os.NewError("ssh: requested service '" + serviceRequest.Service + "' before authenticating")
}
-
serviceAccept := serviceAcceptMsg{
Service: serviceUserAuth,
}
- packet = marshal(msgServiceAccept, serviceAccept)
- if err = s.writePacket(packet); err != nil {
+ if err = s.writePacket(marshal(msgServiceAccept, serviceAccept)); err != nil {
return err
}
if err = s.authenticate(H); err != nil {
return err
}
-
- s.channels = make(map[uint32]*channel)
return nil
}
@@ -382,8 +394,8 @@ func isAcceptableAlgo(algo string) bool {
}
// testPubKey returns true if the given public key is acceptable for the user.
-func (s *ServerConnection) testPubKey(user, algo string, pubKey []byte) bool {
- if s.Server.PubKeyCallback == nil || !isAcceptableAlgo(algo) {
+func (s *ServerConn) testPubKey(user, algo string, pubKey []byte) bool {
+ if s.config.PubKeyCallback == nil || !isAcceptableAlgo(algo) {
return false
}
@@ -393,7 +405,7 @@ func (s *ServerConnection) testPubKey(user, algo string, pubKey []byte) bool {
}
}
- result := s.Server.PubKeyCallback(user, algo, pubKey)
+ result := s.config.PubKeyCallback(user, algo, pubKey)
if len(s.cachedPubKeys) < maxCachedPubKeys {
c := cachedPubKey{
user: user,
@@ -408,7 +420,7 @@ func (s *ServerConnection) testPubKey(user, algo string, pubKey []byte) bool {
return result
}
-func (s *ServerConnection) authenticate(H []byte) os.Error {
+func (s *ServerConn) authenticate(H []byte) os.Error {
var userAuthReq userAuthRequestMsg
var err os.Error
var packet []byte
@@ -428,11 +440,11 @@ userAuthLoop:
switch userAuthReq.Method {
case "none":
- if s.Server.NoClientAuth {
+ if s.config.NoClientAuth {
break userAuthLoop
}
case "password":
- if s.Server.PasswordCallback == nil {
+ if s.config.PasswordCallback == nil {
break
}
payload := userAuthReq.Payload
@@ -445,11 +457,11 @@ userAuthLoop:
return ParseError{msgUserAuthRequest}
}
- if s.Server.PasswordCallback(userAuthReq.User, string(password)) {
+ if s.config.PasswordCallback(userAuthReq.User, string(password)) {
break userAuthLoop
}
case "publickey":
- if s.Server.PubKeyCallback == nil {
+ if s.config.PubKeyCallback == nil {
break
}
payload := userAuthReq.Payload
@@ -520,10 +532,10 @@ userAuthLoop:
}
var failureMsg userAuthFailureMsg
- if s.Server.PasswordCallback != nil {
+ if s.config.PasswordCallback != nil {
failureMsg.Methods = append(failureMsg.Methods, "password")
}
- if s.Server.PubKeyCallback != nil {
+ if s.config.PubKeyCallback != nil {
failureMsg.Methods = append(failureMsg.Methods, "publickey")
}
@@ -546,9 +558,9 @@ userAuthLoop:
const defaultWindowSize = 32768
-// Accept reads and processes messages on a ServerConnection. It must be called
+// Accept reads and processes messages on a ServerConn. It must be called
// in order to demultiplex messages to any resulting Channels.
-func (s *ServerConnection) Accept() (Channel, os.Error) {
+func (s *ServerConn) Accept() (Channel, os.Error) {
if s.err != nil {
return nil, s.err
}
@@ -643,3 +655,44 @@ func (s *ServerConnection) Accept() (Channel, os.Error) {
panic("unreachable")
}
+
+// A Listener implements a network listener (net.Listener) for SSH connections.
+type Listener struct {
+ listener net.Listener
+ config *ServerConfig
+}
+
+// Accept waits for and returns the next incoming SSH connection.
+// The receiver should call Handshake() in another goroutine
+// to avoid blocking the accepter.
+func (l *Listener) Accept() (*ServerConn, os.Error) {
+ c, err := l.listener.Accept()
+ if err != nil {
+ return nil, err
+ }
+ conn := Server(c, l.config)
+ return conn, nil
+}
+
+// Addr returns the listener's network address.
+func (l *Listener) Addr() net.Addr {
+ return l.listener.Addr()
+}
+
+// Close closes the listener.
+func (l *Listener) Close() os.Error {
+ return l.listener.Close()
+}
+
+// Listen creates an SSH listener accepting connections on
+// the given network address using net.Listen.
+func Listen(network, addr string, config *ServerConfig) (*Listener, os.Error) {
+ l, err := net.Listen(network, addr)
+ if err != nil {
+ return nil, err
+ }
+ return &Listener{
+ l,
+ config,
+ }, nil
+}
diff --git a/libgo/go/exp/ssh/session.go b/libgo/go/exp/ssh/session.go
new file mode 100644
index 0000000..13df2f0
--- /dev/null
+++ b/libgo/go/exp/ssh/session.go
@@ -0,0 +1,132 @@
+// Copyright 2011 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.
+
+package ssh
+
+// Session implements an interactive session described in
+// "RFC 4254, section 6".
+
+import (
+ "encoding/binary"
+ "io"
+ "os"
+)
+
+// A Session represents a connection to a remote command or shell.
+type Session struct {
+ // Writes to Stdin are made available to the remote command's standard input.
+ // Closing Stdin causes the command to observe an EOF on its standard input.
+ Stdin io.WriteCloser
+
+ // Reads from Stdout and Stderr consume from the remote command's standard
+ // output and error streams, respectively.
+ // There is a fixed amount of buffering that is shared for the two streams.
+ // Failing to read from either may eventually cause the command to block.
+ // Closing Stdout unblocks such writes and causes them to return errors.
+ Stdout io.ReadCloser
+ Stderr io.Reader
+
+ *clientChan // the channel backing this session
+
+ started bool // started is set to true once a Shell or Exec is invoked.
+}
+
+// Setenv sets an environment variable that will be applied to any
+// command executed by Shell or Exec.
+func (s *Session) Setenv(name, value string) os.Error {
+ n, v := []byte(name), []byte(value)
+ nlen, vlen := stringLength(n), stringLength(v)
+ payload := make([]byte, nlen+vlen)
+ marshalString(payload[:nlen], n)
+ marshalString(payload[nlen:], v)
+
+ return s.sendChanReq(channelRequestMsg{
+ PeersId: s.id,
+ Request: "env",
+ WantReply: true,
+ RequestSpecificData: payload,
+ })
+}
+
+// An empty mode list (a string of 1 character, opcode 0), see RFC 4254 Section 8.
+var emptyModeList = []byte{0, 0, 0, 1, 0}
+
+// RequestPty requests the association of a pty with the session on the remote host.
+func (s *Session) RequestPty(term string, h, w int) os.Error {
+ buf := make([]byte, 4+len(term)+16+len(emptyModeList))
+ b := marshalString(buf, []byte(term))
+ binary.BigEndian.PutUint32(b, uint32(h))
+ binary.BigEndian.PutUint32(b[4:], uint32(w))
+ binary.BigEndian.PutUint32(b[8:], uint32(h*8))
+ binary.BigEndian.PutUint32(b[12:], uint32(w*8))
+ copy(b[16:], emptyModeList)
+
+ return s.sendChanReq(channelRequestMsg{
+ PeersId: s.id,
+ Request: "pty-req",
+ WantReply: true,
+ RequestSpecificData: buf,
+ })
+}
+
+// Exec runs cmd on the remote host. Typically, the remote
+// server passes cmd to the shell for interpretation.
+// A Session only accepts one call to Exec or Shell.
+func (s *Session) Exec(cmd string) os.Error {
+ if s.started {
+ return os.NewError("session already started")
+ }
+ cmdLen := stringLength([]byte(cmd))
+ payload := make([]byte, cmdLen)
+ marshalString(payload, []byte(cmd))
+ s.started = true
+
+ return s.sendChanReq(channelRequestMsg{
+ PeersId: s.id,
+ Request: "exec",
+ WantReply: true,
+ RequestSpecificData: payload,
+ })
+}
+
+// Shell starts a login shell on the remote host. A Session only
+// accepts one call to Exec or Shell.
+func (s *Session) Shell() os.Error {
+ if s.started {
+ return os.NewError("session already started")
+ }
+ s.started = true
+
+ return s.sendChanReq(channelRequestMsg{
+ PeersId: s.id,
+ Request: "shell",
+ WantReply: true,
+ })
+}
+
+// NewSession returns a new interactive session on the remote host.
+func (c *ClientConn) NewSession() (*Session, os.Error) {
+ ch, err := c.openChan("session")
+ if err != nil {
+ return nil, err
+ }
+ return &Session{
+ Stdin: &chanWriter{
+ packetWriter: ch,
+ id: ch.id,
+ win: ch.win,
+ },
+ Stdout: &chanReader{
+ packetWriter: ch,
+ id: ch.id,
+ data: ch.data,
+ },
+ Stderr: &chanReader{
+ packetWriter: ch,
+ id: ch.id,
+ data: ch.dataExt,
+ },
+ clientChan: ch,
+ }, nil
+}
diff --git a/libgo/go/exp/ssh/transport.go b/libgo/go/exp/ssh/transport.go
index 5994004..97eaf97 100644
--- a/libgo/go/exp/ssh/transport.go
+++ b/libgo/go/exp/ssh/transport.go
@@ -332,16 +332,15 @@ func (t truncatingMAC) Size() int {
const maxVersionStringBytes = 1024
// Read version string as specified by RFC 4253, section 4.2.
-func readVersion(r io.Reader) (versionString []byte, ok bool) {
- versionString = make([]byte, 0, 64)
- seenCR := false
-
+func readVersion(r io.Reader) ([]byte, os.Error) {
+ versionString := make([]byte, 0, 64)
+ var ok, seenCR bool
var buf [1]byte
forEachByte:
for len(versionString) < maxVersionStringBytes {
_, err := io.ReadFull(r, buf[:])
if err != nil {
- return
+ return nil, err
}
b := buf[0]
@@ -360,10 +359,10 @@ forEachByte:
versionString = append(versionString, b)
}
- if ok {
- // We need to remove the CR from versionString
- versionString = versionString[:len(versionString)-1]
+ if !ok {
+ return nil, os.NewError("failed to read version string")
}
- return
+ // We need to remove the CR from versionString
+ return versionString[:len(versionString)-1], nil
}
diff --git a/libgo/go/exp/ssh/transport_test.go b/libgo/go/exp/ssh/transport_test.go
index 9a610a7..b2e2a7f 100644
--- a/libgo/go/exp/ssh/transport_test.go
+++ b/libgo/go/exp/ssh/transport_test.go
@@ -12,9 +12,9 @@ import (
func TestReadVersion(t *testing.T) {
buf := []byte(serverVersion)
- result, ok := readVersion(bufio.NewReader(bytes.NewBuffer(buf)))
- if !ok {
- t.Error("readVersion didn't read version correctly")
+ result, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf)))
+ if err != nil {
+ t.Errorf("readVersion didn't read version correctly: %s", err)
}
if !bytes.Equal(buf[:len(buf)-2], result) {
t.Error("version read did not match expected")
@@ -23,7 +23,7 @@ func TestReadVersion(t *testing.T) {
func TestReadVersionTooLong(t *testing.T) {
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))
}
}
@@ -31,7 +31,7 @@ func TestReadVersionTooLong(t *testing.T) {
func TestReadVersionWithoutCRLF(t *testing.T) {
buf := []byte(serverVersion)
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")
}
}
diff --git a/libgo/go/exp/types/gcimporter.go b/libgo/go/exp/types/gcimporter.go
index fe90f91..e744a63 100644
--- a/libgo/go/exp/types/gcimporter.go
+++ b/libgo/go/exp/types/gcimporter.go
@@ -289,9 +289,10 @@ func (p *gcParser) parseExportedName() (*ast.Object, string) {
// BasicType = identifier .
//
func (p *gcParser) parseBasicType() Type {
- obj := Universe.Lookup(p.expect(scanner.Ident))
+ id := p.expect(scanner.Ident)
+ obj := Universe.Lookup(id)
if obj == nil || obj.Kind != ast.Typ {
- p.errorf("not a basic type: %s", obj.Name)
+ p.errorf("not a basic type: %s", id)
}
return obj.Type.(Type)
}
diff --git a/libgo/go/exp/winfsnotify/winfsnotify_test.go b/libgo/go/exp/winfsnotify/winfsnotify_test.go
index edf2165..6e264d0 100644
--- a/libgo/go/exp/winfsnotify/winfsnotify_test.go
+++ b/libgo/go/exp/winfsnotify/winfsnotify_test.go
@@ -6,8 +6,8 @@ package winfsnotify
import (
"os"
- "time"
"testing"
+ "time"
)
func expect(t *testing.T, eventstream <-chan *Event, name string, mask uint32) {
@@ -70,15 +70,11 @@ func TestNotifyEvents(t *testing.T) {
if _, err = file.WriteString("hello, world"); err != nil {
t.Fatalf("failed to write to test file: %s", err)
}
- if err = file.Sync(); err != nil {
- t.Fatalf("failed to sync test file: %s", err)
- }
- expect(t, watcher.Event, testFile, FS_MODIFY)
- expect(t, watcher.Event, testFile, FS_MODIFY)
-
if err = file.Close(); err != nil {
t.Fatalf("failed to close test file: %s", err)
}
+ expect(t, watcher.Event, testFile, FS_MODIFY)
+ expect(t, watcher.Event, testFile, FS_MODIFY)
if err = os.Rename(testFile, testFile2); err != nil {
t.Fatalf("failed to rename test file: %s", err)