session.go raw

   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  	"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  	IUTF8         = 42 // RFC 8160
  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 io.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  			s.errors <- fn()
 384  		}(fn)
 385  	}
 386  	return nil
 387  }
 388  
 389  // Wait waits for the remote command to exit.
 390  //
 391  // The returned error is nil if the command runs, has no problems
 392  // copying stdin, stdout, and stderr, and exits with a zero exit
 393  // status.
 394  //
 395  // If the remote server does not send an exit status, an error of type
 396  // *ExitMissingError is returned. If the command completes
 397  // unsuccessfully or is interrupted by a signal, the error is of type
 398  // *ExitError. Other error types may be returned for I/O problems.
 399  func (s *Session) Wait() error {
 400  	if !s.started {
 401  		return errors.New("ssh: session not started")
 402  	}
 403  	waitErr := <-s.exitStatus
 404  
 405  	if s.stdinPipeWriter != nil {
 406  		s.stdinPipeWriter.Close()
 407  	}
 408  	var copyError error
 409  	for range s.copyFuncs {
 410  		if err := <-s.errors; err != nil && copyError == nil {
 411  			copyError = err
 412  		}
 413  	}
 414  	if waitErr != nil {
 415  		return waitErr
 416  	}
 417  	return copyError
 418  }
 419  
 420  func (s *Session) wait(reqs <-chan *Request) error {
 421  	wm := Waitmsg{status: -1}
 422  	// Wait for msg channel to be closed before returning.
 423  	for msg := range reqs {
 424  		switch msg.Type {
 425  		case "exit-status":
 426  			wm.status = int(binary.BigEndian.Uint32(msg.Payload))
 427  		case "exit-signal":
 428  			var sigval struct {
 429  				Signal     string
 430  				CoreDumped bool
 431  				Error      string
 432  				Lang       string
 433  			}
 434  			if err := Unmarshal(msg.Payload, &sigval); err != nil {
 435  				return err
 436  			}
 437  
 438  			// Must sanitize strings?
 439  			wm.signal = sigval.Signal
 440  			wm.msg = sigval.Error
 441  			wm.lang = sigval.Lang
 442  		default:
 443  			// This handles keepalives and matches
 444  			// OpenSSH's behaviour.
 445  			if msg.WantReply {
 446  				msg.Reply(false, nil)
 447  			}
 448  		}
 449  	}
 450  	if wm.status == 0 {
 451  		return nil
 452  	}
 453  	if wm.status == -1 {
 454  		// exit-status was never sent from server
 455  		if wm.signal == "" {
 456  			// signal was not sent either.  RFC 4254
 457  			// section 6.10 recommends against this
 458  			// behavior, but it is allowed, so we let
 459  			// clients handle it.
 460  			return &ExitMissingError{}
 461  		}
 462  		wm.status = 128
 463  		if _, ok := signals[Signal(wm.signal)]; ok {
 464  			wm.status += signals[Signal(wm.signal)]
 465  		}
 466  	}
 467  
 468  	return &ExitError{wm}
 469  }
 470  
 471  // ExitMissingError is returned if a session is torn down cleanly, but
 472  // the server sends no confirmation of the exit status.
 473  type ExitMissingError struct{}
 474  
 475  func (e *ExitMissingError) Error() string {
 476  	return "wait: remote command exited without exit status or exit signal"
 477  }
 478  
 479  func (s *Session) stdin() {
 480  	if s.stdinpipe {
 481  		return
 482  	}
 483  	var stdin io.Reader
 484  	if s.Stdin == nil {
 485  		stdin = new(bytes.Buffer)
 486  	} else {
 487  		r, w := io.Pipe()
 488  		go func() {
 489  			_, err := io.Copy(w, s.Stdin)
 490  			w.CloseWithError(err)
 491  		}()
 492  		stdin, s.stdinPipeWriter = r, w
 493  	}
 494  	s.copyFuncs = append(s.copyFuncs, func() error {
 495  		_, err := io.Copy(s.ch, stdin)
 496  		if err1 := s.ch.CloseWrite(); err == nil && err1 != io.EOF {
 497  			err = err1
 498  		}
 499  		return err
 500  	})
 501  }
 502  
 503  func (s *Session) stdout() {
 504  	if s.stdoutpipe {
 505  		return
 506  	}
 507  	if s.Stdout == nil {
 508  		s.Stdout = io.Discard
 509  	}
 510  	s.copyFuncs = append(s.copyFuncs, func() error {
 511  		_, err := io.Copy(s.Stdout, s.ch)
 512  		return err
 513  	})
 514  }
 515  
 516  func (s *Session) stderr() {
 517  	if s.stderrpipe {
 518  		return
 519  	}
 520  	if s.Stderr == nil {
 521  		s.Stderr = io.Discard
 522  	}
 523  	s.copyFuncs = append(s.copyFuncs, func() error {
 524  		_, err := io.Copy(s.Stderr, s.ch.Stderr())
 525  		return err
 526  	})
 527  }
 528  
 529  // sessionStdin reroutes Close to CloseWrite.
 530  type sessionStdin struct {
 531  	io.Writer
 532  	ch Channel
 533  }
 534  
 535  func (s *sessionStdin) Close() error {
 536  	return s.ch.CloseWrite()
 537  }
 538  
 539  // StdinPipe returns a pipe that will be connected to the
 540  // remote command's standard input when the command starts.
 541  func (s *Session) StdinPipe() (io.WriteCloser, error) {
 542  	if s.Stdin != nil {
 543  		return nil, errors.New("ssh: Stdin already set")
 544  	}
 545  	if s.started {
 546  		return nil, errors.New("ssh: StdinPipe after process started")
 547  	}
 548  	s.stdinpipe = true
 549  	return &sessionStdin{s.ch, s.ch}, nil
 550  }
 551  
 552  // StdoutPipe returns a pipe that will be connected to the
 553  // remote command's standard output when the command starts.
 554  // There is a fixed amount of buffering that is shared between
 555  // stdout and stderr streams. If the StdoutPipe reader is
 556  // not serviced fast enough it may eventually cause the
 557  // remote command to block.
 558  func (s *Session) StdoutPipe() (io.Reader, error) {
 559  	if s.Stdout != nil {
 560  		return nil, errors.New("ssh: Stdout already set")
 561  	}
 562  	if s.started {
 563  		return nil, errors.New("ssh: StdoutPipe after process started")
 564  	}
 565  	s.stdoutpipe = true
 566  	return s.ch, nil
 567  }
 568  
 569  // StderrPipe returns a pipe that will be connected to the
 570  // remote command's standard error when the command starts.
 571  // There is a fixed amount of buffering that is shared between
 572  // stdout and stderr streams. If the StderrPipe reader is
 573  // not serviced fast enough it may eventually cause the
 574  // remote command to block.
 575  func (s *Session) StderrPipe() (io.Reader, error) {
 576  	if s.Stderr != nil {
 577  		return nil, errors.New("ssh: Stderr already set")
 578  	}
 579  	if s.started {
 580  		return nil, errors.New("ssh: StderrPipe after process started")
 581  	}
 582  	s.stderrpipe = true
 583  	return s.ch.Stderr(), nil
 584  }
 585  
 586  // newSession returns a new interactive session on the remote host.
 587  func newSession(ch Channel, reqs <-chan *Request) (*Session, error) {
 588  	s := &Session{
 589  		ch: ch,
 590  	}
 591  	s.exitStatus = make(chan error, 1)
 592  	go func() {
 593  		s.exitStatus <- s.wait(reqs)
 594  	}()
 595  
 596  	return s, nil
 597  }
 598  
 599  // An ExitError reports unsuccessful completion of a remote command.
 600  type ExitError struct {
 601  	Waitmsg
 602  }
 603  
 604  func (e *ExitError) Error() string {
 605  	return e.Waitmsg.String()
 606  }
 607  
 608  // Waitmsg stores the information about an exited remote command
 609  // as reported by Wait.
 610  type Waitmsg struct {
 611  	status int
 612  	signal string
 613  	msg    string
 614  	lang   string
 615  }
 616  
 617  // ExitStatus returns the exit status of the remote command.
 618  func (w Waitmsg) ExitStatus() int {
 619  	return w.status
 620  }
 621  
 622  // Signal returns the exit signal of the remote command if
 623  // it was terminated violently.
 624  func (w Waitmsg) Signal() string {
 625  	return w.signal
 626  }
 627  
 628  // Msg returns the exit message given by the remote command
 629  func (w Waitmsg) Msg() string {
 630  	return w.msg
 631  }
 632  
 633  // Lang returns the language tag. See RFC 3066
 634  func (w Waitmsg) Lang() string {
 635  	return w.lang
 636  }
 637  
 638  func (w Waitmsg) String() string {
 639  	str := fmt.Sprintf("Process exited with status %v", w.status)
 640  	if w.signal != "" {
 641  		str += fmt.Sprintf(" from signal %v", w.signal)
 642  	}
 643  	if w.msg != "" {
 644  		str += fmt.Sprintf(". Reason was: %v", w.msg)
 645  	}
 646  	return str
 647  }
 648