// 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 }