// 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
	IUTF8         = 42 // RFC 8160
	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.
type Session struct {
	// Stdin specifies the remote process's standard input.
	// If Stdin is nil, the remote process reads from an empty
	// bytes.Buffer.
	Stdin io.Reader

	// 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 io.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
	stdinpipe, stdoutpipe, stderrpipe bool

	// 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.
	stdinPipeWriter io.WriteCloser

	exitStatus chan error
}

// 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.
type setenvRequest struct {
	Name  string
	Value string
}

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

// RFC 4254 Section 6.2.
type ptyRequestMsg struct {
	Term     string
	Columns  uint32
	Rows     uint32
	Width    uint32
	Height   uint32
	Modelist string
}

// 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.
type subsystemRequestMsg struct {
	Subsystem string
}

// 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.
type ptyWindowChangeMsg struct {
	Columns uint32
	Rows    uint32
	Width   uint32
	Height  uint32
}

// 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 {
	 := Waitmsg{status: -1}
	// 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 = .
		default:
			// This handles keepalives and matches
			// OpenSSH's behaviour.
			if .WantReply {
				.Reply(false, nil)
			}
		}
	}
	if .status == 0 {
		return nil
	}
	if .status == -1 {
		// exit-status was never sent from server
		if .signal == "" {
			// 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 = io.Discard
	}
	.copyFuncs = append(.copyFuncs, func() error {
		,  := io.Copy(.Stdout, .ch)
		return 
	})
}

func ( *Session) () {
	if .stderrpipe {
		return
	}
	if .Stderr == nil {
		.Stderr = io.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 newSession( 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.
type Waitmsg struct {
	status int
	signal string
	msg    string
	lang   string
}

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