diff options
Diffstat (limited to 'libgo/go/exp/ssh')
-rw-r--r-- | libgo/go/exp/ssh/channel.go | 12 | ||||
-rw-r--r-- | libgo/go/exp/ssh/client.go | 67 | ||||
-rw-r--r-- | libgo/go/exp/ssh/client_auth_test.go | 2 | ||||
-rw-r--r-- | libgo/go/exp/ssh/common.go | 13 | ||||
-rw-r--r-- | libgo/go/exp/ssh/common_test.go | 26 | ||||
-rw-r--r-- | libgo/go/exp/ssh/doc.go | 4 | ||||
-rw-r--r-- | libgo/go/exp/ssh/server.go | 6 | ||||
-rw-r--r-- | libgo/go/exp/ssh/session.go | 452 | ||||
-rw-r--r-- | libgo/go/exp/ssh/session_test.go | 149 | ||||
-rw-r--r-- | libgo/go/exp/ssh/tcpip.go | 4 | ||||
-rw-r--r-- | libgo/go/exp/ssh/tcpip_func_test.go | 59 | ||||
-rw-r--r-- | libgo/go/exp/ssh/transport.go | 12 |
12 files changed, 647 insertions, 159 deletions
diff --git a/libgo/go/exp/ssh/channel.go b/libgo/go/exp/ssh/channel.go index 6ff8203..9d75f37 100644 --- a/libgo/go/exp/ssh/channel.go +++ b/libgo/go/exp/ssh/channel.go @@ -244,13 +244,13 @@ func (c *channel) Write(data []byte) (n int, err error) { packet := make([]byte, 1+4+4+len(todo)) packet[0] = msgChannelData - packet[1] = byte(c.theirId) >> 24 - packet[2] = byte(c.theirId) >> 16 - packet[3] = byte(c.theirId) >> 8 + packet[1] = byte(c.theirId >> 24) + packet[2] = byte(c.theirId >> 16) + packet[3] = byte(c.theirId >> 8) packet[4] = byte(c.theirId) - packet[5] = byte(len(todo)) >> 24 - packet[6] = byte(len(todo)) >> 16 - packet[7] = byte(len(todo)) >> 8 + packet[5] = byte(len(todo) >> 24) + packet[6] = byte(len(todo) >> 16) + packet[7] = byte(len(todo) >> 8) packet[8] = byte(len(todo)) copy(packet[9:], todo) diff --git a/libgo/go/exp/ssh/client.go b/libgo/go/exp/ssh/client.go index 24569ad..429dee9 100644 --- a/libgo/go/exp/ssh/client.go +++ b/libgo/go/exp/ssh/client.go @@ -172,40 +172,12 @@ func (c *ClientConn) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handsha marshalInt(K, kInt) h.Write(K) - H := h.Sum() + H := h.Sum(nil) 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, 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 - ch.win <- int(msg.MyWindow) - case *channelOpenFailureMsg: - c.chanlist.remove(ch.id) - return nil, errors.New(msg.Message) - default: - c.chanlist.remove(ch.id) - return nil, errors.New("Unexpected packet") - } - return ch, nil -} - -// mainloop reads incoming messages and routes channel messages +// mainLoop reads incoming messages and routes channel messages // to their respective ClientChans. func (c *ClientConn) mainLoop() { // TODO(dfc) signal the underlying close to all channels @@ -271,7 +243,7 @@ func (c *ClientConn) mainLoop() { case *windowAdjustMsg: c.getChan(msg.PeersId).win <- int(msg.AdditionalBytes) default: - fmt.Printf("mainLoop: unhandled %#v\n", msg) + fmt.Printf("mainLoop: unhandled message %T: %v\n", msg, msg) } } } @@ -338,27 +310,16 @@ func newClientChan(t *transport, id uint32) *clientChan { // Close closes the channel. This does not close the underlying connection. func (c *clientChan) Close() error { return c.writePacket(marshal(msgChannelClose, channelCloseMsg{ - PeersId: c.id, + PeersId: c.peersId, })) } -func (c *clientChan) sendChanReq(req channelRequestMsg) 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 + // The PeersId value of messages received by ClientConn.mainLoop is // used to locate the right local clientChan in this slice. chans []*clientChan } @@ -395,7 +356,7 @@ func (c *chanlist) remove(id uint32) { // A chanWriter represents the stdin of a remote process. type chanWriter struct { win chan int // receives window adjustments - id uint32 // this channel's id + peersId uint32 // the peer's id rwin int // current rwin size packetWriter // for sending channelDataMsg } @@ -414,8 +375,8 @@ func (w *chanWriter) Write(data []byte) (n int, err error) { 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)) + byte(w.peersId>>24), byte(w.peersId>>16), byte(w.peersId>>8), byte(w.peersId), + byte(n>>24), byte(n>>16), byte(n>>8), byte(n)) err = w.writePacket(append(packet, data...)) w.rwin -= n return @@ -424,7 +385,7 @@ func (w *chanWriter) Write(data []byte) (n int, err error) { } func (w *chanWriter) Close() error { - return w.writePacket(marshal(msgChannelEOF, channelEOFMsg{w.id})) + return w.writePacket(marshal(msgChannelEOF, channelEOFMsg{w.peersId})) } // A chanReader represents stdout or stderr of a remote process. @@ -433,8 +394,8 @@ type chanReader struct { // 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 + peersId uint32 // the peer's id + packetWriter // for sending windowAdjustMsg buf []byte } @@ -446,7 +407,7 @@ func (r *chanReader) Read(data []byte) (int, error) { n := copy(data, r.buf) r.buf = r.buf[n:] msg := windowAdjustMsg{ - PeersId: r.id, + PeersId: r.peersId, AdditionalBytes: uint32(n), } return n, r.writePacket(marshal(msgChannelWindowAdjust, msg)) @@ -458,7 +419,3 @@ func (r *chanReader) Read(data []byte) (int, error) { } panic("unreachable") } - -func (r *chanReader) Close() error { - return r.writePacket(marshal(msgChannelEOF, channelEOFMsg{r.id})) -} diff --git a/libgo/go/exp/ssh/client_auth_test.go b/libgo/go/exp/ssh/client_auth_test.go index 6467f57..4ef9213 100644 --- a/libgo/go/exp/ssh/client_auth_test.go +++ b/libgo/go/exp/ssh/client_auth_test.go @@ -70,7 +70,7 @@ func (k *keychain) Sign(i int, rand io.Reader, data []byte) (sig []byte, err err hashFunc := crypto.SHA1 h := hashFunc.New() h.Write(data) - digest := h.Sum() + digest := h.Sum(nil) return rsa.SignPKCS1v15(rand, k.keys[i], hashFunc, digest) } diff --git a/libgo/go/exp/ssh/common.go b/libgo/go/exp/ssh/common.go index 01c55219..6844fb8 100644 --- a/libgo/go/exp/ssh/common.go +++ b/libgo/go/exp/ssh/common.go @@ -224,3 +224,16 @@ func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubK r = marshalString(r, pubKey) return ret } + +// safeString sanitises s according to RFC 4251, section 9.2. +// All control characters except tab, carriage return and newline are +// replaced by 0x20. +func safeString(s string) string { + out := []byte(s) + for i, c := range out { + if c < 0x20 && c != 0xd && c != 0xa && c != 0x9 { + out[i] = 0x20 + } + } + return string(out) +} diff --git a/libgo/go/exp/ssh/common_test.go b/libgo/go/exp/ssh/common_test.go new file mode 100644 index 0000000..2f4448a --- /dev/null +++ b/libgo/go/exp/ssh/common_test.go @@ -0,0 +1,26 @@ +// 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 ( + "testing" +) + +var strings = map[string]string{ + "\x20\x0d\x0a": "\x20\x0d\x0a", + "flibble": "flibble", + "new\x20line": "new\x20line", + "123456\x07789": "123456 789", + "\t\t\x10\r\n": "\t\t \r\n", +} + +func TestSafeString(t *testing.T) { + for s, expected := range strings { + actual := safeString(s) + if expected != actual { + t.Errorf("expected: %v, actual: %v", []byte(expected), []byte(actual)) + } + } +} diff --git a/libgo/go/exp/ssh/doc.go b/libgo/go/exp/ssh/doc.go index 248b2fe..480f877 100644 --- a/libgo/go/exp/ssh/doc.go +++ b/libgo/go/exp/ssh/doc.go @@ -92,9 +92,9 @@ Each ClientConn can support multiple interactive sessions, represented by a Sess session, err := client.NewSession() Once a Session is created, you can execute a single command on the remote side -using the Exec method. +using the Run method. - if err := session.Exec("/usr/bin/whoami"); err != nil { + if err := session.Run("/usr/bin/whoami"); err != nil { panic("Failed to exec: " + err.String()) } reader := bufio.NewReader(session.Stdin) diff --git a/libgo/go/exp/ssh/server.go b/libgo/go/exp/ssh/server.go index 428a747..1eee9a4 100644 --- a/libgo/go/exp/ssh/server.go +++ b/libgo/go/exp/ssh/server.go @@ -207,11 +207,11 @@ func (s *ServerConn) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handsha marshalInt(K, kInt) h.Write(K) - H = h.Sum() + H = h.Sum(nil) h.Reset() h.Write(H) - hh := h.Sum() + hh := h.Sum(nil) var sig []byte switch hostKeyAlgo { @@ -478,7 +478,7 @@ userAuthLoop: hashFunc := crypto.SHA1 h := hashFunc.New() h.Write(signedData) - digest := h.Sum() + digest := h.Sum(nil) rsaKey, ok := parseRSA(pubKey) if !ok { return ParseError{msgUserAuthRequest} diff --git a/libgo/go/exp/ssh/session.go b/libgo/go/exp/ssh/session.go index 77154f2..5f98a8d 100644 --- a/libgo/go/exp/ssh/session.go +++ b/libgo/go/exp/ssh/session.go @@ -8,125 +8,409 @@ package ssh // "RFC 4254, section 6". import ( - "encoding/binary" + "bytes" "errors" + "fmt" "io" + "io/ioutil" +) + +type Signal string + +// POSIX signals as listed in RFC 4254 Section 6.10. +const ( + SIGABRT Signal = "ABRT" + SIGALRM Signal = "ALRM" + SIGFPE Signal = "FPE" + SIGHUP Signal = "HUP" + SIGILL Signal = "ILL" + SIGINT Signal = "INT" + SIGKILL Signal = "KILL" + SIGPIPE Signal = "PIPE" + SIGQUIT Signal = "QUIT" + SIGSEGV Signal = "SEGV" + SIGTERM Signal = "TERM" + SIGUSR1 Signal = "USR1" + SIGUSR2 Signal = "USR2" ) // 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 + // Stdin specifies the remote process's standard input. + // If Stdin is nil, the remote process reads from an empty + // bytes.Buffer. + Stdin io.Reader + + // Stdout and Stderr specify the remote process's standard + // output and error. + // + // If either is nil, Run connects the corresponding file + // descriptor to an instance of ioutil.Discard. There is a + // fixed amount of buffering that is shared for the two streams. + // If either blocks it may eventually cause the remote + // command to block. + Stdout io.Writer + Stderr io.Writer *clientChan // the channel backing this session - started bool // started is set to true once a Shell or Exec is invoked. + started bool // true once Start, Run or Shell is invoked. + closeAfterWait []io.Closer + copyFuncs []func() error + errch chan error // one send per copyFunc +} + +// RFC 4254 Section 6.4. +type setenvRequest struct { + PeersId uint32 + Request string + WantReply bool + Name string + Value string } // Setenv sets an environment variable that will be applied to any -// command executed by Shell or Exec. +// command executed by Shell or Run. func (s *Session) Setenv(name, value string) 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, - }) + req := setenvRequest{ + PeersId: s.peersId, + Request: "env", + WantReply: true, + Name: name, + Value: value, + } + if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil { + return err + } + return s.waitForResponse() } -// An empty mode list (a string of 1 character, opcode 0), see RFC 4254 Section 8. -var emptyModeList = []byte{0, 0, 0, 1, 0} +// An empty mode list, see RFC 4254 Section 8. +var emptyModelist = "\x00" + +// RFC 4254 Section 6.2. +type ptyRequestMsg struct { + PeersId uint32 + Request string + WantReply bool + Term string + Columns uint32 + Rows uint32 + Width uint32 + Height uint32 + Modelist string +} // RequestPty requests the association of a pty with the session on the remote host. func (s *Session) RequestPty(term string, h, w int) 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, - }) + req := ptyRequestMsg{ + PeersId: s.peersId, + Request: "pty-req", + WantReply: true, + Term: term, + Columns: uint32(w), + Rows: uint32(h), + Width: uint32(w * 8), + Height: uint32(h * 8), + Modelist: emptyModelist, + } + if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil { + return err + } + return s.waitForResponse() } -// 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) error { +// RFC 4254 Section 6.9. +type signalMsg struct { + PeersId uint32 + Request string + WantReply bool + Signal string +} + +// Signal sends the given signal to the remote process. +// sig is one of the SIG* constants. +func (s *Session) Signal(sig Signal) error { + req := signalMsg{ + PeersId: s.peersId, + Request: "signal", + WantReply: false, + Signal: string(sig), + } + return s.writePacket(marshal(msgChannelRequest, req)) +} + +// RFC 4254 Section 6.5. +type execMsg struct { + PeersId uint32 + Request string + WantReply bool + Command string +} + +// Start runs cmd on the remote host. Typically, the remote +// server passes cmd to the shell for interpretation. +// A Session only accepts one call to Run, Start or Shell. +func (s *Session) Start(cmd string) error { if s.started { - return errors.New("session already started") + return errors.New("ssh: session already started") } - cmdLen := stringLength([]byte(cmd)) - payload := make([]byte, cmdLen) - marshalString(payload, []byte(cmd)) - s.started = true + req := execMsg{ + PeersId: s.peersId, + Request: "exec", + WantReply: true, + Command: cmd, + } + if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil { + return err + } + if err := s.waitForResponse(); err != nil { + return fmt.Errorf("ssh: could not execute command %s: %v", cmd, err) + } + return s.start() +} - return s.sendChanReq(channelRequestMsg{ - PeersId: s.id, - Request: "exec", - WantReply: true, - RequestSpecificData: payload, - }) +// Run runs cmd on the remote host and waits for it to terminate. +// Typically, the remote server passes cmd to the shell for +// interpretation. A Session only accepts one call to Run, +// Start or Shell. +func (s *Session) Run(cmd string) error { + err := s.Start(cmd) + if err != nil { + return err + } + return s.Wait() } -// Shell starts a login shell on the remote host. A Session only -// accepts one call to Exec or Shell. +// Shell starts a login shell on the remote host. A Session only +// accepts one call to Run, Start or Shell. func (s *Session) Shell() error { if s.started { - return errors.New("session already started") + return errors.New("ssh: session already started") } - s.started = true - - return s.sendChanReq(channelRequestMsg{ - PeersId: s.id, + req := channelRequestMsg{ + PeersId: s.peersId, Request: "shell", WantReply: true, + } + if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil { + return err + } + if err := s.waitForResponse(); err != nil { + return fmt.Errorf("ssh: cound not execute shell: %v", err) + } + return s.start() +} + +func (s *Session) waitForResponse() error { + msg := <-s.msg + switch msg.(type) { + case *channelRequestSuccessMsg: + return nil + case *channelRequestFailureMsg: + return errors.New("request failed") + } + return fmt.Errorf("unknown packet %T received: %v", msg, msg) +} + +func (s *Session) start() error { + s.started = true + + type F func(*Session) error + for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} { + if err := setupFd(s); err != nil { + return err + } + } + + s.errch = make(chan error, len(s.copyFuncs)) + for _, fn := range s.copyFuncs { + go func(fn func() error) { + s.errch <- fn() + }(fn) + } + return nil +} + +// Wait waits for the remote command to exit. +func (s *Session) Wait() error { + if !s.started { + return errors.New("ssh: session not started") + } + waitErr := s.wait() + + var copyError error + for _ = range s.copyFuncs { + if err := <-s.errch; err != nil && copyError == nil { + copyError = err + } + } + for _, fd := range s.closeAfterWait { + fd.Close() + } + if waitErr != nil { + return waitErr + } + return copyError +} + +func (s *Session) wait() error { + for { + switch msg := (<-s.msg).(type) { + case *channelRequestMsg: + // TODO(dfc) improve this behavior to match os.Waitmsg + switch msg.Request { + case "exit-status": + d := msg.RequestSpecificData + status := int(d[0])<<24 | int(d[1])<<16 | int(d[2])<<8 | int(d[3]) + if status > 0 { + return fmt.Errorf("remote process exited with %d", status) + } + return nil + case "exit-signal": + // TODO(dfc) make a more readable error message + return fmt.Errorf("%v", msg.RequestSpecificData) + default: + return fmt.Errorf("wait: unexpected channel request: %v", msg) + } + default: + return fmt.Errorf("wait: unexpected packet %T received: %v", msg, msg) + } + } + panic("unreachable") +} + +func (s *Session) stdin() error { + if s.Stdin == nil { + s.Stdin = new(bytes.Buffer) + } + s.copyFuncs = append(s.copyFuncs, func() error { + w := &chanWriter{ + packetWriter: s, + peersId: s.peersId, + win: s.win, + } + _, err := io.Copy(w, s.Stdin) + if err1 := w.Close(); err == nil { + err = err1 + } + return err }) + return nil } +func (s *Session) stdout() error { + if s.Stdout == nil { + s.Stdout = ioutil.Discard + } + s.copyFuncs = append(s.copyFuncs, func() error { + r := &chanReader{ + packetWriter: s, + peersId: s.peersId, + data: s.data, + } + _, err := io.Copy(s.Stdout, r) + return err + }) + return nil +} + +func (s *Session) stderr() error { + if s.Stderr == nil { + s.Stderr = ioutil.Discard + } + s.copyFuncs = append(s.copyFuncs, func() error { + r := &chanReader{ + packetWriter: s, + peersId: s.peersId, + data: s.dataExt, + } + _, err := io.Copy(s.Stderr, r) + return err + }) + return nil +} + +// StdinPipe returns a pipe that will be connected to the +// remote command's standard input when the command starts. +func (s *Session) StdinPipe() (io.WriteCloser, error) { + if s.Stdin != nil { + return nil, errors.New("ssh: Stdin already set") + } + if s.started { + return nil, errors.New("ssh: StdinPipe after process started") + } + pr, pw := io.Pipe() + s.Stdin = pr + s.closeAfterWait = append(s.closeAfterWait, pr) + return pw, nil +} + +// StdoutPipe returns a pipe that will be connected to the +// remote command's standard output when the command starts. +// There is a fixed amount of buffering that is shared between +// stdout and stderr streams. If the StdoutPipe reader is +// not serviced fast enought it may eventually cause the +// remote command to block. +func (s *Session) StdoutPipe() (io.ReadCloser, error) { + if s.Stdout != nil { + return nil, errors.New("ssh: Stdout already set") + } + if s.started { + return nil, errors.New("ssh: StdoutPipe after process started") + } + pr, pw := io.Pipe() + s.Stdout = pw + s.closeAfterWait = append(s.closeAfterWait, pw) + return pr, nil +} + +// StderrPipe returns a pipe that will be connected to the +// remote command's standard error when the command starts. +// There is a fixed amount of buffering that is shared between +// stdout and stderr streams. If the StderrPipe reader is +// not serviced fast enought it may eventually cause the +// remote command to block. +func (s *Session) StderrPipe() (io.ReadCloser, error) { + if s.Stderr != nil { + return nil, errors.New("ssh: Stderr already set") + } + if s.started { + return nil, errors.New("ssh: StderrPipe after process started") + } + pr, pw := io.Pipe() + s.Stderr = pw + s.closeAfterWait = append(s.closeAfterWait, pw) + return pr, nil +} + +// TODO(dfc) add Output and CombinedOutput helpers + // NewSession returns a new interactive session on the remote host. func (c *ClientConn) NewSession() (*Session, error) { - ch, err := c.openChan("session") - if err != nil { + ch := c.newChan(c.transport) + if err := c.writePacket(marshal(msgChannelOpen, channelOpenMsg{ + ChanType: "session", + PeersId: ch.id, + PeersWindow: 1 << 14, + MaxPacketSize: 1 << 15, // RFC 4253 6.1 + })); err != nil { + c.chanlist.remove(ch.id) 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 + // wait for response + msg := <-ch.msg + switch msg := msg.(type) { + case *channelOpenConfirmMsg: + ch.peersId = msg.MyId + ch.win <- int(msg.MyWindow) + return &Session{ + clientChan: ch, + }, nil + case *channelOpenFailureMsg: + c.chanlist.remove(ch.id) + return nil, fmt.Errorf("ssh: channel open failed: %s", msg.Message) + } + c.chanlist.remove(ch.id) + return nil, fmt.Errorf("ssh: unexpected message %T: %v", msg, msg) } diff --git a/libgo/go/exp/ssh/session_test.go b/libgo/go/exp/ssh/session_test.go new file mode 100644 index 0000000..4be7746 --- /dev/null +++ b/libgo/go/exp/ssh/session_test.go @@ -0,0 +1,149 @@ +// 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 tests. + +import ( + "bytes" + "io" + "testing" +) + +// dial constructs a new test server and returns a *ClientConn. +func dial(t *testing.T) *ClientConn { + pw := password("tiger") + serverConfig.PasswordCallback = func(user, pass string) bool { + return user == "testuser" && pass == string(pw) + } + serverConfig.PubKeyCallback = nil + + l, err := Listen("tcp", "127.0.0.1:0", serverConfig) + if err != nil { + t.Fatalf("unable to listen: %s", err) + } + go func() { + defer l.Close() + conn, err := l.Accept() + if err != nil { + t.Errorf("Unable to accept: %v", err) + return + } + defer conn.Close() + if err := conn.Handshake(); err != nil { + t.Errorf("Unable to handshake: %v", err) + return + } + for { + ch, err := conn.Accept() + if err == io.EOF { + return + } + if err != nil { + t.Errorf("Unable to accept incoming channel request: %v", err) + return + } + if ch.ChannelType() != "session" { + ch.Reject(UnknownChannelType, "unknown channel type") + continue + } + ch.Accept() + go func() { + defer ch.Close() + // this string is returned to stdout + shell := NewServerShell(ch, "golang") + shell.ReadLine() + type exitMsg struct { + PeersId uint32 + Request string + WantReply bool + Status uint32 + } + // TODO(dfc) casting to the concrete type should not be + // necessary to send a packet. + msg := exitMsg{ + PeersId: ch.(*channel).theirId, + Request: "exit-status", + WantReply: false, + Status: 0, + } + ch.(*channel).serverConn.writePacket(marshal(msgChannelRequest, msg)) + }() + } + t.Log("done") + }() + + config := &ClientConfig{ + User: "testuser", + Auth: []ClientAuth{ + ClientAuthPassword(pw), + }, + } + + c, err := Dial("tcp", l.Addr().String(), config) + if err != nil { + t.Fatalf("unable to dial remote side: %s", err) + } + return c +} + +// Test a simple string is returned to session.Stdout. +func TestSessionShell(t *testing.T) { + conn := dial(t) + defer conn.Close() + session, err := conn.NewSession() + if err != nil { + t.Fatalf("Unable to request new session: %s", err) + } + defer session.Close() + stdout := new(bytes.Buffer) + session.Stdout = stdout + if err := session.Shell(); err != nil { + t.Fatalf("Unable to execute command: %s", err) + } + if err := session.Wait(); err != nil { + t.Fatalf("Remote command did not exit cleanly: %s", err) + } + actual := stdout.String() + if actual != "golang" { + t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual) + } +} + +// TODO(dfc) add support for Std{in,err}Pipe when the Server supports it. + +// Test a simple string is returned via StdoutPipe. +func TestSessionStdoutPipe(t *testing.T) { + conn := dial(t) + defer conn.Close() + session, err := conn.NewSession() + if err != nil { + t.Fatalf("Unable to request new session: %s", err) + } + defer session.Close() + stdout, err := session.StdoutPipe() + if err != nil { + t.Fatalf("Unable to request StdoutPipe(): %v", err) + } + var buf bytes.Buffer + if err := session.Shell(); err != nil { + t.Fatalf("Unable to execute command: %s", err) + } + done := make(chan bool, 1) + go func() { + if _, err := io.Copy(&buf, stdout); err != nil { + t.Errorf("Copy of stdout failed: %v", err) + } + done <- true + }() + if err := session.Wait(); err != nil { + t.Fatalf("Remote command did not exit cleanly: %s", err) + } + <-done + actual := buf.String() + if actual != "golang" { + t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual) + } +} diff --git a/libgo/go/exp/ssh/tcpip.go b/libgo/go/exp/ssh/tcpip.go index 859dedc..f3bbac5 100644 --- a/libgo/go/exp/ssh/tcpip.go +++ b/libgo/go/exp/ssh/tcpip.go @@ -86,12 +86,12 @@ func (c *ClientConn) dial(laddr string, lport int, raddr string, rport int) (*tc clientChan: ch, Reader: &chanReader{ packetWriter: ch, - id: ch.id, + peersId: ch.peersId, data: ch.data, }, Writer: &chanWriter{ packetWriter: ch, - id: ch.id, + peersId: ch.peersId, win: ch.win, }, }, nil diff --git a/libgo/go/exp/ssh/tcpip_func_test.go b/libgo/go/exp/ssh/tcpip_func_test.go new file mode 100644 index 0000000..2612972 --- /dev/null +++ b/libgo/go/exp/ssh/tcpip_func_test.go @@ -0,0 +1,59 @@ +// 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 + +// direct-tcpip functional tests + +import ( + "net" + "net/http" + "testing" +) + +func TestTCPIPHTTP(t *testing.T) { + if *sshuser == "" { + t.Log("ssh.user not defined, skipping test") + return + } + // google.com will generate at least one redirect, possibly three + // depending on your location. + doTest(t, "http://google.com") +} + +func TestTCPIPHTTPS(t *testing.T) { + if *sshuser == "" { + t.Log("ssh.user not defined, skipping test") + return + } + doTest(t, "https://encrypted.google.com/") +} + +func doTest(t *testing.T, url string) { + config := &ClientConfig{ + User: *sshuser, + Auth: []ClientAuth{ + ClientAuthPassword(password(*sshpass)), + }, + } + conn, err := Dial("tcp", "localhost:22", config) + if err != nil { + t.Fatalf("Unable to connect: %s", err) + } + defer conn.Close() + tr := &http.Transport{ + Dial: func(n, addr string) (net.Conn, error) { + return conn.Dial(n, addr) + }, + } + client := &http.Client{ + Transport: tr, + } + resp, err := client.Get(url) + if err != nil { + t.Fatalf("unable to proxy: %s", err) + } + // got a body without error + t.Log(resp) +} diff --git a/libgo/go/exp/ssh/transport.go b/libgo/go/exp/ssh/transport.go index b8cb2c3..bcd073e 100644 --- a/libgo/go/exp/ssh/transport.go +++ b/libgo/go/exp/ssh/transport.go @@ -123,7 +123,7 @@ func (r *reader) readOnePacket() ([]byte, error) { if r.mac != nil { r.mac.Write(packet[:length-1]) - if subtle.ConstantTimeCompare(r.mac.Sum(), mac) != 1 { + if subtle.ConstantTimeCompare(r.mac.Sum(nil), mac) != 1 { return nil, errors.New("ssh: MAC failure") } } @@ -201,7 +201,7 @@ func (w *writer) writePacket(packet []byte) error { } if w.mac != nil { - if _, err := w.Write(w.mac.Sum()); err != nil { + if _, err := w.Write(w.mac.Sum(nil)); err != nil { return err } } @@ -297,7 +297,7 @@ func generateKeyMaterial(out, tag []byte, K, H, sessionId []byte, h hash.Hash) { h.Write(digestsSoFar) } - digest := h.Sum() + digest := h.Sum(nil) n := copy(out, digest) out = out[n:] if len(out) > 0 { @@ -317,9 +317,9 @@ func (t truncatingMAC) Write(data []byte) (int, error) { return t.hmac.Write(data) } -func (t truncatingMAC) Sum() []byte { - digest := t.hmac.Sum() - return digest[:t.length] +func (t truncatingMAC) Sum(in []byte) []byte { + out := t.hmac.Sum(in) + return out[:len(in)+t.length] } func (t truncatingMAC) Reset() { |