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".
21
// POSIX signals as listed in RFC 4254 Section 6.10.
23
SIGABRT Signal = "ABRT"
24
SIGALRM Signal = "ALRM"
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"
38
var signals = map[Signal]int{
52
type TerminalModes map[uint8]uint32
54
// POSIX terminal mode flags as listed in RFC 4254 Section 8.
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
121
// Stdout and Stderr specify the remote process's standard
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
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
137
// true if pipe method is active
138
stdinpipe, stdoutpipe, stderrpipe bool
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
145
exitStatus chan error
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)
154
func (s *Session) Close() error {
158
// RFC 4254 Section 6.4.
159
type setenvRequest struct {
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{
171
ok, err := s.ch.SendRequest("env", true, Marshal(&msg))
172
if err == nil && !ok {
173
err = errors.New("ssh: setenv failed")
178
// RFC 4254 Section 6.2.
179
type ptyRequestMsg struct {
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 {
191
for k, v := range termmodes {
197
tm = append(tm, Marshal(&kv)...)
199
tm = append(tm, tty_OP_END)
200
req := ptyRequestMsg{
204
Width: uint32(w * 8),
205
Height: uint32(h * 8),
206
Modelist: string(tm),
208
ok, err := s.ch.SendRequest("pty-req", true, Marshal(&req))
209
if err == nil && !ok {
210
err = errors.New("ssh: pty-req failed")
215
// RFC 4254 Section 6.5.
216
type subsystemRequestMsg struct {
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,
226
ok, err := s.ch.SendRequest("subsystem", true, Marshal(&msg))
227
if err == nil && !ok {
228
err = errors.New("ssh: subsystem request failed")
233
// RFC 4254 Section 6.9.
234
type signalMsg struct {
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 {
245
_, err := s.ch.SendRequest("signal", false, Marshal(&msg))
249
// RFC 4254 Section 6.5.
250
type execMsg struct {
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 {
259
return errors.New("ssh: session already started")
265
ok, err := s.ch.SendRequest("exec", true, Marshal(&req))
266
if err == nil && !ok {
267
err = fmt.Errorf("ssh: command %v failed", cmd)
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.
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
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 {
295
// Output runs cmd on the remote host and returns its standard output.
296
func (s *Session) Output(cmd string) ([]byte, error) {
298
return nil, errors.New("ssh: Stdout already set")
303
return b.Bytes(), err
306
type singleWriter struct {
311
func (w *singleWriter) Write(p []byte) (int, error) {
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) {
321
return nil, errors.New("ssh: Stdout already set")
324
return nil, errors.New("ssh: Stderr already set")
330
return b.b.Bytes(), err
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 {
337
return errors.New("ssh: session already started")
340
ok, err := s.ch.SendRequest("shell", true, nil)
341
if err == nil && !ok {
342
return fmt.Errorf("ssh: cound not start shell")
350
func (s *Session) start() error {
353
type F func(*Session)
354
for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} {
358
s.errors = make(chan error, len(s.copyFuncs))
359
for _, fn := range s.copyFuncs {
360
go func(fn func() error) {
367
// Wait waits for the remote command to exit.
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
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 {
378
return errors.New("ssh: session not started")
380
waitErr := <-s.exitStatus
382
if s.stdinPipeWriter != nil {
383
s.stdinPipeWriter.Close()
386
for _ = range s.copyFuncs {
387
if err := <-s.errors; err != nil && copyError == nil {
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 {
404
wm.status = int(d[0])<<24 | int(d[1])<<16 | int(d[2])<<8 | int(d[3])
412
if err := Unmarshal(msg.Payload, &sigval); err != nil {
416
// Must sanitize strings?
417
wm.signal = sigval.Signal
418
wm.msg = sigval.Error
419
wm.lang = sigval.Lang
421
// This handles keepalives and matches
422
// OpenSSH's behaviour.
424
msg.Reply(false, nil)
432
// exit-status was never sent from server
434
return errors.New("wait: remote command exited without exit status or exit signal")
437
if _, ok := signals[Signal(wm.signal)]; ok {
438
wm.status += signals[Signal(wm.signal)]
441
return &ExitError{wm}
444
func (s *Session) stdin() {
450
stdin = new(bytes.Buffer)
454
_, err := io.Copy(w, s.Stdin)
455
w.CloseWithError(err)
457
stdin, s.stdinPipeWriter = r, w
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 {
468
func (s *Session) stdout() {
473
s.Stdout = ioutil.Discard
475
s.copyFuncs = append(s.copyFuncs, func() error {
476
_, err := io.Copy(s.Stdout, s.ch)
481
func (s *Session) stderr() {
486
s.Stderr = ioutil.Discard
488
s.copyFuncs = append(s.copyFuncs, func() error {
489
_, err := io.Copy(s.Stderr, s.ch.Stderr())
494
// sessionStdin reroutes Close to CloseWrite.
495
type sessionStdin struct {
500
func (s *sessionStdin) Close() error {
501
return s.ch.CloseWrite()
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) {
508
return nil, errors.New("ssh: Stdin already set")
511
return nil, errors.New("ssh: StdinPipe after process started")
514
return &sessionStdin{s.ch, s.ch}, nil
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) {
525
return nil, errors.New("ssh: Stdout already set")
528
return nil, errors.New("ssh: StdoutPipe after process started")
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) {
542
return nil, errors.New("ssh: Stderr already set")
545
return nil, errors.New("ssh: StderrPipe after process started")
548
return s.ch.Stderr(), nil
551
// newSession returns a new interactive session on the remote host.
552
func newSession(ch Channel, reqs <-chan *Request) (*Session, error) {
556
s.exitStatus = make(chan error, 1)
558
s.exitStatus <- s.wait(reqs)
564
// An ExitError reports unsuccessful completion of a remote command.
565
type ExitError struct {
569
func (e *ExitError) Error() string {
570
return e.Waitmsg.String()
573
// Waitmsg stores the information about an exited remote command
574
// as reported by Wait.
575
type Waitmsg struct {
582
// ExitStatus returns the exit status of the remote command.
583
func (w Waitmsg) ExitStatus() int {
587
// Signal returns the exit signal of the remote command if
588
// it was terminated violently.
589
func (w Waitmsg) Signal() string {
593
// Msg returns the exit message given by the remote command
594
func (w Waitmsg) Msg() string {
598
// Lang returns the language tag. See RFC 3066
599
func (w Waitmsg) Lang() string {
603
func (w Waitmsg) String() string {
604
return fmt.Sprintf("Process exited with: %v. Reason was: %v (%v)", w.status, w.msg, w.signal)