~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/golang.org/x/crypto/ssh/session.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
        "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
        ISIG          = 50
 
88
        ICANON        = 51
 
89
        XCASE         = 52
 
90
        ECHO          = 53
 
91
        ECHOE         = 54
 
92
        ECHOK         = 55
 
93
        ECHONL        = 56
 
94
        NOFLSH        = 57
 
95
        TOSTOP        = 58
 
96
        IEXTEN        = 59
 
97
        ECHOCTL       = 60
 
98
        ECHOKE        = 61
 
99
        PENDIN        = 62
 
100
        OPOST         = 70
 
101
        OLCUC         = 71
 
102
        ONLCR         = 72
 
103
        OCRNL         = 73
 
104
        ONOCR         = 74
 
105
        ONLRET        = 75
 
106
        CS7           = 90
 
107
        CS8           = 91
 
108
        PARENB        = 92
 
109
        PARODD        = 93
 
110
        TTY_OP_ISPEED = 128
 
111
        TTY_OP_OSPEED = 129
 
112
)
 
113
 
 
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
 
118
        // bytes.Buffer.
 
119
        Stdin io.Reader
 
120
 
 
121
        // Stdout and Stderr specify the remote process's standard
 
122
        // output and error.
 
123
        //
 
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
 
128
        // command to block.
 
129
        Stdout io.Writer
 
130
        Stderr io.Writer
 
131
 
 
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
 
136
 
 
137
        // true if pipe method is active
 
138
        stdinpipe, stdoutpipe, stderrpipe bool
 
139
 
 
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
 
144
 
 
145
        exitStatus chan error
 
146
}
 
147
 
 
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)
 
152
}
 
153
 
 
154
func (s *Session) Close() error {
 
155
        return s.ch.Close()
 
156
}
 
157
 
 
158
// RFC 4254 Section 6.4.
 
159
type setenvRequest struct {
 
160
        Name  string
 
161
        Value string
 
162
}
 
163
 
 
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{
 
168
                Name:  name,
 
169
                Value: value,
 
170
        }
 
171
        ok, err := s.ch.SendRequest("env", true, Marshal(&msg))
 
172
        if err == nil && !ok {
 
173
                err = errors.New("ssh: setenv failed")
 
174
        }
 
175
        return err
 
176
}
 
177
 
 
178
// RFC 4254 Section 6.2.
 
179
type ptyRequestMsg struct {
 
180
        Term     string
 
181
        Columns  uint32
 
182
        Rows     uint32
 
183
        Width    uint32
 
184
        Height   uint32
 
185
        Modelist string
 
186
}
 
187
 
 
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 {
 
190
        var tm []byte
 
191
        for k, v := range termmodes {
 
192
                kv := struct {
 
193
                        Key byte
 
194
                        Val uint32
 
195
                }{k, v}
 
196
 
 
197
                tm = append(tm, Marshal(&kv)...)
 
198
        }
 
199
        tm = append(tm, tty_OP_END)
 
200
        req := ptyRequestMsg{
 
201
                Term:     term,
 
202
                Columns:  uint32(w),
 
203
                Rows:     uint32(h),
 
204
                Width:    uint32(w * 8),
 
205
                Height:   uint32(h * 8),
 
206
                Modelist: string(tm),
 
207
        }
 
208
        ok, err := s.ch.SendRequest("pty-req", true, Marshal(&req))
 
209
        if err == nil && !ok {
 
210
                err = errors.New("ssh: pty-req failed")
 
211
        }
 
212
        return err
 
213
}
 
214
 
 
215
// RFC 4254 Section 6.5.
 
216
type subsystemRequestMsg struct {
 
217
        Subsystem string
 
218
}
 
219
 
 
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,
 
225
        }
 
226
        ok, err := s.ch.SendRequest("subsystem", true, Marshal(&msg))
 
227
        if err == nil && !ok {
 
228
                err = errors.New("ssh: subsystem request failed")
 
229
        }
 
230
        return err
 
231
}
 
232
 
 
233
// RFC 4254 Section 6.9.
 
234
type signalMsg struct {
 
235
        Signal string
 
236
}
 
237
 
 
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 {
 
241
        msg := signalMsg{
 
242
                Signal: string(sig),
 
243
        }
 
244
 
 
245
        _, err := s.ch.SendRequest("signal", false, Marshal(&msg))
 
246
        return err
 
247
}
 
248
 
 
249
// RFC 4254 Section 6.5.
 
250
type execMsg struct {
 
251
        Command string
 
252
}
 
253
 
 
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 {
 
258
        if s.started {
 
259
                return errors.New("ssh: session already started")
 
260
        }
 
261
        req := execMsg{
 
262
                Command: cmd,
 
263
        }
 
264
 
 
265
        ok, err := s.ch.SendRequest("exec", true, Marshal(&req))
 
266
        if err == nil && !ok {
 
267
                err = fmt.Errorf("ssh: command %v failed", cmd)
 
268
        }
 
269
        if err != nil {
 
270
                return err
 
271
        }
 
272
        return s.start()
 
273
}
 
274
 
 
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.
 
279
//
 
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
 
282
// status.
 
283
//
 
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 {
 
288
        err := s.Start(cmd)
 
289
        if err != nil {
 
290
                return err
 
291
        }
 
292
        return s.Wait()
 
293
}
 
294
 
 
295
// Output runs cmd on the remote host and returns its standard output.
 
296
func (s *Session) Output(cmd string) ([]byte, error) {
 
297
        if s.Stdout != nil {
 
298
                return nil, errors.New("ssh: Stdout already set")
 
299
        }
 
300
        var b bytes.Buffer
 
301
        s.Stdout = &b
 
302
        err := s.Run(cmd)
 
303
        return b.Bytes(), err
 
304
}
 
305
 
 
306
type singleWriter struct {
 
307
        b  bytes.Buffer
 
308
        mu sync.Mutex
 
309
}
 
310
 
 
311
func (w *singleWriter) Write(p []byte) (int, error) {
 
312
        w.mu.Lock()
 
313
        defer w.mu.Unlock()
 
314
        return w.b.Write(p)
 
315
}
 
316
 
 
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) {
 
320
        if s.Stdout != nil {
 
321
                return nil, errors.New("ssh: Stdout already set")
 
322
        }
 
323
        if s.Stderr != nil {
 
324
                return nil, errors.New("ssh: Stderr already set")
 
325
        }
 
326
        var b singleWriter
 
327
        s.Stdout = &b
 
328
        s.Stderr = &b
 
329
        err := s.Run(cmd)
 
330
        return b.b.Bytes(), err
 
331
}
 
332
 
 
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 {
 
336
        if s.started {
 
337
                return errors.New("ssh: session already started")
 
338
        }
 
339
 
 
340
        ok, err := s.ch.SendRequest("shell", true, nil)
 
341
        if err == nil && !ok {
 
342
                return fmt.Errorf("ssh: cound not start shell")
 
343
        }
 
344
        if err != nil {
 
345
                return err
 
346
        }
 
347
        return s.start()
 
348
}
 
349
 
 
350
func (s *Session) start() error {
 
351
        s.started = true
 
352
 
 
353
        type F func(*Session)
 
354
        for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} {
 
355
                setupFd(s)
 
356
        }
 
357
 
 
358
        s.errors = make(chan error, len(s.copyFuncs))
 
359
        for _, fn := range s.copyFuncs {
 
360
                go func(fn func() error) {
 
361
                        s.errors <- fn()
 
362
                }(fn)
 
363
        }
 
364
        return nil
 
365
}
 
366
 
 
367
// Wait waits for the remote command to exit.
 
368
//
 
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
 
371
// status.
 
372
//
 
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 {
 
377
        if !s.started {
 
378
                return errors.New("ssh: session not started")
 
379
        }
 
380
        waitErr := <-s.exitStatus
 
381
 
 
382
        if s.stdinPipeWriter != nil {
 
383
                s.stdinPipeWriter.Close()
 
384
        }
 
385
        var copyError error
 
386
        for _ = range s.copyFuncs {
 
387
                if err := <-s.errors; err != nil && copyError == nil {
 
388
                        copyError = err
 
389
                }
 
390
        }
 
391
        if waitErr != nil {
 
392
                return waitErr
 
393
        }
 
394
        return copyError
 
395
}
 
396
 
 
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 {
 
401
                switch msg.Type {
 
402
                case "exit-status":
 
403
                        d := msg.Payload
 
404
                        wm.status = int(d[0])<<24 | int(d[1])<<16 | int(d[2])<<8 | int(d[3])
 
405
                case "exit-signal":
 
406
                        var sigval struct {
 
407
                                Signal     string
 
408
                                CoreDumped bool
 
409
                                Error      string
 
410
                                Lang       string
 
411
                        }
 
412
                        if err := Unmarshal(msg.Payload, &sigval); err != nil {
 
413
                                return err
 
414
                        }
 
415
 
 
416
                        // Must sanitize strings?
 
417
                        wm.signal = sigval.Signal
 
418
                        wm.msg = sigval.Error
 
419
                        wm.lang = sigval.Lang
 
420
                default:
 
421
                        // This handles keepalives and matches
 
422
                        // OpenSSH's behaviour.
 
423
                        if msg.WantReply {
 
424
                                msg.Reply(false, nil)
 
425
                        }
 
426
                }
 
427
        }
 
428
        if wm.status == 0 {
 
429
                return nil
 
430
        }
 
431
        if wm.status == -1 {
 
432
                // exit-status was never sent from server
 
433
                if wm.signal == "" {
 
434
                        return errors.New("wait: remote command exited without exit status or exit signal")
 
435
                }
 
436
                wm.status = 128
 
437
                if _, ok := signals[Signal(wm.signal)]; ok {
 
438
                        wm.status += signals[Signal(wm.signal)]
 
439
                }
 
440
        }
 
441
        return &ExitError{wm}
 
442
}
 
443
 
 
444
func (s *Session) stdin() {
 
445
        if s.stdinpipe {
 
446
                return
 
447
        }
 
448
        var stdin io.Reader
 
449
        if s.Stdin == nil {
 
450
                stdin = new(bytes.Buffer)
 
451
        } else {
 
452
                r, w := io.Pipe()
 
453
                go func() {
 
454
                        _, err := io.Copy(w, s.Stdin)
 
455
                        w.CloseWithError(err)
 
456
                }()
 
457
                stdin, s.stdinPipeWriter = r, w
 
458
        }
 
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 {
 
462
                        err = err1
 
463
                }
 
464
                return err
 
465
        })
 
466
}
 
467
 
 
468
func (s *Session) stdout() {
 
469
        if s.stdoutpipe {
 
470
                return
 
471
        }
 
472
        if s.Stdout == nil {
 
473
                s.Stdout = ioutil.Discard
 
474
        }
 
475
        s.copyFuncs = append(s.copyFuncs, func() error {
 
476
                _, err := io.Copy(s.Stdout, s.ch)
 
477
                return err
 
478
        })
 
479
}
 
480
 
 
481
func (s *Session) stderr() {
 
482
        if s.stderrpipe {
 
483
                return
 
484
        }
 
485
        if s.Stderr == nil {
 
486
                s.Stderr = ioutil.Discard
 
487
        }
 
488
        s.copyFuncs = append(s.copyFuncs, func() error {
 
489
                _, err := io.Copy(s.Stderr, s.ch.Stderr())
 
490
                return err
 
491
        })
 
492
}
 
493
 
 
494
// sessionStdin reroutes Close to CloseWrite.
 
495
type sessionStdin struct {
 
496
        io.Writer
 
497
        ch Channel
 
498
}
 
499
 
 
500
func (s *sessionStdin) Close() error {
 
501
        return s.ch.CloseWrite()
 
502
}
 
503
 
 
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) {
 
507
        if s.Stdin != nil {
 
508
                return nil, errors.New("ssh: Stdin already set")
 
509
        }
 
510
        if s.started {
 
511
                return nil, errors.New("ssh: StdinPipe after process started")
 
512
        }
 
513
        s.stdinpipe = true
 
514
        return &sessionStdin{s.ch, s.ch}, nil
 
515
}
 
516
 
 
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) {
 
524
        if s.Stdout != nil {
 
525
                return nil, errors.New("ssh: Stdout already set")
 
526
        }
 
527
        if s.started {
 
528
                return nil, errors.New("ssh: StdoutPipe after process started")
 
529
        }
 
530
        s.stdoutpipe = true
 
531
        return s.ch, nil
 
532
}
 
533
 
 
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) {
 
541
        if s.Stderr != nil {
 
542
                return nil, errors.New("ssh: Stderr already set")
 
543
        }
 
544
        if s.started {
 
545
                return nil, errors.New("ssh: StderrPipe after process started")
 
546
        }
 
547
        s.stderrpipe = true
 
548
        return s.ch.Stderr(), nil
 
549
}
 
550
 
 
551
// newSession returns a new interactive session on the remote host.
 
552
func newSession(ch Channel, reqs <-chan *Request) (*Session, error) {
 
553
        s := &Session{
 
554
                ch: ch,
 
555
        }
 
556
        s.exitStatus = make(chan error, 1)
 
557
        go func() {
 
558
                s.exitStatus <- s.wait(reqs)
 
559
        }()
 
560
 
 
561
        return s, nil
 
562
}
 
563
 
 
564
// An ExitError reports unsuccessful completion of a remote command.
 
565
type ExitError struct {
 
566
        Waitmsg
 
567
}
 
568
 
 
569
func (e *ExitError) Error() string {
 
570
        return e.Waitmsg.String()
 
571
}
 
572
 
 
573
// Waitmsg stores the information about an exited remote command
 
574
// as reported by Wait.
 
575
type Waitmsg struct {
 
576
        status int
 
577
        signal string
 
578
        msg    string
 
579
        lang   string
 
580
}
 
581
 
 
582
// ExitStatus returns the exit status of the remote command.
 
583
func (w Waitmsg) ExitStatus() int {
 
584
        return w.status
 
585
}
 
586
 
 
587
// Signal returns the exit signal of the remote command if
 
588
// it was terminated violently.
 
589
func (w Waitmsg) Signal() string {
 
590
        return w.signal
 
591
}
 
592
 
 
593
// Msg returns the exit message given by the remote command
 
594
func (w Waitmsg) Msg() string {
 
595
        return w.msg
 
596
}
 
597
 
 
598
// Lang returns the language tag. See RFC 3066
 
599
func (w Waitmsg) Lang() string {
 
600
        return w.lang
 
601
}
 
602
 
 
603
func (w Waitmsg) String() string {
 
604
        return fmt.Sprintf("Process exited with: %v. Reason was: %v (%v)", w.status, w.msg, w.signal)
 
605
}