github.com/glycerine/xcryptossh@v7.0.4+incompatible/session.go (about)

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package ssh
     6  
     7  // Session implements an interactive session described in
     8  // "RFC 4254, section 6".
     9  
    10  import (
    11  	"bytes"
    12  	"encoding/binary"
    13  	"errors"
    14  	"fmt"
    15  	"io"
    16  	"io/ioutil"
    17  	"sync"
    18  )
    19  
    20  type Signal string
    21  
    22  // POSIX signals as listed in RFC 4254 Section 6.10.
    23  const (
    24  	SIGABRT Signal = "ABRT"
    25  	SIGALRM Signal = "ALRM"
    26  	SIGFPE  Signal = "FPE"
    27  	SIGHUP  Signal = "HUP"
    28  	SIGILL  Signal = "ILL"
    29  	SIGINT  Signal = "INT"
    30  	SIGKILL Signal = "KILL"
    31  	SIGPIPE Signal = "PIPE"
    32  	SIGQUIT Signal = "QUIT"
    33  	SIGSEGV Signal = "SEGV"
    34  	SIGTERM Signal = "TERM"
    35  	SIGUSR1 Signal = "USR1"
    36  	SIGUSR2 Signal = "USR2"
    37  )
    38  
    39  var signals = map[Signal]int{
    40  	SIGABRT: 6,
    41  	SIGALRM: 14,
    42  	SIGFPE:  8,
    43  	SIGHUP:  1,
    44  	SIGILL:  4,
    45  	SIGINT:  2,
    46  	SIGKILL: 9,
    47  	SIGPIPE: 13,
    48  	SIGQUIT: 3,
    49  	SIGSEGV: 11,
    50  	SIGTERM: 15,
    51  }
    52  
    53  type TerminalModes map[uint8]uint32
    54  
    55  // POSIX terminal mode flags as listed in RFC 4254 Section 8.
    56  const (
    57  	tty_OP_END    = 0
    58  	VINTR         = 1
    59  	VQUIT         = 2
    60  	VERASE        = 3
    61  	VKILL         = 4
    62  	VEOF          = 5
    63  	VEOL          = 6
    64  	VEOL2         = 7
    65  	VSTART        = 8
    66  	VSTOP         = 9
    67  	VSUSP         = 10
    68  	VDSUSP        = 11
    69  	VREPRINT      = 12
    70  	VWERASE       = 13
    71  	VLNEXT        = 14
    72  	VFLUSH        = 15
    73  	VSWTCH        = 16
    74  	VSTATUS       = 17
    75  	VDISCARD      = 18
    76  	IGNPAR        = 30
    77  	PARMRK        = 31
    78  	INPCK         = 32
    79  	ISTRIP        = 33
    80  	INLCR         = 34
    81  	IGNCR         = 35
    82  	ICRNL         = 36
    83  	IUCLC         = 37
    84  	IXON          = 38
    85  	IXANY         = 39
    86  	IXOFF         = 40
    87  	IMAXBEL       = 41
    88  	ISIG          = 50
    89  	ICANON        = 51
    90  	XCASE         = 52
    91  	ECHO          = 53
    92  	ECHOE         = 54
    93  	ECHOK         = 55
    94  	ECHONL        = 56
    95  	NOFLSH        = 57
    96  	TOSTOP        = 58
    97  	IEXTEN        = 59
    98  	ECHOCTL       = 60
    99  	ECHOKE        = 61
   100  	PENDIN        = 62
   101  	OPOST         = 70
   102  	OLCUC         = 71
   103  	ONLCR         = 72
   104  	OCRNL         = 73
   105  	ONOCR         = 74
   106  	ONLRET        = 75
   107  	CS7           = 90
   108  	CS8           = 91
   109  	PARENB        = 92
   110  	PARODD        = 93
   111  	TTY_OP_ISPEED = 128
   112  	TTY_OP_OSPEED = 129
   113  )
   114  
   115  // A Session represents a connection to a remote command or shell.
   116  type Session struct {
   117  	// Stdin specifies the remote process's standard input.
   118  	// If Stdin is nil, the remote process reads from an empty
   119  	// bytes.Buffer.
   120  	Stdin io.Reader
   121  
   122  	// Stdout and Stderr specify the remote process's standard
   123  	// output and error.
   124  	//
   125  	// If either is nil, Run connects the corresponding file
   126  	// descriptor to an instance of ioutil.Discard. There is a
   127  	// fixed amount of buffering that is shared for the two streams.
   128  	// If either blocks it may eventually cause the remote
   129  	// command to block.
   130  	Stdout io.Writer
   131  	Stderr io.Writer
   132  
   133  	ch        Channel // the channel backing this session
   134  	started   bool    // true once Start, Run or Shell is invoked.
   135  	copyFuncs []func() error
   136  	errors    chan error // one send per copyFunc
   137  
   138  	// true if pipe method is active
   139  	stdinpipe, stdoutpipe, stderrpipe bool
   140  
   141  	// stdinPipeWriter is non-nil if StdinPipe has not been called
   142  	// and Stdin was specified by the user; it is the write end of
   143  	// a pipe connecting Session.Stdin to the stdin channel.
   144  	stdinPipeWriter io.WriteCloser
   145  
   146  	exitStatus chan error
   147  }
   148  
   149  // SendRequest sends an out-of-band channel request on the SSH channel
   150  // underlying the session.
   151  func (s *Session) SendRequest(name string, wantReply bool, payload []byte) (bool, error) {
   152  	return s.ch.SendRequest(name, wantReply, payload)
   153  }
   154  
   155  func (s *Session) Close() error {
   156  	return s.ch.Close()
   157  }
   158  
   159  // RFC 4254 Section 6.4.
   160  type setenvRequest struct {
   161  	Name  string
   162  	Value string
   163  }
   164  
   165  // Setenv sets an environment variable that will be applied to any
   166  // command executed by Shell or Run.
   167  func (s *Session) Setenv(name, value string) error {
   168  	msg := setenvRequest{
   169  		Name:  name,
   170  		Value: value,
   171  	}
   172  	ok, err := s.ch.SendRequest("env", true, Marshal(&msg))
   173  	if err == nil && !ok {
   174  		err = errors.New("ssh: setenv failed")
   175  	}
   176  	return err
   177  }
   178  
   179  // RFC 4254 Section 6.2.
   180  type ptyRequestMsg struct {
   181  	Term     string
   182  	Columns  uint32
   183  	Rows     uint32
   184  	Width    uint32
   185  	Height   uint32
   186  	Modelist string
   187  }
   188  
   189  // RequestPty requests the association of a pty with the session on the remote host.
   190  func (s *Session) RequestPty(term string, h, w int, termmodes TerminalModes) error {
   191  	var tm []byte
   192  	for k, v := range termmodes {
   193  		kv := struct {
   194  			Key byte
   195  			Val uint32
   196  		}{k, v}
   197  
   198  		tm = append(tm, Marshal(&kv)...)
   199  	}
   200  	tm = append(tm, tty_OP_END)
   201  	req := ptyRequestMsg{
   202  		Term:     term,
   203  		Columns:  uint32(w),
   204  		Rows:     uint32(h),
   205  		Width:    uint32(w * 8),
   206  		Height:   uint32(h * 8),
   207  		Modelist: string(tm),
   208  	}
   209  	ok, err := s.ch.SendRequest("pty-req", true, Marshal(&req))
   210  	if err == nil && !ok {
   211  		err = errors.New("ssh: pty-req failed")
   212  	}
   213  	return err
   214  }
   215  
   216  // RFC 4254 Section 6.5.
   217  type subsystemRequestMsg struct {
   218  	Subsystem string
   219  }
   220  
   221  // RequestSubsystem requests the association of a subsystem with the session on the remote host.
   222  // A subsystem is a predefined command that runs in the background when the ssh session is initiated
   223  func (s *Session) RequestSubsystem(subsystem string) error {
   224  	msg := subsystemRequestMsg{
   225  		Subsystem: subsystem,
   226  	}
   227  	ok, err := s.ch.SendRequest("subsystem", true, Marshal(&msg))
   228  	if err == nil && !ok {
   229  		err = errors.New("ssh: subsystem request failed")
   230  	}
   231  	return err
   232  }
   233  
   234  // RFC 4254 Section 6.7.
   235  type ptyWindowChangeMsg struct {
   236  	Columns uint32
   237  	Rows    uint32
   238  	Width   uint32
   239  	Height  uint32
   240  }
   241  
   242  // WindowChange informs the remote host about a terminal window dimension change to h rows and w columns.
   243  func (s *Session) WindowChange(h, w int) error {
   244  	req := ptyWindowChangeMsg{
   245  		Columns: uint32(w),
   246  		Rows:    uint32(h),
   247  		Width:   uint32(w * 8),
   248  		Height:  uint32(h * 8),
   249  	}
   250  	_, err := s.ch.SendRequest("window-change", false, Marshal(&req))
   251  	return err
   252  }
   253  
   254  // RFC 4254 Section 6.9.
   255  type signalMsg struct {
   256  	Signal string
   257  }
   258  
   259  // Signal sends the given signal to the remote process.
   260  // sig is one of the SIG* constants.
   261  func (s *Session) Signal(sig Signal) error {
   262  	msg := signalMsg{
   263  		Signal: string(sig),
   264  	}
   265  
   266  	_, err := s.ch.SendRequest("signal", false, Marshal(&msg))
   267  	return err
   268  }
   269  
   270  // RFC 4254 Section 6.5.
   271  type execMsg struct {
   272  	Command string
   273  }
   274  
   275  // Start runs cmd on the remote host. Typically, the remote
   276  // server passes cmd to the shell for interpretation.
   277  // A Session only accepts one call to Run, Start or Shell.
   278  func (s *Session) Start(cmd string) error {
   279  	if s.started {
   280  		return errors.New("ssh: session already started")
   281  	}
   282  	req := execMsg{
   283  		Command: cmd,
   284  	}
   285  
   286  	ok, err := s.ch.SendRequest("exec", true, Marshal(&req))
   287  	if err == nil && !ok {
   288  		err = fmt.Errorf("ssh: command %v failed", cmd)
   289  	}
   290  	if err != nil {
   291  		return err
   292  	}
   293  	return s.start()
   294  }
   295  
   296  // Run runs cmd on the remote host. Typically, the remote
   297  // server passes cmd to the shell for interpretation.
   298  // A Session only accepts one call to Run, Start, Shell, Output,
   299  // or CombinedOutput.
   300  //
   301  // The returned error is nil if the command runs, has no problems
   302  // copying stdin, stdout, and stderr, and exits with a zero exit
   303  // status.
   304  //
   305  // If the remote server does not send an exit status, an error of type
   306  // *ExitMissingError is returned. If the command completes
   307  // unsuccessfully or is interrupted by a signal, the error is of type
   308  // *ExitError. Other error types may be returned for I/O problems.
   309  func (s *Session) Run(cmd string) error {
   310  	err := s.Start(cmd)
   311  	if err != nil {
   312  		return err
   313  	}
   314  	return s.Wait()
   315  }
   316  
   317  // Output runs cmd on the remote host and returns its standard output.
   318  func (s *Session) Output(cmd string) ([]byte, error) {
   319  	if s.Stdout != nil {
   320  		return nil, errors.New("ssh: Stdout already set")
   321  	}
   322  	var b bytes.Buffer
   323  	s.Stdout = &b
   324  	err := s.Run(cmd)
   325  	return b.Bytes(), err
   326  }
   327  
   328  type singleWriter struct {
   329  	b  bytes.Buffer
   330  	mu sync.Mutex
   331  }
   332  
   333  func (w *singleWriter) Write(p []byte) (int, error) {
   334  	w.mu.Lock()
   335  	defer w.mu.Unlock()
   336  	return w.b.Write(p)
   337  }
   338  
   339  // CombinedOutput runs cmd on the remote host and returns its combined
   340  // standard output and standard error.
   341  func (s *Session) CombinedOutput(cmd string) ([]byte, error) {
   342  	if s.Stdout != nil {
   343  		return nil, errors.New("ssh: Stdout already set")
   344  	}
   345  	if s.Stderr != nil {
   346  		return nil, errors.New("ssh: Stderr already set")
   347  	}
   348  	var b singleWriter
   349  	s.Stdout = &b
   350  	s.Stderr = &b
   351  	err := s.Run(cmd)
   352  	return b.b.Bytes(), err
   353  }
   354  
   355  // Shell starts a login shell on the remote host. A Session only
   356  // accepts one call to Run, Start, Shell, Output, or CombinedOutput.
   357  func (s *Session) Shell() error {
   358  	if s.started {
   359  		return errors.New("ssh: session already started")
   360  	}
   361  
   362  	ok, err := s.ch.SendRequest("shell", true, nil)
   363  	if err == nil && !ok {
   364  		return errors.New("ssh: could not start shell")
   365  	}
   366  	if err != nil {
   367  		return err
   368  	}
   369  	return s.start()
   370  }
   371  
   372  func (s *Session) start() error {
   373  	s.started = true
   374  
   375  	type F func(*Session)
   376  	for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} {
   377  		setupFd(s)
   378  	}
   379  
   380  	s.errors = make(chan error, len(s.copyFuncs))
   381  	for _, fn := range s.copyFuncs {
   382  		go func(fn func() error) {
   383  			select {
   384  			case s.errors <- fn():
   385  			case <-s.ch.Done():
   386  				return
   387  			}
   388  		}(fn)
   389  	}
   390  	return nil
   391  }
   392  
   393  // Wait waits for the remote command to exit.
   394  //
   395  // The returned error is nil if the command runs, has no problems
   396  // copying stdin, stdout, and stderr, and exits with a zero exit
   397  // status.
   398  //
   399  // If the remote server does not send an exit status, an error of type
   400  // *ExitMissingError is returned. If the command completes
   401  // unsuccessfully or is interrupted by a signal, the error is of type
   402  // *ExitError. Other error types may be returned for I/O problems.
   403  func (s *Session) Wait() error {
   404  	if !s.started {
   405  		return errors.New("ssh: session not started")
   406  	}
   407  	select {
   408  	case <-s.ch.Done():
   409  		return io.EOF
   410  
   411  	case waitErr := <-s.exitStatus:
   412  
   413  		if s.stdinPipeWriter != nil {
   414  			s.stdinPipeWriter.Close()
   415  		}
   416  		var copyError error
   417  		for _ = range s.copyFuncs {
   418  			select {
   419  			case <-s.ch.Done():
   420  				return io.EOF
   421  			case err := <-s.errors:
   422  				if err != nil && copyError == nil {
   423  					copyError = err
   424  				}
   425  			}
   426  		}
   427  		if waitErr != nil {
   428  			return waitErr
   429  		}
   430  		return copyError
   431  	}
   432  }
   433  
   434  func (s *Session) wait(reqs <-chan *Request) error {
   435  	wm := Waitmsg{status: -1}
   436  	// Wait for msg channel to be closed before returning.
   437  	for msg := range reqs {
   438  		switch msg.Type {
   439  		case "exit-status":
   440  			wm.status = int(binary.BigEndian.Uint32(msg.Payload))
   441  		case "exit-signal":
   442  			var sigval struct {
   443  				Signal     string
   444  				CoreDumped bool
   445  				Error      string
   446  				Lang       string
   447  			}
   448  			if err := Unmarshal(msg.Payload, &sigval); err != nil {
   449  				return err
   450  			}
   451  
   452  			// Must sanitize strings?
   453  			wm.signal = sigval.Signal
   454  			wm.msg = sigval.Error
   455  			wm.lang = sigval.Lang
   456  		default:
   457  			// This handles keepalives and matches
   458  			// OpenSSH's behaviour.
   459  			if msg.WantReply {
   460  				msg.Reply(false, nil)
   461  			}
   462  		}
   463  	}
   464  	if wm.status == 0 {
   465  		return nil
   466  	}
   467  	if wm.status == -1 {
   468  		// exit-status was never sent from server
   469  		if wm.signal == "" {
   470  			// signal was not sent either.  RFC 4254
   471  			// section 6.10 recommends against this
   472  			// behavior, but it is allowed, so we let
   473  			// clients handle it.
   474  			return &ExitMissingError{}
   475  		}
   476  		wm.status = 128
   477  		if _, ok := signals[Signal(wm.signal)]; ok {
   478  			wm.status += signals[Signal(wm.signal)]
   479  		}
   480  	}
   481  
   482  	return &ExitError{wm}
   483  }
   484  
   485  // ExitMissingError is returned if a session is torn down cleanly, but
   486  // the server sends no confirmation of the exit status.
   487  type ExitMissingError struct{}
   488  
   489  func (e *ExitMissingError) Error() string {
   490  	return "wait: remote command exited without exit status or exit signal"
   491  }
   492  
   493  func (s *Session) stdin() {
   494  	if s.stdinpipe {
   495  		return
   496  	}
   497  	var stdin io.Reader
   498  	if s.Stdin == nil {
   499  		stdin = new(bytes.Buffer)
   500  	} else {
   501  		r, w := io.Pipe()
   502  		go func() {
   503  			_, err := io.Copy(w, s.Stdin)
   504  			w.CloseWithError(err)
   505  		}()
   506  		stdin, s.stdinPipeWriter = r, w
   507  	}
   508  	s.copyFuncs = append(s.copyFuncs, func() error {
   509  		_, err := io.Copy(s.ch, stdin)
   510  		if err1 := s.ch.CloseWrite(); err == nil && err1 != io.EOF {
   511  			err = err1
   512  		}
   513  		return err
   514  	})
   515  }
   516  
   517  func (s *Session) stdout() {
   518  	if s.stdoutpipe {
   519  		return
   520  	}
   521  	if s.Stdout == nil {
   522  		s.Stdout = ioutil.Discard
   523  	}
   524  	s.copyFuncs = append(s.copyFuncs, func() error {
   525  		_, err := io.Copy(s.Stdout, s.ch)
   526  		return err
   527  	})
   528  }
   529  
   530  func (s *Session) stderr() {
   531  	if s.stderrpipe {
   532  		return
   533  	}
   534  	if s.Stderr == nil {
   535  		s.Stderr = ioutil.Discard
   536  	}
   537  	s.copyFuncs = append(s.copyFuncs, func() error {
   538  		_, err := io.Copy(s.Stderr, s.ch.Stderr())
   539  		return err
   540  	})
   541  }
   542  
   543  // sessionStdin reroutes Close to CloseWrite.
   544  type sessionStdin struct {
   545  	io.Writer
   546  	ch Channel
   547  }
   548  
   549  func (s *sessionStdin) Close() error {
   550  	return s.ch.CloseWrite()
   551  }
   552  
   553  // StdinPipe returns a pipe that will be connected to the
   554  // remote command's standard input when the command starts.
   555  func (s *Session) StdinPipe() (io.WriteCloser, error) {
   556  	if s.Stdin != nil {
   557  		return nil, errors.New("ssh: Stdin already set")
   558  	}
   559  	if s.started {
   560  		return nil, errors.New("ssh: StdinPipe after process started")
   561  	}
   562  	s.stdinpipe = true
   563  	return &sessionStdin{s.ch, s.ch}, nil
   564  }
   565  
   566  // StdoutPipe returns a pipe that will be connected to the
   567  // remote command's standard output when the command starts.
   568  // There is a fixed amount of buffering that is shared between
   569  // stdout and stderr streams. If the StdoutPipe reader is
   570  // not serviced fast enough it may eventually cause the
   571  // remote command to block.
   572  func (s *Session) StdoutPipe() (io.Reader, error) {
   573  	if s.Stdout != nil {
   574  		return nil, errors.New("ssh: Stdout already set")
   575  	}
   576  	if s.started {
   577  		return nil, errors.New("ssh: StdoutPipe after process started")
   578  	}
   579  	s.stdoutpipe = true
   580  	return s.ch, nil
   581  }
   582  
   583  // StderrPipe returns a pipe that will be connected to the
   584  // remote command's standard error when the command starts.
   585  // There is a fixed amount of buffering that is shared between
   586  // stdout and stderr streams. If the StderrPipe reader is
   587  // not serviced fast enough it may eventually cause the
   588  // remote command to block.
   589  func (s *Session) StderrPipe() (io.Reader, error) {
   590  	if s.Stderr != nil {
   591  		return nil, errors.New("ssh: Stderr already set")
   592  	}
   593  	if s.started {
   594  		return nil, errors.New("ssh: StderrPipe after process started")
   595  	}
   596  	s.stderrpipe = true
   597  	return s.ch.Stderr(), nil
   598  }
   599  
   600  // newSession returns a new interactive session on the remote host.
   601  func newSession(ch Channel, reqs <-chan *Request) (*Session, error) {
   602  	s := &Session{
   603  		ch: ch,
   604  	}
   605  	s.exitStatus = make(chan error, 1)
   606  	go func() {
   607  		select {
   608  		case s.exitStatus <- s.wait(reqs):
   609  		case <-ch.Done():
   610  			return
   611  		}
   612  	}()
   613  
   614  	return s, nil
   615  }
   616  
   617  // An ExitError reports unsuccessful completion of a remote command.
   618  type ExitError struct {
   619  	Waitmsg
   620  }
   621  
   622  func (e *ExitError) Error() string {
   623  	return e.Waitmsg.String()
   624  }
   625  
   626  // Waitmsg stores the information about an exited remote command
   627  // as reported by Wait.
   628  type Waitmsg struct {
   629  	status int
   630  	signal string
   631  	msg    string
   632  	lang   string
   633  }
   634  
   635  // ExitStatus returns the exit status of the remote command.
   636  func (w Waitmsg) ExitStatus() int {
   637  	return w.status
   638  }
   639  
   640  // Signal returns the exit signal of the remote command if
   641  // it was terminated violently.
   642  func (w Waitmsg) Signal() string {
   643  	return w.signal
   644  }
   645  
   646  // Msg returns the exit message given by the remote command
   647  func (w Waitmsg) Msg() string {
   648  	return w.msg
   649  }
   650  
   651  // Lang returns the language tag. See RFC 3066
   652  func (w Waitmsg) Lang() string {
   653  	return w.lang
   654  }
   655  
   656  func (w Waitmsg) String() string {
   657  	str := fmt.Sprintf("Process exited with status %v", w.status)
   658  	if w.signal != "" {
   659  		str += fmt.Sprintf(" from signal %v", w.signal)
   660  	}
   661  	if w.msg != "" {
   662  		str += fmt.Sprintf(". Reason was: %v", w.msg)
   663  	}
   664  	return str
   665  }