]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/golang.org/x/crypto/ssh/session.go
Initial transfer of provider code
[github/fretlink/terraform-provider-statuscake.git] / vendor / golang.org / x / crypto / ssh / session.go
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 "encoding/binary"
13 "errors"
14 "fmt"
15 "io"
16 "io/ioutil"
17 "sync"
18 )
19
20 type Signal string
21
22 // POSIX signals as listed in RFC 4254 Section 6.10.
23 const (
24 SIGABRT Signal = "ABRT"
25 SIGALRM Signal = "ALRM"
26 SIGFPE Signal = "FPE"
27 SIGHUP Signal = "HUP"
28 SIGILL Signal = "ILL"
29 SIGINT Signal = "INT"
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"
37 )
38
39 var signals = map[Signal]int{
40 SIGABRT: 6,
41 SIGALRM: 14,
42 SIGFPE: 8,
43 SIGHUP: 1,
44 SIGILL: 4,
45 SIGINT: 2,
46 SIGKILL: 9,
47 SIGPIPE: 13,
48 SIGQUIT: 3,
49 SIGSEGV: 11,
50 SIGTERM: 15,
51 }
52
53 type TerminalModes map[uint8]uint32
54
55 // POSIX terminal mode flags as listed in RFC 4254 Section 8.
56 const (
57 tty_OP_END = 0
58 VINTR = 1
59 VQUIT = 2
60 VERASE = 3
61 VKILL = 4
62 VEOF = 5
63 VEOL = 6
64 VEOL2 = 7
65 VSTART = 8
66 VSTOP = 9
67 VSUSP = 10
68 VDSUSP = 11
69 VREPRINT = 12
70 VWERASE = 13
71 VLNEXT = 14
72 VFLUSH = 15
73 VSWTCH = 16
74 VSTATUS = 17
75 VDISCARD = 18
76 IGNPAR = 30
77 PARMRK = 31
78 INPCK = 32
79 ISTRIP = 33
80 INLCR = 34
81 IGNCR = 35
82 ICRNL = 36
83 IUCLC = 37
84 IXON = 38
85 IXANY = 39
86 IXOFF = 40
87 IMAXBEL = 41
88 ISIG = 50
89 ICANON = 51
90 XCASE = 52
91 ECHO = 53
92 ECHOE = 54
93 ECHOK = 55
94 ECHONL = 56
95 NOFLSH = 57
96 TOSTOP = 58
97 IEXTEN = 59
98 ECHOCTL = 60
99 ECHOKE = 61
100 PENDIN = 62
101 OPOST = 70
102 OLCUC = 71
103 ONLCR = 72
104 OCRNL = 73
105 ONOCR = 74
106 ONLRET = 75
107 CS7 = 90
108 CS8 = 91
109 PARENB = 92
110 PARODD = 93
111 TTY_OP_ISPEED = 128
112 TTY_OP_OSPEED = 129
113 )
114
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
119 // bytes.Buffer.
120 Stdin io.Reader
121
122 // Stdout and Stderr specify the remote process's standard
123 // output and error.
124 //
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
129 // command to block.
130 Stdout io.Writer
131 Stderr io.Writer
132
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
137
138 // true if pipe method is active
139 stdinpipe, stdoutpipe, stderrpipe bool
140
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
145
146 exitStatus chan error
147 }
148
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)
153 }
154
155 func (s *Session) Close() error {
156 return s.ch.Close()
157 }
158
159 // RFC 4254 Section 6.4.
160 type setenvRequest struct {
161 Name string
162 Value string
163 }
164
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{
169 Name: name,
170 Value: value,
171 }
172 ok, err := s.ch.SendRequest("env", true, Marshal(&msg))
173 if err == nil && !ok {
174 err = errors.New("ssh: setenv failed")
175 }
176 return err
177 }
178
179 // RFC 4254 Section 6.2.
180 type ptyRequestMsg struct {
181 Term string
182 Columns uint32
183 Rows uint32
184 Width uint32
185 Height uint32
186 Modelist string
187 }
188
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 {
191 var tm []byte
192 for k, v := range termmodes {
193 kv := struct {
194 Key byte
195 Val uint32
196 }{k, v}
197
198 tm = append(tm, Marshal(&kv)...)
199 }
200 tm = append(tm, tty_OP_END)
201 req := ptyRequestMsg{
202 Term: term,
203 Columns: uint32(w),
204 Rows: uint32(h),
205 Width: uint32(w * 8),
206 Height: uint32(h * 8),
207 Modelist: string(tm),
208 }
209 ok, err := s.ch.SendRequest("pty-req", true, Marshal(&req))
210 if err == nil && !ok {
211 err = errors.New("ssh: pty-req failed")
212 }
213 return err
214 }
215
216 // RFC 4254 Section 6.5.
217 type subsystemRequestMsg struct {
218 Subsystem string
219 }
220
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,
226 }
227 ok, err := s.ch.SendRequest("subsystem", true, Marshal(&msg))
228 if err == nil && !ok {
229 err = errors.New("ssh: subsystem request failed")
230 }
231 return err
232 }
233
234 // RFC 4254 Section 6.9.
235 type signalMsg struct {
236 Signal string
237 }
238
239 // Signal sends the given signal to the remote process.
240 // sig is one of the SIG* constants.
241 func (s *Session) Signal(sig Signal) error {
242 msg := signalMsg{
243 Signal: string(sig),
244 }
245
246 _, err := s.ch.SendRequest("signal", false, Marshal(&msg))
247 return err
248 }
249
250 // RFC 4254 Section 6.5.
251 type execMsg struct {
252 Command string
253 }
254
255 // Start runs cmd on the remote host. Typically, the remote
256 // server passes cmd to the shell for interpretation.
257 // A Session only accepts one call to Run, Start or Shell.
258 func (s *Session) Start(cmd string) error {
259 if s.started {
260 return errors.New("ssh: session already started")
261 }
262 req := execMsg{
263 Command: cmd,
264 }
265
266 ok, err := s.ch.SendRequest("exec", true, Marshal(&req))
267 if err == nil && !ok {
268 err = fmt.Errorf("ssh: command %v failed", cmd)
269 }
270 if err != nil {
271 return err
272 }
273 return s.start()
274 }
275
276 // Run runs cmd on the remote host. Typically, the remote
277 // server passes cmd to the shell for interpretation.
278 // A Session only accepts one call to Run, Start, Shell, Output,
279 // or CombinedOutput.
280 //
281 // The returned error is nil if the command runs, has no problems
282 // copying stdin, stdout, and stderr, and exits with a zero exit
283 // status.
284 //
285 // If the remote server does not send an exit status, an error of type
286 // *ExitMissingError is returned. If the command completes
287 // unsuccessfully or is interrupted by a signal, the error is of type
288 // *ExitError. Other error types may be returned for I/O problems.
289 func (s *Session) Run(cmd string) error {
290 err := s.Start(cmd)
291 if err != nil {
292 return err
293 }
294 return s.Wait()
295 }
296
297 // Output runs cmd on the remote host and returns its standard output.
298 func (s *Session) Output(cmd string) ([]byte, error) {
299 if s.Stdout != nil {
300 return nil, errors.New("ssh: Stdout already set")
301 }
302 var b bytes.Buffer
303 s.Stdout = &b
304 err := s.Run(cmd)
305 return b.Bytes(), err
306 }
307
308 type singleWriter struct {
309 b bytes.Buffer
310 mu sync.Mutex
311 }
312
313 func (w *singleWriter) Write(p []byte) (int, error) {
314 w.mu.Lock()
315 defer w.mu.Unlock()
316 return w.b.Write(p)
317 }
318
319 // CombinedOutput runs cmd on the remote host and returns its combined
320 // standard output and standard error.
321 func (s *Session) CombinedOutput(cmd string) ([]byte, error) {
322 if s.Stdout != nil {
323 return nil, errors.New("ssh: Stdout already set")
324 }
325 if s.Stderr != nil {
326 return nil, errors.New("ssh: Stderr already set")
327 }
328 var b singleWriter
329 s.Stdout = &b
330 s.Stderr = &b
331 err := s.Run(cmd)
332 return b.b.Bytes(), err
333 }
334
335 // Shell starts a login shell on the remote host. A Session only
336 // accepts one call to Run, Start, Shell, Output, or CombinedOutput.
337 func (s *Session) Shell() error {
338 if s.started {
339 return errors.New("ssh: session already started")
340 }
341
342 ok, err := s.ch.SendRequest("shell", true, nil)
343 if err == nil && !ok {
344 return errors.New("ssh: could not start shell")
345 }
346 if err != nil {
347 return err
348 }
349 return s.start()
350 }
351
352 func (s *Session) start() error {
353 s.started = true
354
355 type F func(*Session)
356 for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} {
357 setupFd(s)
358 }
359
360 s.errors = make(chan error, len(s.copyFuncs))
361 for _, fn := range s.copyFuncs {
362 go func(fn func() error) {
363 s.errors <- fn()
364 }(fn)
365 }
366 return nil
367 }
368
369 // Wait waits for the remote command to exit.
370 //
371 // The returned error is nil if the command runs, has no problems
372 // copying stdin, stdout, and stderr, and exits with a zero exit
373 // status.
374 //
375 // If the remote server does not send an exit status, an error of type
376 // *ExitMissingError is returned. If the command completes
377 // unsuccessfully or is interrupted by a signal, the error is of type
378 // *ExitError. Other error types may be returned for I/O problems.
379 func (s *Session) Wait() error {
380 if !s.started {
381 return errors.New("ssh: session not started")
382 }
383 waitErr := <-s.exitStatus
384
385 if s.stdinPipeWriter != nil {
386 s.stdinPipeWriter.Close()
387 }
388 var copyError error
389 for _ = range s.copyFuncs {
390 if err := <-s.errors; err != nil && copyError == nil {
391 copyError = err
392 }
393 }
394 if waitErr != nil {
395 return waitErr
396 }
397 return copyError
398 }
399
400 func (s *Session) wait(reqs <-chan *Request) error {
401 wm := Waitmsg{status: -1}
402 // Wait for msg channel to be closed before returning.
403 for msg := range reqs {
404 switch msg.Type {
405 case "exit-status":
406 wm.status = int(binary.BigEndian.Uint32(msg.Payload))
407 case "exit-signal":
408 var sigval struct {
409 Signal string
410 CoreDumped bool
411 Error string
412 Lang string
413 }
414 if err := Unmarshal(msg.Payload, &sigval); err != nil {
415 return err
416 }
417
418 // Must sanitize strings?
419 wm.signal = sigval.Signal
420 wm.msg = sigval.Error
421 wm.lang = sigval.Lang
422 default:
423 // This handles keepalives and matches
424 // OpenSSH's behaviour.
425 if msg.WantReply {
426 msg.Reply(false, nil)
427 }
428 }
429 }
430 if wm.status == 0 {
431 return nil
432 }
433 if wm.status == -1 {
434 // exit-status was never sent from server
435 if wm.signal == "" {
436 // signal was not sent either. RFC 4254
437 // section 6.10 recommends against this
438 // behavior, but it is allowed, so we let
439 // clients handle it.
440 return &ExitMissingError{}
441 }
442 wm.status = 128
443 if _, ok := signals[Signal(wm.signal)]; ok {
444 wm.status += signals[Signal(wm.signal)]
445 }
446 }
447
448 return &ExitError{wm}
449 }
450
451 // ExitMissingError is returned if a session is torn down cleanly, but
452 // the server sends no confirmation of the exit status.
453 type ExitMissingError struct{}
454
455 func (e *ExitMissingError) Error() string {
456 return "wait: remote command exited without exit status or exit signal"
457 }
458
459 func (s *Session) stdin() {
460 if s.stdinpipe {
461 return
462 }
463 var stdin io.Reader
464 if s.Stdin == nil {
465 stdin = new(bytes.Buffer)
466 } else {
467 r, w := io.Pipe()
468 go func() {
469 _, err := io.Copy(w, s.Stdin)
470 w.CloseWithError(err)
471 }()
472 stdin, s.stdinPipeWriter = r, w
473 }
474 s.copyFuncs = append(s.copyFuncs, func() error {
475 _, err := io.Copy(s.ch, stdin)
476 if err1 := s.ch.CloseWrite(); err == nil && err1 != io.EOF {
477 err = err1
478 }
479 return err
480 })
481 }
482
483 func (s *Session) stdout() {
484 if s.stdoutpipe {
485 return
486 }
487 if s.Stdout == nil {
488 s.Stdout = ioutil.Discard
489 }
490 s.copyFuncs = append(s.copyFuncs, func() error {
491 _, err := io.Copy(s.Stdout, s.ch)
492 return err
493 })
494 }
495
496 func (s *Session) stderr() {
497 if s.stderrpipe {
498 return
499 }
500 if s.Stderr == nil {
501 s.Stderr = ioutil.Discard
502 }
503 s.copyFuncs = append(s.copyFuncs, func() error {
504 _, err := io.Copy(s.Stderr, s.ch.Stderr())
505 return err
506 })
507 }
508
509 // sessionStdin reroutes Close to CloseWrite.
510 type sessionStdin struct {
511 io.Writer
512 ch Channel
513 }
514
515 func (s *sessionStdin) Close() error {
516 return s.ch.CloseWrite()
517 }
518
519 // StdinPipe returns a pipe that will be connected to the
520 // remote command's standard input when the command starts.
521 func (s *Session) StdinPipe() (io.WriteCloser, error) {
522 if s.Stdin != nil {
523 return nil, errors.New("ssh: Stdin already set")
524 }
525 if s.started {
526 return nil, errors.New("ssh: StdinPipe after process started")
527 }
528 s.stdinpipe = true
529 return &sessionStdin{s.ch, s.ch}, nil
530 }
531
532 // StdoutPipe returns a pipe that will be connected to the
533 // remote command's standard output when the command starts.
534 // There is a fixed amount of buffering that is shared between
535 // stdout and stderr streams. If the StdoutPipe reader is
536 // not serviced fast enough it may eventually cause the
537 // remote command to block.
538 func (s *Session) StdoutPipe() (io.Reader, error) {
539 if s.Stdout != nil {
540 return nil, errors.New("ssh: Stdout already set")
541 }
542 if s.started {
543 return nil, errors.New("ssh: StdoutPipe after process started")
544 }
545 s.stdoutpipe = true
546 return s.ch, nil
547 }
548
549 // StderrPipe returns a pipe that will be connected to the
550 // remote command's standard error when the command starts.
551 // There is a fixed amount of buffering that is shared between
552 // stdout and stderr streams. If the StderrPipe reader is
553 // not serviced fast enough it may eventually cause the
554 // remote command to block.
555 func (s *Session) StderrPipe() (io.Reader, error) {
556 if s.Stderr != nil {
557 return nil, errors.New("ssh: Stderr already set")
558 }
559 if s.started {
560 return nil, errors.New("ssh: StderrPipe after process started")
561 }
562 s.stderrpipe = true
563 return s.ch.Stderr(), nil
564 }
565
566 // newSession returns a new interactive session on the remote host.
567 func newSession(ch Channel, reqs <-chan *Request) (*Session, error) {
568 s := &Session{
569 ch: ch,
570 }
571 s.exitStatus = make(chan error, 1)
572 go func() {
573 s.exitStatus <- s.wait(reqs)
574 }()
575
576 return s, nil
577 }
578
579 // An ExitError reports unsuccessful completion of a remote command.
580 type ExitError struct {
581 Waitmsg
582 }
583
584 func (e *ExitError) Error() string {
585 return e.Waitmsg.String()
586 }
587
588 // Waitmsg stores the information about an exited remote command
589 // as reported by Wait.
590 type Waitmsg struct {
591 status int
592 signal string
593 msg string
594 lang string
595 }
596
597 // ExitStatus returns the exit status of the remote command.
598 func (w Waitmsg) ExitStatus() int {
599 return w.status
600 }
601
602 // Signal returns the exit signal of the remote command if
603 // it was terminated violently.
604 func (w Waitmsg) Signal() string {
605 return w.signal
606 }
607
608 // Msg returns the exit message given by the remote command
609 func (w Waitmsg) Msg() string {
610 return w.msg
611 }
612
613 // Lang returns the language tag. See RFC 3066
614 func (w Waitmsg) Lang() string {
615 return w.lang
616 }
617
618 func (w Waitmsg) String() string {
619 str := fmt.Sprintf("Process exited with status %v", w.status)
620 if w.signal != "" {
621 str += fmt.Sprintf(" from signal %v", w.signal)
622 }
623 if w.msg != "" {
624 str += fmt.Sprintf(". Reason was: %v", w.msg)
625 }
626 return str
627 }