github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/third_party/code.google.com/p/go.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  )
    17  
    18  type Signal string
    19  
    20  // POSIX signals as listed in RFC 4254 Section 6.10.
    21  const (
    22  	SIGABRT Signal = "ABRT"
    23  	SIGALRM Signal = "ALRM"
    24  	SIGFPE  Signal = "FPE"
    25  	SIGHUP  Signal = "HUP"
    26  	SIGILL  Signal = "ILL"
    27  	SIGINT  Signal = "INT"
    28  	SIGKILL Signal = "KILL"
    29  	SIGPIPE Signal = "PIPE"
    30  	SIGQUIT Signal = "QUIT"
    31  	SIGSEGV Signal = "SEGV"
    32  	SIGTERM Signal = "TERM"
    33  	SIGUSR1 Signal = "USR1"
    34  	SIGUSR2 Signal = "USR2"
    35  )
    36  
    37  var signals = map[Signal]int{
    38  	SIGABRT: 6,
    39  	SIGALRM: 14,
    40  	SIGFPE:  8,
    41  	SIGHUP:  1,
    42  	SIGILL:  4,
    43  	SIGINT:  2,
    44  	SIGKILL: 9,
    45  	SIGPIPE: 13,
    46  	SIGQUIT: 3,
    47  	SIGSEGV: 11,
    48  	SIGTERM: 15,
    49  }
    50  
    51  // A Session represents a connection to a remote command or shell.
    52  type Session struct {
    53  	// Stdin specifies the remote process's standard input.
    54  	// If Stdin is nil, the remote process reads from an empty
    55  	// bytes.Buffer.
    56  	Stdin io.Reader
    57  
    58  	// Stdout and Stderr specify the remote process's standard
    59  	// output and error.
    60  	//
    61  	// If either is nil, Run connects the corresponding file
    62  	// descriptor to an instance of ioutil.Discard. There is a
    63  	// fixed amount of buffering that is shared for the two streams.
    64  	// If either blocks it may eventually cause the remote
    65  	// command to block.
    66  	Stdout io.Writer
    67  	Stderr io.Writer
    68  
    69  	*clientChan // the channel backing this session
    70  
    71  	started   bool // true once Start, Run or Shell is invoked.
    72  	copyFuncs []func() error
    73  	errors    chan error // one send per copyFunc
    74  
    75  	// true if pipe method is active
    76  	stdinpipe, stdoutpipe, stderrpipe bool
    77  }
    78  
    79  // RFC 4254 Section 6.4.
    80  type setenvRequest struct {
    81  	PeersId   uint32
    82  	Request   string
    83  	WantReply bool
    84  	Name      string
    85  	Value     string
    86  }
    87  
    88  // Setenv sets an environment variable that will be applied to any
    89  // command executed by Shell or Run.
    90  func (s *Session) Setenv(name, value string) error {
    91  	req := setenvRequest{
    92  		PeersId:   s.peersId,
    93  		Request:   "env",
    94  		WantReply: true,
    95  		Name:      name,
    96  		Value:     value,
    97  	}
    98  	if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
    99  		return err
   100  	}
   101  	return s.waitForResponse()
   102  }
   103  
   104  // An empty mode list, see RFC 4254 Section 8.
   105  var emptyModelist = "\x00"
   106  
   107  // RFC 4254 Section 6.2.
   108  type ptyRequestMsg struct {
   109  	PeersId   uint32
   110  	Request   string
   111  	WantReply bool
   112  	Term      string
   113  	Columns   uint32
   114  	Rows      uint32
   115  	Width     uint32
   116  	Height    uint32
   117  	Modelist  string
   118  }
   119  
   120  // RequestPty requests the association of a pty with the session on the remote host.
   121  func (s *Session) RequestPty(term string, h, w int) error {
   122  	req := ptyRequestMsg{
   123  		PeersId:   s.peersId,
   124  		Request:   "pty-req",
   125  		WantReply: true,
   126  		Term:      term,
   127  		Columns:   uint32(w),
   128  		Rows:      uint32(h),
   129  		Width:     uint32(w * 8),
   130  		Height:    uint32(h * 8),
   131  		Modelist:  emptyModelist,
   132  	}
   133  	if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
   134  		return err
   135  	}
   136  	return s.waitForResponse()
   137  }
   138  
   139  // RFC 4254 Section 6.9.
   140  type signalMsg struct {
   141  	PeersId   uint32
   142  	Request   string
   143  	WantReply bool
   144  	Signal    string
   145  }
   146  
   147  // Signal sends the given signal to the remote process.
   148  // sig is one of the SIG* constants.
   149  func (s *Session) Signal(sig Signal) error {
   150  	req := signalMsg{
   151  		PeersId:   s.peersId,
   152  		Request:   "signal",
   153  		WantReply: false,
   154  		Signal:    string(sig),
   155  	}
   156  	return s.writePacket(marshal(msgChannelRequest, req))
   157  }
   158  
   159  // RFC 4254 Section 6.5.
   160  type execMsg struct {
   161  	PeersId   uint32
   162  	Request   string
   163  	WantReply bool
   164  	Command   string
   165  }
   166  
   167  // Start runs cmd on the remote host. Typically, the remote
   168  // server passes cmd to the shell for interpretation.
   169  // A Session only accepts one call to Run, Start or Shell.
   170  func (s *Session) Start(cmd string) error {
   171  	if s.started {
   172  		return errors.New("ssh: session already started")
   173  	}
   174  	req := execMsg{
   175  		PeersId:   s.peersId,
   176  		Request:   "exec",
   177  		WantReply: true,
   178  		Command:   cmd,
   179  	}
   180  	if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
   181  		return err
   182  	}
   183  	if err := s.waitForResponse(); err != nil {
   184  		return fmt.Errorf("ssh: could not execute command %s: %v", cmd, err)
   185  	}
   186  	return s.start()
   187  }
   188  
   189  // Run runs cmd on the remote host. Typically, the remote
   190  // server passes cmd to the shell for interpretation.
   191  // A Session only accepts one call to Run, Start or Shell.
   192  //
   193  // The returned error is nil if the command runs, has no problems
   194  // copying stdin, stdout, and stderr, and exits with a zero exit
   195  // status.
   196  //
   197  // If the command fails to run or doesn't complete successfully, the
   198  // error is of type *ExitError. Other error types may be
   199  // returned for I/O problems.
   200  func (s *Session) Run(cmd string) error {
   201  	err := s.Start(cmd)
   202  	if err != nil {
   203  		return err
   204  	}
   205  	return s.Wait()
   206  }
   207  
   208  // Shell starts a login shell on the remote host. A Session only
   209  // accepts one call to Run, Start or Shell.
   210  func (s *Session) Shell() error {
   211  	if s.started {
   212  		return errors.New("ssh: session already started")
   213  	}
   214  	req := channelRequestMsg{
   215  		PeersId:   s.peersId,
   216  		Request:   "shell",
   217  		WantReply: true,
   218  	}
   219  	if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
   220  		return err
   221  	}
   222  	if err := s.waitForResponse(); err != nil {
   223  		return fmt.Errorf("ssh: cound not execute shell: %v", err)
   224  	}
   225  	return s.start()
   226  }
   227  
   228  func (s *Session) waitForResponse() error {
   229  	msg := <-s.msg
   230  	switch msg.(type) {
   231  	case *channelRequestSuccessMsg:
   232  		return nil
   233  	case *channelRequestFailureMsg:
   234  		return errors.New("request failed")
   235  	}
   236  	return fmt.Errorf("unknown packet %T received: %v", msg, msg)
   237  }
   238  
   239  func (s *Session) start() error {
   240  	s.started = true
   241  
   242  	type F func(*Session)
   243  	for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} {
   244  		setupFd(s)
   245  	}
   246  
   247  	s.errors = make(chan error, len(s.copyFuncs))
   248  	for _, fn := range s.copyFuncs {
   249  		go func(fn func() error) {
   250  			s.errors <- fn()
   251  		}(fn)
   252  	}
   253  	return nil
   254  }
   255  
   256  // Wait waits for the remote command to exit.
   257  //
   258  // The returned error is nil if the command runs, has no problems
   259  // copying stdin, stdout, and stderr, and exits with a zero exit
   260  // status.
   261  //
   262  // If the command fails to run or doesn't complete successfully, the
   263  // error is of type *ExitError. Other error types may be
   264  // returned for I/O problems.
   265  func (s *Session) Wait() error {
   266  	if !s.started {
   267  		return errors.New("ssh: session not started")
   268  	}
   269  	waitErr := s.wait()
   270  
   271  	var copyError error
   272  	for _ = range s.copyFuncs {
   273  		if err := <-s.errors; err != nil && copyError == nil {
   274  			copyError = err
   275  		}
   276  	}
   277  	if waitErr != nil {
   278  		return waitErr
   279  	}
   280  	return copyError
   281  }
   282  
   283  func (s *Session) wait() error {
   284  	wm := Waitmsg{status: -1}
   285  
   286  	// Wait for msg channel to be closed before returning.
   287  	for msg := range s.msg {
   288  		switch msg := msg.(type) {
   289  		case *channelRequestMsg:
   290  			switch msg.Request {
   291  			case "exit-status":
   292  				d := msg.RequestSpecificData
   293  				wm.status = int(d[0])<<24 | int(d[1])<<16 | int(d[2])<<8 | int(d[3])
   294  			case "exit-signal":
   295  				signal, rest, ok := parseString(msg.RequestSpecificData)
   296  				if !ok {
   297  					return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
   298  				}
   299  				wm.signal = safeString(string(signal))
   300  
   301  				// skip coreDumped bool
   302  				if len(rest) == 0 {
   303  					return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
   304  				}
   305  				rest = rest[1:]
   306  
   307  				errmsg, rest, ok := parseString(rest)
   308  				if !ok {
   309  					return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
   310  				}
   311  				wm.msg = safeString(string(errmsg))
   312  
   313  				lang, _, ok := parseString(rest)
   314  				if !ok {
   315  					return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
   316  				}
   317  				wm.lang = safeString(string(lang))
   318  			default:
   319  				return fmt.Errorf("wait: unexpected channel request: %v", msg)
   320  			}
   321  		default:
   322  			return fmt.Errorf("wait: unexpected packet %T received: %v", msg, msg)
   323  		}
   324  	}
   325  	if wm.status == 0 {
   326  		return nil
   327  	}
   328  	if wm.status == -1 {
   329  		// exit-status was never sent from server
   330  		if wm.signal == "" {
   331  			return errors.New("wait: remote command exited without exit status or exit signal")
   332  		}
   333  		wm.status = 128
   334  		if _, ok := signals[Signal(wm.signal)]; ok {
   335  			wm.status += signals[Signal(wm.signal)]
   336  		}
   337  	}
   338  	return &ExitError{wm}
   339  }
   340  
   341  func (s *Session) stdin() {
   342  	if s.stdinpipe {
   343  		return
   344  	}
   345  	if s.Stdin == nil {
   346  		s.Stdin = new(bytes.Buffer)
   347  	}
   348  	s.copyFuncs = append(s.copyFuncs, func() error {
   349  		_, err := io.Copy(s.clientChan.stdin, s.Stdin)
   350  		if err1 := s.clientChan.stdin.Close(); err == nil {
   351  			err = err1
   352  		}
   353  		return err
   354  	})
   355  }
   356  
   357  func (s *Session) stdout() {
   358  	if s.stdoutpipe {
   359  		return
   360  	}
   361  	if s.Stdout == nil {
   362  		s.Stdout = ioutil.Discard
   363  	}
   364  	s.copyFuncs = append(s.copyFuncs, func() error {
   365  		_, err := io.Copy(s.Stdout, s.clientChan.stdout)
   366  		return err
   367  	})
   368  }
   369  
   370  func (s *Session) stderr() {
   371  	if s.stderrpipe {
   372  		return
   373  	}
   374  	if s.Stderr == nil {
   375  		s.Stderr = ioutil.Discard
   376  	}
   377  	s.copyFuncs = append(s.copyFuncs, func() error {
   378  		_, err := io.Copy(s.Stderr, s.clientChan.stderr)
   379  		return err
   380  	})
   381  }
   382  
   383  // StdinPipe returns a pipe that will be connected to the
   384  // remote command's standard input when the command starts.
   385  func (s *Session) StdinPipe() (io.WriteCloser, error) {
   386  	if s.Stdin != nil {
   387  		return nil, errors.New("ssh: Stdin already set")
   388  	}
   389  	if s.started {
   390  		return nil, errors.New("ssh: StdinPipe after process started")
   391  	}
   392  	s.stdinpipe = true
   393  	return s.clientChan.stdin, nil
   394  }
   395  
   396  // StdoutPipe returns a pipe that will be connected to the
   397  // remote command's standard output when the command starts.
   398  // There is a fixed amount of buffering that is shared between
   399  // stdout and stderr streams. If the StdoutPipe reader is
   400  // not serviced fast enought it may eventually cause the
   401  // remote command to block.
   402  func (s *Session) StdoutPipe() (io.Reader, error) {
   403  	if s.Stdout != nil {
   404  		return nil, errors.New("ssh: Stdout already set")
   405  	}
   406  	if s.started {
   407  		return nil, errors.New("ssh: StdoutPipe after process started")
   408  	}
   409  	s.stdoutpipe = true
   410  	return s.clientChan.stdout, nil
   411  }
   412  
   413  // StderrPipe returns a pipe that will be connected to the
   414  // remote command's standard error when the command starts.
   415  // There is a fixed amount of buffering that is shared between
   416  // stdout and stderr streams. If the StderrPipe reader is
   417  // not serviced fast enought it may eventually cause the
   418  // remote command to block.
   419  func (s *Session) StderrPipe() (io.Reader, error) {
   420  	if s.Stderr != nil {
   421  		return nil, errors.New("ssh: Stderr already set")
   422  	}
   423  	if s.started {
   424  		return nil, errors.New("ssh: StderrPipe after process started")
   425  	}
   426  	s.stderrpipe = true
   427  	return s.clientChan.stderr, nil
   428  }
   429  
   430  // TODO(dfc) add Output and CombinedOutput helpers
   431  
   432  // NewSession returns a new interactive session on the remote host.
   433  func (c *ClientConn) NewSession() (*Session, error) {
   434  	ch := c.newChan(c.transport)
   435  	if err := c.writePacket(marshal(msgChannelOpen, channelOpenMsg{
   436  		ChanType:      "session",
   437  		PeersId:       ch.id,
   438  		PeersWindow:   1 << 14,
   439  		MaxPacketSize: 1 << 15, // RFC 4253 6.1
   440  	})); err != nil {
   441  		c.chanlist.remove(ch.id)
   442  		return nil, err
   443  	}
   444  	if err := ch.waitForChannelOpenResponse(); err != nil {
   445  		c.chanlist.remove(ch.id)
   446  		return nil, fmt.Errorf("ssh: unable to open session: %v", err)
   447  	}
   448  	return &Session{
   449  		clientChan: ch,
   450  	}, nil
   451  }
   452  
   453  // An ExitError reports unsuccessful completion of a remote command.
   454  type ExitError struct {
   455  	Waitmsg
   456  }
   457  
   458  func (e *ExitError) Error() string {
   459  	return e.Waitmsg.String()
   460  }
   461  
   462  // Waitmsg stores the information about an exited remote command
   463  // as reported by Wait.
   464  type Waitmsg struct {
   465  	status int
   466  	signal string
   467  	msg    string
   468  	lang   string
   469  }
   470  
   471  // ExitStatus returns the exit status of the remote command.
   472  func (w Waitmsg) ExitStatus() int {
   473  	return w.status
   474  }
   475  
   476  // Signal returns the exit signal of the remote command if
   477  // it was terminated violently.
   478  func (w Waitmsg) Signal() string {
   479  	return w.signal
   480  }
   481  
   482  // Msg returns the exit message given by the remote command
   483  func (w Waitmsg) Msg() string {
   484  	return w.msg
   485  }
   486  
   487  // Lang returns the language tag. See RFC 3066
   488  func (w Waitmsg) Lang() string {
   489  	return w.lang
   490  }
   491  
   492  func (w Waitmsg) String() string {
   493  	return fmt.Sprintf("Process exited with: %v. Reason was: %v (%v)", w.status, w.msg, w.signal)
   494  }