github.com/maenmax/kairep@v0.0.0-20210218001208-55bf3df36788/src/golang.org/x/crypto/ssh/session.go (about) 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 }