session.go (15437B)
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 "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 IUTF8 = 42 // RFC 8160 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 io.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.7. 235 type ptyWindowChangeMsg struct { 236 Columns uint32 237 Rows uint32 238 Width uint32 239 Height uint32 240 } 241 242 // WindowChange informs the remote host about a terminal window dimension change to h rows and w columns. 243 func (s *Session) WindowChange(h, w int) error { 244 req := ptyWindowChangeMsg{ 245 Columns: uint32(w), 246 Rows: uint32(h), 247 Width: uint32(w * 8), 248 Height: uint32(h * 8), 249 } 250 _, err := s.ch.SendRequest("window-change", false, Marshal(&req)) 251 return err 252 } 253 254 // RFC 4254 Section 6.9. 255 type signalMsg struct { 256 Signal string 257 } 258 259 // Signal sends the given signal to the remote process. 260 // sig is one of the SIG* constants. 261 func (s *Session) Signal(sig Signal) error { 262 msg := signalMsg{ 263 Signal: string(sig), 264 } 265 266 _, err := s.ch.SendRequest("signal", false, Marshal(&msg)) 267 return err 268 } 269 270 // RFC 4254 Section 6.5. 271 type execMsg struct { 272 Command string 273 } 274 275 // Start 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 or Shell. 278 func (s *Session) Start(cmd string) error { 279 if s.started { 280 return errors.New("ssh: session already started") 281 } 282 req := execMsg{ 283 Command: cmd, 284 } 285 286 ok, err := s.ch.SendRequest("exec", true, Marshal(&req)) 287 if err == nil && !ok { 288 err = fmt.Errorf("ssh: command %v failed", cmd) 289 } 290 if err != nil { 291 return err 292 } 293 return s.start() 294 } 295 296 // Run runs cmd on the remote host. Typically, the remote 297 // server passes cmd to the shell for interpretation. 298 // A Session only accepts one call to Run, Start, Shell, Output, 299 // or CombinedOutput. 300 // 301 // The returned error is nil if the command runs, has no problems 302 // copying stdin, stdout, and stderr, and exits with a zero exit 303 // status. 304 // 305 // If the remote server does not send an exit status, an error of type 306 // *ExitMissingError is returned. If the command completes 307 // unsuccessfully or is interrupted by a signal, the error is of type 308 // *ExitError. Other error types may be returned for I/O problems. 309 func (s *Session) Run(cmd string) error { 310 err := s.Start(cmd) 311 if err != nil { 312 return err 313 } 314 return s.Wait() 315 } 316 317 // Output runs cmd on the remote host and returns its standard output. 318 func (s *Session) Output(cmd string) ([]byte, error) { 319 if s.Stdout != nil { 320 return nil, errors.New("ssh: Stdout already set") 321 } 322 var b bytes.Buffer 323 s.Stdout = &b 324 err := s.Run(cmd) 325 return b.Bytes(), err 326 } 327 328 type singleWriter struct { 329 b bytes.Buffer 330 mu sync.Mutex 331 } 332 333 func (w *singleWriter) Write(p []byte) (int, error) { 334 w.mu.Lock() 335 defer w.mu.Unlock() 336 return w.b.Write(p) 337 } 338 339 // CombinedOutput runs cmd on the remote host and returns its combined 340 // standard output and standard error. 341 func (s *Session) CombinedOutput(cmd string) ([]byte, error) { 342 if s.Stdout != nil { 343 return nil, errors.New("ssh: Stdout already set") 344 } 345 if s.Stderr != nil { 346 return nil, errors.New("ssh: Stderr already set") 347 } 348 var b singleWriter 349 s.Stdout = &b 350 s.Stderr = &b 351 err := s.Run(cmd) 352 return b.b.Bytes(), err 353 } 354 355 // Shell starts a login shell on the remote host. A Session only 356 // accepts one call to Run, Start, Shell, Output, or CombinedOutput. 357 func (s *Session) Shell() error { 358 if s.started { 359 return errors.New("ssh: session already started") 360 } 361 362 ok, err := s.ch.SendRequest("shell", true, nil) 363 if err == nil && !ok { 364 return errors.New("ssh: could not start shell") 365 } 366 if err != nil { 367 return err 368 } 369 return s.start() 370 } 371 372 func (s *Session) start() error { 373 s.started = true 374 375 type F func(*Session) 376 for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} { 377 setupFd(s) 378 } 379 380 s.errors = make(chan error, len(s.copyFuncs)) 381 for _, fn := range s.copyFuncs { 382 go func(fn func() error) { 383 s.errors <- fn() 384 }(fn) 385 } 386 return nil 387 } 388 389 // Wait waits for the remote command to exit. 390 // 391 // The returned error is nil if the command runs, has no problems 392 // copying stdin, stdout, and stderr, and exits with a zero exit 393 // status. 394 // 395 // If the remote server does not send an exit status, an error of type 396 // *ExitMissingError is returned. If the command completes 397 // unsuccessfully or is interrupted by a signal, the error is of type 398 // *ExitError. Other error types may be returned for I/O problems. 399 func (s *Session) Wait() error { 400 if !s.started { 401 return errors.New("ssh: session not started") 402 } 403 waitErr := <-s.exitStatus 404 405 if s.stdinPipeWriter != nil { 406 s.stdinPipeWriter.Close() 407 } 408 var copyError error 409 for range s.copyFuncs { 410 if err := <-s.errors; err != nil && copyError == nil { 411 copyError = err 412 } 413 } 414 if waitErr != nil { 415 return waitErr 416 } 417 return copyError 418 } 419 420 func (s *Session) wait(reqs <-chan *Request) error { 421 wm := Waitmsg{status: -1} 422 // Wait for msg channel to be closed before returning. 423 for msg := range reqs { 424 switch msg.Type { 425 case "exit-status": 426 wm.status = int(binary.BigEndian.Uint32(msg.Payload)) 427 case "exit-signal": 428 var sigval struct { 429 Signal string 430 CoreDumped bool 431 Error string 432 Lang string 433 } 434 if err := Unmarshal(msg.Payload, &sigval); err != nil { 435 return err 436 } 437 438 // Must sanitize strings? 439 wm.signal = sigval.Signal 440 wm.msg = sigval.Error 441 wm.lang = sigval.Lang 442 default: 443 // This handles keepalives and matches 444 // OpenSSH's behaviour. 445 if msg.WantReply { 446 msg.Reply(false, nil) 447 } 448 } 449 } 450 if wm.status == 0 { 451 return nil 452 } 453 if wm.status == -1 { 454 // exit-status was never sent from server 455 if wm.signal == "" { 456 // signal was not sent either. RFC 4254 457 // section 6.10 recommends against this 458 // behavior, but it is allowed, so we let 459 // clients handle it. 460 return &ExitMissingError{} 461 } 462 wm.status = 128 463 if _, ok := signals[Signal(wm.signal)]; ok { 464 wm.status += signals[Signal(wm.signal)] 465 } 466 } 467 468 return &ExitError{wm} 469 } 470 471 // ExitMissingError is returned if a session is torn down cleanly, but 472 // the server sends no confirmation of the exit status. 473 type ExitMissingError struct{} 474 475 func (e *ExitMissingError) Error() string { 476 return "wait: remote command exited without exit status or exit signal" 477 } 478 479 func (s *Session) stdin() { 480 if s.stdinpipe { 481 return 482 } 483 var stdin io.Reader 484 if s.Stdin == nil { 485 stdin = new(bytes.Buffer) 486 } else { 487 r, w := io.Pipe() 488 go func() { 489 _, err := io.Copy(w, s.Stdin) 490 w.CloseWithError(err) 491 }() 492 stdin, s.stdinPipeWriter = r, w 493 } 494 s.copyFuncs = append(s.copyFuncs, func() error { 495 _, err := io.Copy(s.ch, stdin) 496 if err1 := s.ch.CloseWrite(); err == nil && err1 != io.EOF { 497 err = err1 498 } 499 return err 500 }) 501 } 502 503 func (s *Session) stdout() { 504 if s.stdoutpipe { 505 return 506 } 507 if s.Stdout == nil { 508 s.Stdout = io.Discard 509 } 510 s.copyFuncs = append(s.copyFuncs, func() error { 511 _, err := io.Copy(s.Stdout, s.ch) 512 return err 513 }) 514 } 515 516 func (s *Session) stderr() { 517 if s.stderrpipe { 518 return 519 } 520 if s.Stderr == nil { 521 s.Stderr = io.Discard 522 } 523 s.copyFuncs = append(s.copyFuncs, func() error { 524 _, err := io.Copy(s.Stderr, s.ch.Stderr()) 525 return err 526 }) 527 } 528 529 // sessionStdin reroutes Close to CloseWrite. 530 type sessionStdin struct { 531 io.Writer 532 ch Channel 533 } 534 535 func (s *sessionStdin) Close() error { 536 return s.ch.CloseWrite() 537 } 538 539 // StdinPipe returns a pipe that will be connected to the 540 // remote command's standard input when the command starts. 541 func (s *Session) StdinPipe() (io.WriteCloser, error) { 542 if s.Stdin != nil { 543 return nil, errors.New("ssh: Stdin already set") 544 } 545 if s.started { 546 return nil, errors.New("ssh: StdinPipe after process started") 547 } 548 s.stdinpipe = true 549 return &sessionStdin{s.ch, s.ch}, nil 550 } 551 552 // StdoutPipe returns a pipe that will be connected to the 553 // remote command's standard output when the command starts. 554 // There is a fixed amount of buffering that is shared between 555 // stdout and stderr streams. If the StdoutPipe reader is 556 // not serviced fast enough it may eventually cause the 557 // remote command to block. 558 func (s *Session) StdoutPipe() (io.Reader, error) { 559 if s.Stdout != nil { 560 return nil, errors.New("ssh: Stdout already set") 561 } 562 if s.started { 563 return nil, errors.New("ssh: StdoutPipe after process started") 564 } 565 s.stdoutpipe = true 566 return s.ch, nil 567 } 568 569 // StderrPipe returns a pipe that will be connected to the 570 // remote command's standard error when the command starts. 571 // There is a fixed amount of buffering that is shared between 572 // stdout and stderr streams. If the StderrPipe reader is 573 // not serviced fast enough it may eventually cause the 574 // remote command to block. 575 func (s *Session) StderrPipe() (io.Reader, error) { 576 if s.Stderr != nil { 577 return nil, errors.New("ssh: Stderr already set") 578 } 579 if s.started { 580 return nil, errors.New("ssh: StderrPipe after process started") 581 } 582 s.stderrpipe = true 583 return s.ch.Stderr(), nil 584 } 585 586 // newSession returns a new interactive session on the remote host. 587 func newSession(ch Channel, reqs <-chan *Request) (*Session, error) { 588 s := &Session{ 589 ch: ch, 590 } 591 s.exitStatus = make(chan error, 1) 592 go func() { 593 s.exitStatus <- s.wait(reqs) 594 }() 595 596 return s, nil 597 } 598 599 // An ExitError reports unsuccessful completion of a remote command. 600 type ExitError struct { 601 Waitmsg 602 } 603 604 func (e *ExitError) Error() string { 605 return e.Waitmsg.String() 606 } 607 608 // Waitmsg stores the information about an exited remote command 609 // as reported by Wait. 610 type Waitmsg struct { 611 status int 612 signal string 613 msg string 614 lang string 615 } 616 617 // ExitStatus returns the exit status of the remote command. 618 func (w Waitmsg) ExitStatus() int { 619 return w.status 620 } 621 622 // Signal returns the exit signal of the remote command if 623 // it was terminated violently. 624 func (w Waitmsg) Signal() string { 625 return w.signal 626 } 627 628 // Msg returns the exit message given by the remote command 629 func (w Waitmsg) Msg() string { 630 return w.msg 631 } 632 633 // Lang returns the language tag. See RFC 3066 634 func (w Waitmsg) Lang() string { 635 return w.lang 636 } 637 638 func (w Waitmsg) String() string { 639 str := fmt.Sprintf("Process exited with status %v", w.status) 640 if w.signal != "" { 641 str += fmt.Sprintf(" from signal %v", w.signal) 642 } 643 if w.msg != "" { 644 str += fmt.Sprintf(". Reason was: %v", w.msg) 645 } 646 return str 647 }