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 (
	
	
	
	
	
	
	
)

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"
)

var signals = map[Signal]int{
	SIGABRT: 6,
	SIGALRM: 14,
	SIGFPE:  8,
	SIGHUP:  1,
	SIGILL:  4,
	SIGINT:  2,
	SIGKILL: 9,
	SIGPIPE: 13,
	SIGQUIT: 3,
	SIGSEGV: 11,
	SIGTERM: 15,
}

type TerminalModes map[uint8]uint32
POSIX terminal mode flags as listed in RFC 4254 Section 8.
const (
	tty_OP_END    = 0
	VINTR         = 1
	VQUIT         = 2
	VERASE        = 3
	VKILL         = 4
	VEOF          = 5
	VEOL          = 6
	VEOL2         = 7
	VSTART        = 8
	VSTOP         = 9
	VSUSP         = 10
	VDSUSP        = 11
	VREPRINT      = 12
	VWERASE       = 13
	VLNEXT        = 14
	VFLUSH        = 15
	VSWTCH        = 16
	VSTATUS       = 17
	VDISCARD      = 18
	IGNPAR        = 30
	PARMRK        = 31
	INPCK         = 32
	ISTRIP        = 33
	INLCR         = 34
	IGNCR         = 35
	ICRNL         = 36
	IUCLC         = 37
	IXON          = 38
	IXANY         = 39
	IXOFF         = 40
	IMAXBEL       = 41
	ISIG          = 50
	ICANON        = 51
	XCASE         = 52
	ECHO          = 53
	ECHOE         = 54
	ECHOK         = 55
	ECHONL        = 56
	NOFLSH        = 57
	TOSTOP        = 58
	IEXTEN        = 59
	ECHOCTL       = 60
	ECHOKE        = 61
	PENDIN        = 62
	OPOST         = 70
	OLCUC         = 71
	ONLCR         = 72
	OCRNL         = 73
	ONOCR         = 74
	ONLRET        = 75
	CS7           = 90
	CS8           = 91
	PARENB        = 92
	PARODD        = 93
	TTY_OP_ISPEED = 128
	TTY_OP_OSPEED = 129
)
A Session represents a connection to a remote command or shell.
Stdin specifies the remote process's standard input. If Stdin is nil, the remote process reads from an empty bytes.Buffer.
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

	ch        Channel // the channel backing this session
	started   bool    // true once Start, Run or Shell is invoked.
	copyFuncs []func() error
	errors    chan error // one send per copyFunc
true if pipe method is active
stdinPipeWriter is non-nil if StdinPipe has not been called and Stdin was specified by the user; it is the write end of a pipe connecting Session.Stdin to the stdin channel.
SendRequest sends an out-of-band channel request on the SSH channel underlying the session.
func ( *Session) ( string,  bool,  []byte) (bool, error) {
	return .ch.SendRequest(, , )
}

func ( *Session) () error {
	return .ch.Close()
}
RFC 4254 Section 6.4.
Setenv sets an environment variable that will be applied to any command executed by Shell or Run.
func ( *Session) (,  string) error {
	 := setenvRequest{
		Name:  ,
		Value: ,
	}
	,  := .ch.SendRequest("env", true, Marshal(&))
	if  == nil && ! {
		 = errors.New("ssh: setenv failed")
	}
	return 
}
RequestPty requests the association of a pty with the session on the remote host.
func ( *Session) ( string, ,  int,  TerminalModes) error {
	var  []byte
	for ,  := range  {
		 := struct {
			 byte
			 uint32
		}{, }

		 = append(, Marshal(&)...)
	}
	 = append(, tty_OP_END)
	 := ptyRequestMsg{
		Term:     ,
		Columns:  uint32(),
		Rows:     uint32(),
		Width:    uint32( * 8),
		Height:   uint32( * 8),
		Modelist: string(),
	}
	,  := .ch.SendRequest("pty-req", true, Marshal(&))
	if  == nil && ! {
		 = errors.New("ssh: pty-req failed")
	}
	return 
}
RFC 4254 Section 6.5.
RequestSubsystem requests the association of a subsystem with the session on the remote host. A subsystem is a predefined command that runs in the background when the ssh session is initiated
func ( *Session) ( string) error {
	 := subsystemRequestMsg{
		Subsystem: ,
	}
	,  := .ch.SendRequest("subsystem", true, Marshal(&))
	if  == nil && ! {
		 = errors.New("ssh: subsystem request failed")
	}
	return 
}
RFC 4254 Section 6.7.
WindowChange informs the remote host about a terminal window dimension change to h rows and w columns.
func ( *Session) (,  int) error {
	 := ptyWindowChangeMsg{
		Columns: uint32(),
		Rows:    uint32(),
		Width:   uint32( * 8),
		Height:  uint32( * 8),
	}
	,  := .ch.SendRequest("window-change", false, Marshal(&))
	return 
}
RFC 4254 Section 6.9.
type signalMsg struct {
	Signal string
}
Signal sends the given signal to the remote process. sig is one of the SIG* constants.
func ( *Session) ( Signal) error {
	 := signalMsg{
		Signal: string(),
	}

	,  := .ch.SendRequest("signal", false, Marshal(&))
	return 
}
RFC 4254 Section 6.5.
type execMsg struct {
	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 ( *Session) ( string) error {
	if .started {
		return errors.New("ssh: session already started")
	}
	 := execMsg{
		Command: ,
	}

	,  := .ch.SendRequest("exec", true, Marshal(&))
	if  == nil && ! {
		 = fmt.Errorf("ssh: command %v failed", )
	}
	if  != nil {
		return 
	}
	return .start()
}
Run 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, Shell, Output, or CombinedOutput. The returned error is nil if the command runs, has no problems copying stdin, stdout, and stderr, and exits with a zero exit status. If the remote server does not send an exit status, an error of type *ExitMissingError is returned. If the command completes unsuccessfully or is interrupted by a signal, the error is of type *ExitError. Other error types may be returned for I/O problems.
func ( *Session) ( string) error {
	 := .Start()
	if  != nil {
		return 
	}
	return .Wait()
}
Output runs cmd on the remote host and returns its standard output.
func ( *Session) ( string) ([]byte, error) {
	if .Stdout != nil {
		return nil, errors.New("ssh: Stdout already set")
	}
	var  bytes.Buffer
	.Stdout = &
	 := .Run()
	return .Bytes(), 
}

type singleWriter struct {
	b  bytes.Buffer
	mu sync.Mutex
}

func ( *singleWriter) ( []byte) (int, error) {
	.mu.Lock()
	defer .mu.Unlock()
	return .b.Write()
}
CombinedOutput runs cmd on the remote host and returns its combined standard output and standard error.
func ( *Session) ( string) ([]byte, error) {
	if .Stdout != nil {
		return nil, errors.New("ssh: Stdout already set")
	}
	if .Stderr != nil {
		return nil, errors.New("ssh: Stderr already set")
	}
	var  singleWriter
	.Stdout = &
	.Stderr = &
	 := .Run()
	return .b.Bytes(), 
}
Shell starts a login shell on the remote host. A Session only accepts one call to Run, Start, Shell, Output, or CombinedOutput.
func ( *Session) () error {
	if .started {
		return errors.New("ssh: session already started")
	}

	,  := .ch.SendRequest("shell", true, nil)
	if  == nil && ! {
		return errors.New("ssh: could not start shell")
	}
	if  != nil {
		return 
	}
	return .start()
}

func ( *Session) () error {
	.started = true

	type  func(*Session)
	for ,  := range []{(*Session).stdin, (*Session).stdout, (*Session).stderr} {
		()
	}

	.errors = make(chan error, len(.copyFuncs))
	for ,  := range .copyFuncs {
		go func( func() error) {
			.errors <- ()
		}()
	}
	return nil
}
Wait waits for the remote command to exit. The returned error is nil if the command runs, has no problems copying stdin, stdout, and stderr, and exits with a zero exit status. If the remote server does not send an exit status, an error of type *ExitMissingError is returned. If the command completes unsuccessfully or is interrupted by a signal, the error is of type *ExitError. Other error types may be returned for I/O problems.
func ( *Session) () error {
	if !.started {
		return errors.New("ssh: session not started")
	}
	 := <-.exitStatus

	if .stdinPipeWriter != nil {
		.stdinPipeWriter.Close()
	}
	var  error
	for range .copyFuncs {
		if  := <-.errors;  != nil &&  == nil {
			 = 
		}
	}
	if  != nil {
		return 
	}
	return 
}

func ( *Session) ( <-chan *Request) error {
Wait for msg channel to be closed before returning.
	for  := range  {
		switch .Type {
		case "exit-status":
			.status = int(binary.BigEndian.Uint32(.Payload))
		case "exit-signal":
			var  struct {
				     string
				 bool
				      string
				       string
			}
			if  := Unmarshal(.Payload, &);  != nil {
				return 
			}
Must sanitize strings?
			.signal = .
			.msg = .
			.lang = .
This handles keepalives and matches OpenSSH's behaviour.
			if .WantReply {
				.Reply(false, nil)
			}
		}
	}
	if .status == 0 {
		return nil
	}
exit-status was never sent from server
signal was not sent either. RFC 4254 section 6.10 recommends against this behavior, but it is allowed, so we let clients handle it.
			return &ExitMissingError{}
		}
		.status = 128
		if ,  := signals[Signal(.signal)];  {
			.status += signals[Signal(.signal)]
		}
	}

	return &ExitError{}
}
ExitMissingError is returned if a session is torn down cleanly, but the server sends no confirmation of the exit status.
type ExitMissingError struct{}

func ( *ExitMissingError) () string {
	return "wait: remote command exited without exit status or exit signal"
}

func ( *Session) () {
	if .stdinpipe {
		return
	}
	var  io.Reader
	if .Stdin == nil {
		 = new(bytes.Buffer)
	} else {
		,  := io.Pipe()
		go func() {
			,  := io.Copy(, .Stdin)
			.CloseWithError()
		}()
		, .stdinPipeWriter = , 
	}
	.copyFuncs = append(.copyFuncs, func() error {
		,  := io.Copy(.ch, )
		if  := .ch.CloseWrite();  == nil &&  != io.EOF {
			 = 
		}
		return 
	})
}

func ( *Session) () {
	if .stdoutpipe {
		return
	}
	if .Stdout == nil {
		.Stdout = ioutil.Discard
	}
	.copyFuncs = append(.copyFuncs, func() error {
		,  := io.Copy(.Stdout, .ch)
		return 
	})
}

func ( *Session) () {
	if .stderrpipe {
		return
	}
	if .Stderr == nil {
		.Stderr = ioutil.Discard
	}
	.copyFuncs = append(.copyFuncs, func() error {
		,  := io.Copy(.Stderr, .ch.Stderr())
		return 
	})
}
sessionStdin reroutes Close to CloseWrite.
type sessionStdin struct {
	io.Writer
	ch Channel
}

func ( *sessionStdin) () error {
	return .ch.CloseWrite()
}
StdinPipe returns a pipe that will be connected to the remote command's standard input when the command starts.
func ( *Session) () (io.WriteCloser, error) {
	if .Stdin != nil {
		return nil, errors.New("ssh: Stdin already set")
	}
	if .started {
		return nil, errors.New("ssh: StdinPipe after process started")
	}
	.stdinpipe = true
	return &sessionStdin{.ch, .ch}, 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 enough it may eventually cause the remote command to block.
func ( *Session) () (io.Reader, error) {
	if .Stdout != nil {
		return nil, errors.New("ssh: Stdout already set")
	}
	if .started {
		return nil, errors.New("ssh: StdoutPipe after process started")
	}
	.stdoutpipe = true
	return .ch, 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 enough it may eventually cause the remote command to block.
func ( *Session) () (io.Reader, error) {
	if .Stderr != nil {
		return nil, errors.New("ssh: Stderr already set")
	}
	if .started {
		return nil, errors.New("ssh: StderrPipe after process started")
	}
	.stderrpipe = true
	return .ch.Stderr(), nil
}
newSession returns a new interactive session on the remote host.
func ( Channel,  <-chan *Request) (*Session, error) {
	 := &Session{
		ch: ,
	}
	.exitStatus = make(chan error, 1)
	go func() {
		.exitStatus <- .wait()
	}()

	return , nil
}
An ExitError reports unsuccessful completion of a remote command.
type ExitError struct {
	Waitmsg
}

func ( *ExitError) () string {
	return .Waitmsg.String()
}
Waitmsg stores the information about an exited remote command as reported by Wait.
ExitStatus returns the exit status of the remote command.
func ( Waitmsg) () int {
	return .status
}
Signal returns the exit signal of the remote command if it was terminated violently.
func ( Waitmsg) () string {
	return .signal
}
Msg returns the exit message given by the remote command
func ( Waitmsg) () string {
	return .msg
}
Lang returns the language tag. See RFC 3066
func ( Waitmsg) () string {
	return .lang
}

func ( Waitmsg) () string {
	 := fmt.Sprintf("Process exited with status %v", .status)
	if .signal != "" {
		 += fmt.Sprintf(" from signal %v", .signal)
	}
	if .msg != "" {
		 += fmt.Sprintf(". Reason was: %v", .msg)
	}
	return