mirror of
https://github.com/golang/go
synced 2024-11-20 10:34:42 -07:00
exp/ssh: introduce Session to replace Cmd for interactive commands
This CL replaces the Cmd type with a Session type representing interactive channels. This lays the foundation for supporting other kinds of channels like direct-tcpip or x11. client.go: * replace chanlist map with slice. * generalize stdout and stderr into a single type. * unexport ClientChan to clientChan. doc.go: * update ServerConfig/ServerConn documentation. * update Client example for Session. message.go: * make channelExtendedData more like channelData. session.go: * added Session which replaces Cmd. R=agl, rsc, n13m3y3r, gustavo CC=golang-dev https://golang.org/cl/5302054
This commit is contained in:
parent
2f3f3aa2ed
commit
5791233461
@ -13,5 +13,6 @@ GOFILES=\
|
|||||||
transport.go\
|
transport.go\
|
||||||
server.go\
|
server.go\
|
||||||
server_shell.go\
|
server_shell.go\
|
||||||
|
session.go\
|
||||||
|
|
||||||
include ../../../Make.pkg
|
include ../../../Make.pkg
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
"big"
|
"big"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@ -31,10 +30,6 @@ func Client(c net.Conn, config *ClientConfig) (*ClientConn, os.Error) {
|
|||||||
conn := &ClientConn{
|
conn := &ClientConn{
|
||||||
transport: newTransport(c, config.rand()),
|
transport: newTransport(c, config.rand()),
|
||||||
config: config,
|
config: config,
|
||||||
chanlist: chanlist{
|
|
||||||
Mutex: new(sync.Mutex),
|
|
||||||
chans: make(map[uint32]*ClientChan),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
if err := conn.handshake(); err != nil {
|
if err := conn.handshake(); err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
@ -233,18 +228,17 @@ func (c *ClientConn) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handsha
|
|||||||
return H, K, nil
|
return H, K, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenChan opens a new client channel. The most common session type is "session".
|
// 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.
|
// The full set of valid session types are listed in RFC 4250 4.9.1.
|
||||||
func (c *ClientConn) OpenChan(typ string) (*ClientChan, os.Error) {
|
func (c *ClientConn) openChan(typ string) (*clientChan, os.Error) {
|
||||||
ch, id := c.newChan(c.transport)
|
ch := c.newChan(c.transport)
|
||||||
if err := c.writePacket(marshal(msgChannelOpen, channelOpenMsg{
|
if err := c.writePacket(marshal(msgChannelOpen, channelOpenMsg{
|
||||||
ChanType: typ,
|
ChanType: typ,
|
||||||
PeersId: id,
|
PeersId: ch.id,
|
||||||
PeersWindow: 8192,
|
PeersWindow: 1 << 14,
|
||||||
MaxPacketSize: 16384,
|
MaxPacketSize: 1 << 15, // RFC 4253 6.1
|
||||||
})); err != nil {
|
})); err != nil {
|
||||||
// remove channel reference
|
c.chanlist.remove(ch.id)
|
||||||
c.chanlist.remove(id)
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// wait for response
|
// wait for response
|
||||||
@ -252,10 +246,10 @@ func (c *ClientConn) OpenChan(typ string) (*ClientChan, os.Error) {
|
|||||||
case *channelOpenConfirmMsg:
|
case *channelOpenConfirmMsg:
|
||||||
ch.peersId = msg.MyId
|
ch.peersId = msg.MyId
|
||||||
case *channelOpenFailureMsg:
|
case *channelOpenFailureMsg:
|
||||||
c.chanlist.remove(id)
|
c.chanlist.remove(ch.id)
|
||||||
return nil, os.NewError(msg.Message)
|
return nil, os.NewError(msg.Message)
|
||||||
default:
|
default:
|
||||||
c.chanlist.remove(id)
|
c.chanlist.remove(ch.id)
|
||||||
return nil, os.NewError("Unexpected packet")
|
return nil, os.NewError("Unexpected packet")
|
||||||
}
|
}
|
||||||
return ch, nil
|
return ch, nil
|
||||||
@ -271,6 +265,10 @@ func (c *ClientConn) mainLoop() {
|
|||||||
c.Close()
|
c.Close()
|
||||||
return
|
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) {
|
switch msg := decode(packet).(type) {
|
||||||
case *channelOpenMsg:
|
case *channelOpenMsg:
|
||||||
c.getChan(msg.PeersId).msg <- msg
|
c.getChan(msg.PeersId).msg <- msg
|
||||||
@ -280,9 +278,9 @@ func (c *ClientConn) mainLoop() {
|
|||||||
c.getChan(msg.PeersId).msg <- msg
|
c.getChan(msg.PeersId).msg <- msg
|
||||||
case *channelCloseMsg:
|
case *channelCloseMsg:
|
||||||
ch := c.getChan(msg.PeersId)
|
ch := c.getChan(msg.PeersId)
|
||||||
close(ch.stdinWriter.win)
|
close(ch.win)
|
||||||
close(ch.stdoutReader.data)
|
close(ch.data)
|
||||||
close(ch.stderrReader.dataExt)
|
close(ch.dataExt)
|
||||||
c.chanlist.remove(msg.PeersId)
|
c.chanlist.remove(msg.PeersId)
|
||||||
case *channelEOFMsg:
|
case *channelEOFMsg:
|
||||||
c.getChan(msg.PeersId).msg <- msg
|
c.getChan(msg.PeersId).msg <- msg
|
||||||
@ -293,13 +291,16 @@ func (c *ClientConn) mainLoop() {
|
|||||||
case *channelRequestMsg:
|
case *channelRequestMsg:
|
||||||
c.getChan(msg.PeersId).msg <- msg
|
c.getChan(msg.PeersId).msg <- msg
|
||||||
case *windowAdjustMsg:
|
case *windowAdjustMsg:
|
||||||
c.getChan(msg.PeersId).stdinWriter.win <- int(msg.AdditionalBytes)
|
c.getChan(msg.PeersId).win <- int(msg.AdditionalBytes)
|
||||||
case *channelData:
|
case *channelData:
|
||||||
c.getChan(msg.PeersId).stdoutReader.data <- msg.Payload
|
c.getChan(msg.PeersId).data <- msg.Payload
|
||||||
case *channelExtendedData:
|
case *channelExtendedData:
|
||||||
// TODO(dfc) should this send be non blocking. RFC 4254 5.2 suggests
|
// RFC 4254 5.2 defines data_type_code 1 to be data destined
|
||||||
// ext data consumes window size, does that need to be handled as well ?
|
// for stderr on interactive sessions. Other data types are
|
||||||
c.getChan(msg.PeersId).stderrReader.dataExt <- msg.Data
|
// silently discarded.
|
||||||
|
if msg.Datatype == 1 {
|
||||||
|
c.getChan(msg.PeersId).dataExt <- msg.Payload
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
fmt.Printf("mainLoop: unhandled %#v\n", msg)
|
fmt.Printf("mainLoop: unhandled %#v\n", msg)
|
||||||
}
|
}
|
||||||
@ -338,207 +339,95 @@ func (c *ClientConfig) rand() io.Reader {
|
|||||||
return c.Rand
|
return c.Rand
|
||||||
}
|
}
|
||||||
|
|
||||||
// A ClientChan represents a single RFC 4254 channel that is multiplexed
|
// A clientChan represents a single RFC 4254 channel that is multiplexed
|
||||||
// over a single SSH connection.
|
// over a single SSH connection.
|
||||||
type ClientChan struct {
|
type clientChan struct {
|
||||||
packetWriter
|
packetWriter
|
||||||
*stdinWriter // used by Exec and Shell
|
id, peersId uint32
|
||||||
*stdoutReader // used by Exec and Shell
|
data chan []byte // receives the payload of channelData messages
|
||||||
*stderrReader // used by Exec and Shell
|
dataExt chan []byte // receives the payload of channelExtendedData messages
|
||||||
id, peersId uint32
|
win chan int // receives window adjustments
|
||||||
msg chan interface{} // incoming messages
|
msg chan interface{} // incoming messages
|
||||||
}
|
}
|
||||||
|
|
||||||
func newClientChan(t *transport, id uint32) *ClientChan {
|
func newClientChan(t *transport, id uint32) *clientChan {
|
||||||
// TODO(DFC) allocating stdin/out/err on ClientChan creation is
|
return &clientChan{
|
||||||
// wasteful, but ClientConn.mainLoop() needs a way of finding
|
|
||||||
// those channels before Exec/Shell is called because the remote
|
|
||||||
// may send window adjustments at any time.
|
|
||||||
return &ClientChan{
|
|
||||||
packetWriter: t,
|
packetWriter: t,
|
||||||
stdinWriter: &stdinWriter{
|
id: id,
|
||||||
packetWriter: t,
|
data: make(chan []byte, 16),
|
||||||
id: id,
|
dataExt: make(chan []byte, 16),
|
||||||
win: make(chan int, 16),
|
win: make(chan int, 16),
|
||||||
},
|
msg: make(chan interface{}, 16),
|
||||||
stdoutReader: &stdoutReader{
|
|
||||||
packetWriter: t,
|
|
||||||
id: id,
|
|
||||||
win: 8192,
|
|
||||||
data: make(chan []byte, 16),
|
|
||||||
},
|
|
||||||
stderrReader: &stderrReader{
|
|
||||||
dataExt: make(chan string, 16),
|
|
||||||
},
|
|
||||||
id: id,
|
|
||||||
msg: make(chan interface{}, 16),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the channel. This does not close the underlying connection.
|
// Close closes the channel. This does not close the underlying connection.
|
||||||
func (c *ClientChan) Close() os.Error {
|
func (c *clientChan) Close() os.Error {
|
||||||
return c.writePacket(marshal(msgChannelClose, channelCloseMsg{
|
return c.writePacket(marshal(msgChannelClose, channelCloseMsg{
|
||||||
PeersId: c.id,
|
PeersId: c.id,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setenv sets an environment variable that will be applied to any
|
func (c *clientChan) sendChanReq(req channelRequestMsg) os.Error {
|
||||||
// command executed by Shell or Exec.
|
|
||||||
func (c *ClientChan) Setenv(name, value string) os.Error {
|
|
||||||
namLen := stringLength([]byte(name))
|
|
||||||
valLen := stringLength([]byte(value))
|
|
||||||
payload := make([]byte, namLen+valLen)
|
|
||||||
marshalString(payload[:namLen], []byte(name))
|
|
||||||
marshalString(payload[namLen:], []byte(value))
|
|
||||||
|
|
||||||
return c.sendChanReq(channelRequestMsg{
|
|
||||||
PeersId: c.id,
|
|
||||||
Request: "env",
|
|
||||||
WantReply: true,
|
|
||||||
RequestSpecificData: payload,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientChan) sendChanReq(req channelRequestMsg) os.Error {
|
|
||||||
if err := c.writePacket(marshal(msgChannelRequest, req)); err != nil {
|
if err := c.writePacket(marshal(msgChannelRequest, req)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for {
|
msg := <-c.msg
|
||||||
switch msg := (<-c.msg).(type) {
|
if _, ok := msg.(*channelRequestSuccessMsg); ok {
|
||||||
case *channelRequestSuccessMsg:
|
return nil
|
||||||
return nil
|
|
||||||
case *channelRequestFailureMsg:
|
|
||||||
return os.NewError(req.Request)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("%#v", msg)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
panic("unreachable")
|
return fmt.Errorf("failed to complete request: %s, %#v", req.Request, msg)
|
||||||
}
|
|
||||||
|
|
||||||
// An empty mode list (a string of 1 character, opcode 0), see RFC 4254 Section 8.
|
|
||||||
var emptyModeList = []byte{0, 0, 0, 1, 0}
|
|
||||||
|
|
||||||
// RequstPty requests a pty to be allocated on the remote side of this channel.
|
|
||||||
func (c *ClientChan) 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 c.sendChanReq(channelRequestMsg{
|
|
||||||
PeersId: c.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.
|
|
||||||
func (c *ClientChan) Exec(cmd string) (*Cmd, os.Error) {
|
|
||||||
cmdLen := stringLength([]byte(cmd))
|
|
||||||
payload := make([]byte, cmdLen)
|
|
||||||
marshalString(payload, []byte(cmd))
|
|
||||||
err := c.sendChanReq(channelRequestMsg{
|
|
||||||
PeersId: c.id,
|
|
||||||
Request: "exec",
|
|
||||||
WantReply: true,
|
|
||||||
RequestSpecificData: payload,
|
|
||||||
})
|
|
||||||
return &Cmd{
|
|
||||||
c.stdinWriter,
|
|
||||||
c.stdoutReader,
|
|
||||||
c.stderrReader,
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shell starts a login shell on the remote host.
|
|
||||||
func (c *ClientChan) Shell() (*Cmd, os.Error) {
|
|
||||||
err := c.sendChanReq(channelRequestMsg{
|
|
||||||
PeersId: c.id,
|
|
||||||
Request: "shell",
|
|
||||||
WantReply: true,
|
|
||||||
})
|
|
||||||
return &Cmd{
|
|
||||||
c.stdinWriter,
|
|
||||||
c.stdoutReader,
|
|
||||||
c.stderrReader,
|
|
||||||
}, err
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thread safe channel list.
|
// Thread safe channel list.
|
||||||
type chanlist struct {
|
type chanlist struct {
|
||||||
*sync.Mutex
|
// protects concurrent access to chans
|
||||||
// TODO(dfc) should could be converted to a slice
|
sync.Mutex
|
||||||
chans map[uint32]*ClientChan
|
// 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.
|
// Allocate a new ClientChan with the next avail local id.
|
||||||
func (c *chanlist) newChan(t *transport) (*ClientChan, uint32) {
|
func (c *chanlist) newChan(t *transport) *clientChan {
|
||||||
c.Lock()
|
c.Lock()
|
||||||
defer c.Unlock()
|
defer c.Unlock()
|
||||||
|
for i := range c.chans {
|
||||||
for i := uint32(0); i < 1<<31; i++ {
|
if c.chans[i] == nil {
|
||||||
if _, ok := c.chans[i]; !ok {
|
ch := newClientChan(t, uint32(i))
|
||||||
ch := newClientChan(t, i)
|
|
||||||
c.chans[i] = ch
|
c.chans[i] = ch
|
||||||
return ch, uint32(i)
|
return ch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panic("unable to find free channel")
|
i := len(c.chans)
|
||||||
|
ch := newClientChan(t, uint32(i))
|
||||||
|
c.chans = append(c.chans, ch)
|
||||||
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *chanlist) getChan(id uint32) *ClientChan {
|
func (c *chanlist) getChan(id uint32) *clientChan {
|
||||||
c.Lock()
|
c.Lock()
|
||||||
defer c.Unlock()
|
defer c.Unlock()
|
||||||
return c.chans[id]
|
return c.chans[int(id)]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *chanlist) remove(id uint32) {
|
func (c *chanlist) remove(id uint32) {
|
||||||
c.Lock()
|
c.Lock()
|
||||||
defer c.Unlock()
|
defer c.Unlock()
|
||||||
delete(c.chans, id)
|
c.chans[int(id)] = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Cmd represents a connection to a remote command or shell
|
// A chanWriter represents the stdin of a remote process.
|
||||||
// Closing Cmd.Stdin will be observed by the remote process.
|
type chanWriter struct {
|
||||||
type Cmd struct {
|
|
||||||
// Writes to Stdin are made available to the command's standard input.
|
|
||||||
// Closing Stdin causes the command to observe an EOF on its standard input.
|
|
||||||
Stdin io.WriteCloser
|
|
||||||
|
|
||||||
// Reads from Stdout consume the command's standard output.
|
|
||||||
// There is a fixed amount of buffering of the command's standard output.
|
|
||||||
// Failing to read from Stdout will eventually cause the command to block
|
|
||||||
// when writing to its standard output. Closing Stdout unblocks any
|
|
||||||
// such writes and makes them return errors.
|
|
||||||
Stdout io.ReadCloser
|
|
||||||
|
|
||||||
// Reads from Stderr consume the command's standard error.
|
|
||||||
// The SSH protocol assumes it can always send standard error;
|
|
||||||
// the command will never block writing to its standard error.
|
|
||||||
// However, failure to read from Stderr will eventually cause the
|
|
||||||
// SSH protocol to jam, so it is important to arrange for reading
|
|
||||||
// from Stderr, even if by
|
|
||||||
// go io.Copy(ioutil.Discard, cmd.Stderr)
|
|
||||||
Stderr io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
// A stdinWriter represents the stdin of a remote process.
|
|
||||||
type stdinWriter struct {
|
|
||||||
win chan int // receives window adjustments
|
win chan int // receives window adjustments
|
||||||
id uint32
|
id uint32 // this channel's id
|
||||||
rwin int // current rwin size
|
rwin int // current rwin size
|
||||||
packetWriter // for sending channelDataMsg
|
packetWriter // for sending channelDataMsg
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write writes data to the remote process's standard input.
|
// Write writes data to the remote process's standard input.
|
||||||
func (w *stdinWriter) Write(data []byte) (n int, err os.Error) {
|
func (w *chanWriter) Write(data []byte) (n int, err os.Error) {
|
||||||
for {
|
for {
|
||||||
if w.rwin == 0 {
|
if w.rwin == 0 {
|
||||||
win, ok := <-w.win
|
win, ok := <-w.win
|
||||||
@ -560,69 +449,42 @@ func (w *stdinWriter) Write(data []byte) (n int, err os.Error) {
|
|||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *stdinWriter) Close() os.Error {
|
func (w *chanWriter) Close() os.Error {
|
||||||
return w.writePacket(marshal(msgChannelEOF, channelEOFMsg{w.id}))
|
return w.writePacket(marshal(msgChannelEOF, channelEOFMsg{w.id}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// A stdoutReader represents the stdout of a remote process.
|
// A chanReader represents stdout or stderr of a remote process.
|
||||||
type stdoutReader struct {
|
type chanReader struct {
|
||||||
// TODO(dfc) a fixed size channel may not be the right data structure.
|
// TODO(dfc) a fixed size channel may not be the right data structure.
|
||||||
// If writes to this channel block, they will block mainLoop, making
|
// If writes to this channel block, they will block mainLoop, making
|
||||||
// it unable to receive new messages from the remote side.
|
// it unable to receive new messages from the remote side.
|
||||||
data chan []byte // receives data from remote
|
data chan []byte // receives data from remote
|
||||||
id uint32
|
id uint32
|
||||||
win int // current win size
|
packetWriter // for sending windowAdjustMsg
|
||||||
packetWriter // for sending windowAdjustMsg
|
|
||||||
buf []byte
|
buf []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read reads data from the remote process's standard output.
|
// Read reads data from the remote process's stdout or stderr.
|
||||||
func (r *stdoutReader) Read(data []byte) (int, os.Error) {
|
func (r *chanReader) Read(data []byte) (int, os.Error) {
|
||||||
var ok bool
|
var ok bool
|
||||||
for {
|
for {
|
||||||
if len(r.buf) > 0 {
|
if len(r.buf) > 0 {
|
||||||
n := copy(data, r.buf)
|
n := copy(data, r.buf)
|
||||||
r.buf = r.buf[n:]
|
r.buf = r.buf[n:]
|
||||||
r.win += n
|
|
||||||
msg := windowAdjustMsg{
|
msg := windowAdjustMsg{
|
||||||
PeersId: r.id,
|
PeersId: r.id,
|
||||||
AdditionalBytes: uint32(n),
|
AdditionalBytes: uint32(n),
|
||||||
}
|
}
|
||||||
err := r.writePacket(marshal(msgChannelWindowAdjust, msg))
|
return n, r.writePacket(marshal(msgChannelWindowAdjust, msg))
|
||||||
return n, err
|
|
||||||
}
|
}
|
||||||
r.buf, ok = <-r.data
|
r.buf, ok = <-r.data
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0, os.EOF
|
return 0, os.EOF
|
||||||
}
|
}
|
||||||
r.win -= len(r.buf)
|
|
||||||
}
|
}
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *stdoutReader) Close() os.Error {
|
func (r *chanReader) Close() os.Error {
|
||||||
return r.writePacket(marshal(msgChannelEOF, channelEOFMsg{r.id}))
|
return r.writePacket(marshal(msgChannelEOF, channelEOFMsg{r.id}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// A stderrReader represents the stderr of a remote process.
|
|
||||||
type stderrReader struct {
|
|
||||||
dataExt chan string // receives dataExt from remote
|
|
||||||
buf []byte // buffer current dataExt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read reads a line of data from the remote process's stderr.
|
|
||||||
func (r *stderrReader) Read(data []byte) (int, os.Error) {
|
|
||||||
for {
|
|
||||||
if len(r.buf) > 0 {
|
|
||||||
n := copy(data, r.buf)
|
|
||||||
r.buf = r.buf[n:]
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
buf, ok := <-r.dataExt
|
|
||||||
if !ok {
|
|
||||||
return 0, os.EOF
|
|
||||||
}
|
|
||||||
r.buf = []byte(buf)
|
|
||||||
}
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
@ -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
|
the multiplexed nature of SSH is exposed to users that wish to support
|
||||||
others.
|
others.
|
||||||
|
|
||||||
An SSH server is represented by a Server, which manages a number of
|
An SSH server is represented by a ServerConfig, which holds certificate
|
||||||
ServerConnections and handles authentication.
|
details and handles authentication of ServerConns.
|
||||||
|
|
||||||
var s Server
|
config := new(ServerConfig)
|
||||||
s.PubKeyCallback = pubKeyAuth
|
config.PubKeyCallback = pubKeyAuth
|
||||||
s.PasswordCallback = passwordAuth
|
config.PasswordCallback = passwordAuth
|
||||||
|
|
||||||
pemBytes, err := ioutil.ReadFile("id_rsa")
|
pemBytes, err := ioutil.ReadFile("id_rsa")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("Failed to load private key")
|
panic("Failed to load private key")
|
||||||
}
|
}
|
||||||
err = s.SetRSAPrivateKey(pemBytes)
|
err = config.SetRSAPrivateKey(pemBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("Failed to parse private key")
|
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
|
listener := Listen("tcp", "0.0.0.0:2022", config)
|
||||||
sConn.Server = &s
|
sConn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to accept incoming connection")
|
||||||
|
}
|
||||||
err = sConn.Handshake(conn)
|
err = sConn.Handshake(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("failed to handshake")
|
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:
|
An SSH connection multiplexes several channels, which must be accepted themselves:
|
||||||
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
channel, err := sConn.Accept()
|
channel, err := sConn.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -85,17 +87,19 @@ authentication method is supported.
|
|||||||
}
|
}
|
||||||
client, err := Dial("yourserver.com:22", config)
|
client, err := Dial("yourserver.com:22", config)
|
||||||
|
|
||||||
Each ClientConn can support multiple channels, represented by ClientChan. Each
|
Each ClientConn can support multiple interactive sessions, represented by a Session.
|
||||||
channel should be of a type specified in rfc4250, 4.9.1.
|
|
||||||
|
|
||||||
ch, err := client.OpenChan("session")
|
session, err := client.NewSession()
|
||||||
|
|
||||||
Once the ClientChan is opened, you can execute a single command on the remote side
|
Once a Session is created, you can execute a single command on the remote side
|
||||||
using the Exec method.
|
using the Exec method.
|
||||||
|
|
||||||
cmd, err := ch.Exec("/usr/bin/whoami")
|
if err := session.Exec("/usr/bin/whoami"); err != nil {
|
||||||
reader := bufio.NewReader(cmd.Stdin)
|
panic("Failed to exec: " + err.String())
|
||||||
|
}
|
||||||
|
reader := bufio.NewReader(session.Stdin)
|
||||||
line, _, _ := reader.ReadLine()
|
line, _, _ := reader.ReadLine()
|
||||||
fmt.Println(line)
|
fmt.Println(line)
|
||||||
|
session.Close()
|
||||||
*/
|
*/
|
||||||
package ssh
|
package ssh
|
||||||
|
@ -154,7 +154,7 @@ type channelData struct {
|
|||||||
type channelExtendedData struct {
|
type channelExtendedData struct {
|
||||||
PeersId uint32
|
PeersId uint32
|
||||||
Datatype uint32
|
Datatype uint32
|
||||||
Data string
|
Payload []byte `ssh:"rest"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type channelRequestMsg struct {
|
type channelRequestMsg struct {
|
||||||
|
132
src/pkg/exp/ssh/session.go
Normal file
132
src/pkg/exp/ssh/session.go
Normal file
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user