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