132 lines
3.7 KiB
Go
132 lines
3.7 KiB
Go
// 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"
|
|
"errors"
|
|
"io"
|
|
)
|
|
|
|
// 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) 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) 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) error {
|
|
if s.started {
|
|
return errors.New("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() error {
|
|
if s.started {
|
|
return errors.New("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, 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
|
|
}
|