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