// Copyright 2011 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.package ssh// Session implements an interactive session described in// "RFC 4254, section 6".import ()typeSignalstring// POSIX signals as listed in RFC 4254 Section 6.10.const (SIGABRTSignal = "ABRT"SIGALRMSignal = "ALRM"SIGFPESignal = "FPE"SIGHUPSignal = "HUP"SIGILLSignal = "ILL"SIGINTSignal = "INT"SIGKILLSignal = "KILL"SIGPIPESignal = "PIPE"SIGQUITSignal = "QUIT"SIGSEGVSignal = "SEGV"SIGTERMSignal = "TERM"SIGUSR1Signal = "USR1"SIGUSR2Signal = "USR2")var signals = map[Signal]int{SIGABRT: 6,SIGALRM: 14,SIGFPE: 8,SIGHUP: 1,SIGILL: 4,SIGINT: 2,SIGKILL: 9,SIGPIPE: 13,SIGQUIT: 3,SIGSEGV: 11,SIGTERM: 15,}typeTerminalModesmap[uint8]uint32// POSIX terminal mode flags as listed in RFC 4254 Section 8.const ( tty_OP_END = 0VINTR = 1VQUIT = 2VERASE = 3VKILL = 4VEOF = 5VEOL = 6VEOL2 = 7VSTART = 8VSTOP = 9VSUSP = 10VDSUSP = 11VREPRINT = 12VWERASE = 13VLNEXT = 14VFLUSH = 15VSWTCH = 16VSTATUS = 17VDISCARD = 18IGNPAR = 30PARMRK = 31INPCK = 32ISTRIP = 33INLCR = 34IGNCR = 35ICRNL = 36IUCLC = 37IXON = 38IXANY = 39IXOFF = 40IMAXBEL = 41IUTF8 = 42// RFC 8160ISIG = 50ICANON = 51XCASE = 52ECHO = 53ECHOE = 54ECHOK = 55ECHONL = 56NOFLSH = 57TOSTOP = 58IEXTEN = 59ECHOCTL = 60ECHOKE = 61PENDIN = 62OPOST = 70OLCUC = 71ONLCR = 72OCRNL = 73ONOCR = 74ONLRET = 75CS7 = 90CS8 = 91PARENB = 92PARODD = 93TTY_OP_ISPEED = 128TTY_OP_OSPEED = 129)// A Session represents a connection to a remote command or shell.typeSessionstruct {// Stdin specifies the remote process's standard input. // If Stdin is nil, the remote process reads from an empty // bytes.Buffer. Stdin io.Reader// Stdout and Stderr specify the remote process's standard // output and error. // // If either is nil, Run connects the corresponding file // descriptor to an instance of io.Discard. There is a // fixed amount of buffering that is shared for the two streams. // If either blocks it may eventually cause the remote // command to block. Stdout io.Writer Stderr io.Writer ch Channel// the channel backing this session started bool// true once Start, Run or Shell is invoked. copyFuncs []func() error errors chanerror// one send per copyFunc// true if pipe method is active stdinpipe, stdoutpipe, stderrpipe bool// stdinPipeWriter is non-nil if StdinPipe has not been called // and Stdin was specified by the user; it is the write end of // a pipe connecting Session.Stdin to the stdin channel. stdinPipeWriter io.WriteCloser exitStatus chanerror}// SendRequest sends an out-of-band channel request on the SSH channel// underlying the session.func ( *Session) ( string, bool, []byte) (bool, error) {return .ch.SendRequest(, , )}func ( *Session) () error {return .ch.Close()}// RFC 4254 Section 6.4.type setenvRequest struct { Name string Value string}// Setenv sets an environment variable that will be applied to any// command executed by Shell or Run.func ( *Session) (, string) error { := setenvRequest{Name: ,Value: , } , := .ch.SendRequest("env", true, Marshal(&))if == nil && ! { = errors.New("ssh: setenv failed") }return}// RFC 4254 Section 6.2.type ptyRequestMsg struct { Term string Columns uint32 Rows uint32 Width uint32 Height uint32 Modelist string}// RequestPty requests the association of a pty with the session on the remote host.func ( *Session) ( string, , int, TerminalModes) error {var []bytefor , := range { := struct {byteuint32 }{, } = append(, Marshal(&)...) } = append(, tty_OP_END) := ptyRequestMsg{Term: ,Columns: uint32(),Rows: uint32(),Width: uint32( * 8),Height: uint32( * 8),Modelist: string(), } , := .ch.SendRequest("pty-req", true, Marshal(&))if == nil && ! { = errors.New("ssh: pty-req failed") }return}// RFC 4254 Section 6.5.type subsystemRequestMsg struct { Subsystem string}// RequestSubsystem requests the association of a subsystem with the session on the remote host.// A subsystem is a predefined command that runs in the background when the ssh session is initiatedfunc ( *Session) ( string) error { := subsystemRequestMsg{Subsystem: , } , := .ch.SendRequest("subsystem", true, Marshal(&))if == nil && ! { = errors.New("ssh: subsystem request failed") }return}// RFC 4254 Section 6.7.type ptyWindowChangeMsg struct { Columns uint32 Rows uint32 Width uint32 Height uint32}// WindowChange informs the remote host about a terminal window dimension change to h rows and w columns.func ( *Session) (, int) error { := ptyWindowChangeMsg{Columns: uint32(),Rows: uint32(),Width: uint32( * 8),Height: uint32( * 8), } , := .ch.SendRequest("window-change", false, Marshal(&))return}// RFC 4254 Section 6.9.type signalMsg struct { Signal string}// Signal sends the given signal to the remote process.// sig is one of the SIG* constants.func ( *Session) ( Signal) error { := signalMsg{Signal: string(), } , := .ch.SendRequest("signal", false, Marshal(&))return}// RFC 4254 Section 6.5.type execMsg struct { Command string}// Start runs cmd on the remote host. Typically, the remote// server passes cmd to the shell for interpretation.// A Session only accepts one call to Run, Start or Shell.func ( *Session) ( string) error {if .started {returnerrors.New("ssh: session already started") } := execMsg{Command: , } , := .ch.SendRequest("exec", true, Marshal(&))if == nil && ! { = fmt.Errorf("ssh: command %v failed", ) }if != nil {return }return .start()}// Run runs cmd on the remote host. Typically, the remote// server passes cmd to the shell for interpretation.// A Session only accepts one call to Run, Start, Shell, Output,// or CombinedOutput.//// The returned error is nil if the command runs, has no problems// copying stdin, stdout, and stderr, and exits with a zero exit// status.//// If the remote server does not send an exit status, an error of type// *ExitMissingError is returned. If the command completes// unsuccessfully or is interrupted by a signal, the error is of type// *ExitError. Other error types may be returned for I/O problems.func ( *Session) ( string) error { := .Start()if != nil {return }return .Wait()}// Output runs cmd on the remote host and returns its standard output.func ( *Session) ( string) ([]byte, error) {if .Stdout != nil {returnnil, errors.New("ssh: Stdout already set") }varbytes.Buffer .Stdout = & := .Run()return .Bytes(), }type singleWriter struct { b bytes.Buffer mu sync.Mutex}func ( *singleWriter) ( []byte) (int, error) { .mu.Lock()defer .mu.Unlock()return .b.Write()}// CombinedOutput runs cmd on the remote host and returns its combined// standard output and standard error.func ( *Session) ( string) ([]byte, error) {if .Stdout != nil {returnnil, errors.New("ssh: Stdout already set") }if .Stderr != nil {returnnil, errors.New("ssh: Stderr already set") }varsingleWriter .Stdout = & .Stderr = & := .Run()return .b.Bytes(), }// Shell starts a login shell on the remote host. A Session only// accepts one call to Run, Start, Shell, Output, or CombinedOutput.func ( *Session) () error {if .started {returnerrors.New("ssh: session already started") } , := .ch.SendRequest("shell", true, nil)if == nil && ! {returnerrors.New("ssh: could not start shell") }if != nil {return }return .start()}func ( *Session) () error { .started = truetypefunc(*Session)for , := range []{(*Session).stdin, (*Session).stdout, (*Session).stderr} { () } .errors = make(chanerror, len(.copyFuncs))for , := range .copyFuncs {gofunc( func() error) { .errors <- () }() }returnnil}// Wait waits for the remote command to exit.//// The returned error is nil if the command runs, has no problems// copying stdin, stdout, and stderr, and exits with a zero exit// status.//// If the remote server does not send an exit status, an error of type// *ExitMissingError is returned. If the command completes// unsuccessfully or is interrupted by a signal, the error is of type// *ExitError. Other error types may be returned for I/O problems.func ( *Session) () error {if !.started {returnerrors.New("ssh: session not started") } := <-.exitStatusif .stdinPipeWriter != nil { .stdinPipeWriter.Close() }varerrorforrange .copyFuncs {if := <-.errors; != nil && == nil { = } }if != nil {return }return}func ( *Session) ( <-chan *Request) error { := Waitmsg{status: -1}// Wait for msg channel to be closed before returning.for := range {switch .Type {case"exit-status": .status = int(binary.BigEndian.Uint32(.Payload))case"exit-signal":varstruct {stringboolstringstring }if := Unmarshal(.Payload, &); != nil {return }// Must sanitize strings? .signal = . .msg = . .lang = .default:// This handles keepalives and matches // OpenSSH's behaviour.if .WantReply { .Reply(false, nil) } } }if .status == 0 {returnnil }if .status == -1 {// exit-status was never sent from serverif .signal == "" {// signal was not sent either. RFC 4254 // section 6.10 recommends against this // behavior, but it is allowed, so we let // clients handle it.return &ExitMissingError{} } .status = 128if , := signals[Signal(.signal)]; { .status += signals[Signal(.signal)] } }return &ExitError{}}// ExitMissingError is returned if a session is torn down cleanly, but// the server sends no confirmation of the exit status.typeExitMissingErrorstruct{}func ( *ExitMissingError) () string {return"wait: remote command exited without exit status or exit signal"}func ( *Session) () {if .stdinpipe {return }vario.Readerif .Stdin == nil { = new(bytes.Buffer) } else { , := io.Pipe()gofunc() { , := io.Copy(, .Stdin) .CloseWithError() }() , .stdinPipeWriter = , } .copyFuncs = append(.copyFuncs, func() error { , := io.Copy(.ch, )if := .ch.CloseWrite(); == nil && != io.EOF { = }return })}func ( *Session) () {if .stdoutpipe {return }if .Stdout == nil { .Stdout = io.Discard } .copyFuncs = append(.copyFuncs, func() error { , := io.Copy(.Stdout, .ch)return })}func ( *Session) () {if .stderrpipe {return }if .Stderr == nil { .Stderr = io.Discard } .copyFuncs = append(.copyFuncs, func() error { , := io.Copy(.Stderr, .ch.Stderr())return })}// sessionStdin reroutes Close to CloseWrite.type sessionStdin struct {io.Writer ch Channel}func ( *sessionStdin) () error {return .ch.CloseWrite()}// StdinPipe returns a pipe that will be connected to the// remote command's standard input when the command starts.func ( *Session) () (io.WriteCloser, error) {if .Stdin != nil {returnnil, errors.New("ssh: Stdin already set") }if .started {returnnil, errors.New("ssh: StdinPipe after process started") } .stdinpipe = truereturn &sessionStdin{.ch, .ch}, nil}// StdoutPipe returns a pipe that will be connected to the// remote command's standard output when the command starts.// There is a fixed amount of buffering that is shared between// stdout and stderr streams. If the StdoutPipe reader is// not serviced fast enough it may eventually cause the// remote command to block.func ( *Session) () (io.Reader, error) {if .Stdout != nil {returnnil, errors.New("ssh: Stdout already set") }if .started {returnnil, errors.New("ssh: StdoutPipe after process started") } .stdoutpipe = truereturn .ch, nil}// StderrPipe returns a pipe that will be connected to the// remote command's standard error when the command starts.// There is a fixed amount of buffering that is shared between// stdout and stderr streams. If the StderrPipe reader is// not serviced fast enough it may eventually cause the// remote command to block.func ( *Session) () (io.Reader, error) {if .Stderr != nil {returnnil, errors.New("ssh: Stderr already set") }if .started {returnnil, errors.New("ssh: StderrPipe after process started") } .stderrpipe = truereturn .ch.Stderr(), nil}// newSession returns a new interactive session on the remote host.func newSession( Channel, <-chan *Request) (*Session, error) { := &Session{ch: , } .exitStatus = make(chanerror, 1)gofunc() { .exitStatus <- .wait() }()return , nil}// An ExitError reports unsuccessful completion of a remote command.typeExitErrorstruct {Waitmsg}func ( *ExitError) () string {return .Waitmsg.String()}// Waitmsg stores the information about an exited remote command// as reported by Wait.typeWaitmsgstruct { status int signal string msg string lang string}// ExitStatus returns the exit status of the remote command.func ( Waitmsg) () int {return .status}// Signal returns the exit signal of the remote command if// it was terminated violently.func ( Waitmsg) () string {return .signal}// Msg returns the exit message given by the remote commandfunc ( Waitmsg) () string {return .msg}// Lang returns the language tag. See RFC 3066func ( Waitmsg) () string {return .lang}func ( Waitmsg) () string { := fmt.Sprintf("Process exited with status %v", .status)if .signal != "" { += fmt.Sprintf(" from signal %v", .signal) }if .msg != "" { += fmt.Sprintf(". Reason was: %v", .msg) }return}
The pages are generated with Goldsv0.6.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds.