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.
7 // Session implements an interactive session described in
8 // "RFC 4254, section 6".
22 // POSIX signals as listed in RFC 4254 Section 6.10.
24 SIGABRT Signal = "ABRT"
25 SIGALRM Signal = "ALRM"
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"
39 var signals = map[Signal]int{
53 type TerminalModes map[uint8]uint32
55 // POSIX terminal mode flags as listed in RFC 4254 Section 8.
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
122 // Stdout and Stderr specify the remote process's standard
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
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
138 // true if pipe method is active
139 stdinpipe, stdoutpipe, stderrpipe bool
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
146 exitStatus chan error
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)
155 func (s *Session) Close() error {
159 // RFC 4254 Section 6.4.
160 type setenvRequest struct {
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{
172 ok, err := s.ch.SendRequest("env", true, Marshal(&msg))
173 if err == nil && !ok {
174 err = errors.New("ssh: setenv failed")
179 // RFC 4254 Section 6.2.
180 type ptyRequestMsg struct {
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 {
192 for k, v := range termmodes {
198 tm = append(tm, Marshal(&kv)...)
200 tm = append(tm, tty_OP_END)
201 req := ptyRequestMsg{
205 Width: uint32(w * 8),
206 Height: uint32(h * 8),
207 Modelist: string(tm),
209 ok, err := s.ch.SendRequest("pty-req", true, Marshal(&req))
210 if err == nil && !ok {
211 err = errors.New("ssh: pty-req failed")
216 // RFC 4254 Section 6.5.
217 type subsystemRequestMsg struct {
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,
227 ok, err := s.ch.SendRequest("subsystem", true, Marshal(&msg))
228 if err == nil && !ok {
229 err = errors.New("ssh: subsystem request failed")
234 // RFC 4254 Section 6.7.
235 type ptyWindowChangeMsg struct {
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{
247 Width: uint32(w * 8),
248 Height: uint32(h * 8),
250 _, err := s.ch.SendRequest("window-change", false, Marshal(&req))
254 // RFC 4254 Section 6.9.
255 type signalMsg struct {
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 {
266 _, err := s.ch.SendRequest("signal", false, Marshal(&msg))
270 // RFC 4254 Section 6.5.
271 type execMsg struct {
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 {
280 return errors.New("ssh: session already started")
286 ok, err := s.ch.SendRequest("exec", true, Marshal(&req))
287 if err == nil && !ok {
288 err = fmt.Errorf("ssh: command %v failed", cmd)
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.
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
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 {
317 // Output runs cmd on the remote host and returns its standard output.
318 func (s *Session) Output(cmd string) ([]byte, error) {
320 return nil, errors.New("ssh: Stdout already set")
325 return b.Bytes(), err
328 type singleWriter struct {
333 func (w *singleWriter) Write(p []byte) (int, error) {
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) {
343 return nil, errors.New("ssh: Stdout already set")
346 return nil, errors.New("ssh: Stderr already set")
352 return b.b.Bytes(), err
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 {
359 return errors.New("ssh: session already started")
362 ok, err := s.ch.SendRequest("shell", true, nil)
363 if err == nil && !ok {
364 return errors.New("ssh: could not start shell")
372 func (s *Session) start() error {
375 type F func(*Session)
376 for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} {
380 s.errors = make(chan error, len(s.copyFuncs))
381 for _, fn := range s.copyFuncs {
382 go func(fn func() error) {
389 // Wait waits for the remote command to exit.
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
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 {
401 return errors.New("ssh: session not started")
403 waitErr := <-s.exitStatus
405 if s.stdinPipeWriter != nil {
406 s.stdinPipeWriter.Close()
409 for range s.copyFuncs {
410 if err := <-s.errors; err != nil && copyError == nil {
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 {
426 wm.status = int(binary.BigEndian.Uint32(msg.Payload))
434 if err := Unmarshal(msg.Payload, &sigval); err != nil {
438 // Must sanitize strings?
439 wm.signal = sigval.Signal
440 wm.msg = sigval.Error
441 wm.lang = sigval.Lang
443 // This handles keepalives and matches
444 // OpenSSH's behaviour.
446 msg.Reply(false, nil)
454 // exit-status was never sent from server
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{}
463 if _, ok := signals[Signal(wm.signal)]; ok {
464 wm.status += signals[Signal(wm.signal)]
468 return &ExitError{wm}
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{}
475 func (e *ExitMissingError) Error() string {
476 return "wait: remote command exited without exit status or exit signal"
479 func (s *Session) stdin() {
485 stdin = new(bytes.Buffer)
489 _, err := io.Copy(w, s.Stdin)
490 w.CloseWithError(err)
492 stdin, s.stdinPipeWriter = r, w
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 {
503 func (s *Session) stdout() {
508 s.Stdout = ioutil.Discard
510 s.copyFuncs = append(s.copyFuncs, func() error {
511 _, err := io.Copy(s.Stdout, s.ch)
516 func (s *Session) stderr() {
521 s.Stderr = ioutil.Discard
523 s.copyFuncs = append(s.copyFuncs, func() error {
524 _, err := io.Copy(s.Stderr, s.ch.Stderr())
529 // sessionStdin reroutes Close to CloseWrite.
530 type sessionStdin struct {
535 func (s *sessionStdin) Close() error {
536 return s.ch.CloseWrite()
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) {
543 return nil, errors.New("ssh: Stdin already set")
546 return nil, errors.New("ssh: StdinPipe after process started")
549 return &sessionStdin{s.ch, s.ch}, nil
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) {
560 return nil, errors.New("ssh: Stdout already set")
563 return nil, errors.New("ssh: StdoutPipe after process started")
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) {
577 return nil, errors.New("ssh: Stderr already set")
580 return nil, errors.New("ssh: StderrPipe after process started")
583 return s.ch.Stderr(), nil
586 // newSession returns a new interactive session on the remote host.
587 func newSession(ch Channel, reqs <-chan *Request) (*Session, error) {
591 s.exitStatus = make(chan error, 1)
593 s.exitStatus <- s.wait(reqs)
599 // An ExitError reports unsuccessful completion of a remote command.
600 type ExitError struct {
604 func (e *ExitError) Error() string {
605 return e.Waitmsg.String()
608 // Waitmsg stores the information about an exited remote command
609 // as reported by Wait.
610 type Waitmsg struct {
617 // ExitStatus returns the exit status of the remote command.
618 func (w Waitmsg) ExitStatus() int {
622 // Signal returns the exit signal of the remote command if
623 // it was terminated violently.
624 func (w Waitmsg) Signal() string {
628 // Msg returns the exit message given by the remote command
629 func (w Waitmsg) Msg() string {
633 // Lang returns the language tag. See RFC 3066
634 func (w Waitmsg) Lang() string {
638 func (w Waitmsg) String() string {
639 str := fmt.Sprintf("Process exited with status %v", w.status)
641 str += fmt.Sprintf(" from signal %v", w.signal)
644 str += fmt.Sprintf(". Reason was: %v", w.msg)