github.com/glycerine/xcryptossh@v7.0.4+incompatible/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.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 select { 384 case s.errors <- fn(): 385 case <-s.ch.Done(): 386 return 387 } 388 }(fn) 389 } 390 return nil 391 } 392 393 // Wait waits for the remote command to exit. 394 // 395 // The returned error is nil if the command runs, has no problems 396 // copying stdin, stdout, and stderr, and exits with a zero exit 397 // status. 398 // 399 // If the remote server does not send an exit status, an error of type 400 // *ExitMissingError is returned. If the command completes 401 // unsuccessfully or is interrupted by a signal, the error is of type 402 // *ExitError. Other error types may be returned for I/O problems. 403 func (s *Session) Wait() error { 404 if !s.started { 405 return errors.New("ssh: session not started") 406 } 407 select { 408 case <-s.ch.Done(): 409 return io.EOF 410 411 case waitErr := <-s.exitStatus: 412 413 if s.stdinPipeWriter != nil { 414 s.stdinPipeWriter.Close() 415 } 416 var copyError error 417 for _ = range s.copyFuncs { 418 select { 419 case <-s.ch.Done(): 420 return io.EOF 421 case err := <-s.errors: 422 if err != nil && copyError == nil { 423 copyError = err 424 } 425 } 426 } 427 if waitErr != nil { 428 return waitErr 429 } 430 return copyError 431 } 432 } 433 434 func (s *Session) wait(reqs <-chan *Request) error { 435 wm := Waitmsg{status: -1} 436 // Wait for msg channel to be closed before returning. 437 for msg := range reqs { 438 switch msg.Type { 439 case "exit-status": 440 wm.status = int(binary.BigEndian.Uint32(msg.Payload)) 441 case "exit-signal": 442 var sigval struct { 443 Signal string 444 CoreDumped bool 445 Error string 446 Lang string 447 } 448 if err := Unmarshal(msg.Payload, &sigval); err != nil { 449 return err 450 } 451 452 // Must sanitize strings? 453 wm.signal = sigval.Signal 454 wm.msg = sigval.Error 455 wm.lang = sigval.Lang 456 default: 457 // This handles keepalives and matches 458 // OpenSSH's behaviour. 459 if msg.WantReply { 460 msg.Reply(false, nil) 461 } 462 } 463 } 464 if wm.status == 0 { 465 return nil 466 } 467 if wm.status == -1 { 468 // exit-status was never sent from server 469 if wm.signal == "" { 470 // signal was not sent either. RFC 4254 471 // section 6.10 recommends against this 472 // behavior, but it is allowed, so we let 473 // clients handle it. 474 return &ExitMissingError{} 475 } 476 wm.status = 128 477 if _, ok := signals[Signal(wm.signal)]; ok { 478 wm.status += signals[Signal(wm.signal)] 479 } 480 } 481 482 return &ExitError{wm} 483 } 484 485 // ExitMissingError is returned if a session is torn down cleanly, but 486 // the server sends no confirmation of the exit status. 487 type ExitMissingError struct{} 488 489 func (e *ExitMissingError) Error() string { 490 return "wait: remote command exited without exit status or exit signal" 491 } 492 493 func (s *Session) stdin() { 494 if s.stdinpipe { 495 return 496 } 497 var stdin io.Reader 498 if s.Stdin == nil { 499 stdin = new(bytes.Buffer) 500 } else { 501 r, w := io.Pipe() 502 go func() { 503 _, err := io.Copy(w, s.Stdin) 504 w.CloseWithError(err) 505 }() 506 stdin, s.stdinPipeWriter = r, w 507 } 508 s.copyFuncs = append(s.copyFuncs, func() error { 509 _, err := io.Copy(s.ch, stdin) 510 if err1 := s.ch.CloseWrite(); err == nil && err1 != io.EOF { 511 err = err1 512 } 513 return err 514 }) 515 } 516 517 func (s *Session) stdout() { 518 if s.stdoutpipe { 519 return 520 } 521 if s.Stdout == nil { 522 s.Stdout = ioutil.Discard 523 } 524 s.copyFuncs = append(s.copyFuncs, func() error { 525 _, err := io.Copy(s.Stdout, s.ch) 526 return err 527 }) 528 } 529 530 func (s *Session) stderr() { 531 if s.stderrpipe { 532 return 533 } 534 if s.Stderr == nil { 535 s.Stderr = ioutil.Discard 536 } 537 s.copyFuncs = append(s.copyFuncs, func() error { 538 _, err := io.Copy(s.Stderr, s.ch.Stderr()) 539 return err 540 }) 541 } 542 543 // sessionStdin reroutes Close to CloseWrite. 544 type sessionStdin struct { 545 io.Writer 546 ch Channel 547 } 548 549 func (s *sessionStdin) Close() error { 550 return s.ch.CloseWrite() 551 } 552 553 // StdinPipe returns a pipe that will be connected to the 554 // remote command's standard input when the command starts. 555 func (s *Session) StdinPipe() (io.WriteCloser, error) { 556 if s.Stdin != nil { 557 return nil, errors.New("ssh: Stdin already set") 558 } 559 if s.started { 560 return nil, errors.New("ssh: StdinPipe after process started") 561 } 562 s.stdinpipe = true 563 return &sessionStdin{s.ch, s.ch}, nil 564 } 565 566 // StdoutPipe returns a pipe that will be connected to the 567 // remote command's standard output when the command starts. 568 // There is a fixed amount of buffering that is shared between 569 // stdout and stderr streams. If the StdoutPipe reader is 570 // not serviced fast enough it may eventually cause the 571 // remote command to block. 572 func (s *Session) StdoutPipe() (io.Reader, error) { 573 if s.Stdout != nil { 574 return nil, errors.New("ssh: Stdout already set") 575 } 576 if s.started { 577 return nil, errors.New("ssh: StdoutPipe after process started") 578 } 579 s.stdoutpipe = true 580 return s.ch, nil 581 } 582 583 // StderrPipe returns a pipe that will be connected to the 584 // remote command's standard error when the command starts. 585 // There is a fixed amount of buffering that is shared between 586 // stdout and stderr streams. If the StderrPipe reader is 587 // not serviced fast enough it may eventually cause the 588 // remote command to block. 589 func (s *Session) StderrPipe() (io.Reader, error) { 590 if s.Stderr != nil { 591 return nil, errors.New("ssh: Stderr already set") 592 } 593 if s.started { 594 return nil, errors.New("ssh: StderrPipe after process started") 595 } 596 s.stderrpipe = true 597 return s.ch.Stderr(), nil 598 } 599 600 // newSession returns a new interactive session on the remote host. 601 func newSession(ch Channel, reqs <-chan *Request) (*Session, error) { 602 s := &Session{ 603 ch: ch, 604 } 605 s.exitStatus = make(chan error, 1) 606 go func() { 607 select { 608 case s.exitStatus <- s.wait(reqs): 609 case <-ch.Done(): 610 return 611 } 612 }() 613 614 return s, nil 615 } 616 617 // An ExitError reports unsuccessful completion of a remote command. 618 type ExitError struct { 619 Waitmsg 620 } 621 622 func (e *ExitError) Error() string { 623 return e.Waitmsg.String() 624 } 625 626 // Waitmsg stores the information about an exited remote command 627 // as reported by Wait. 628 type Waitmsg struct { 629 status int 630 signal string 631 msg string 632 lang string 633 } 634 635 // ExitStatus returns the exit status of the remote command. 636 func (w Waitmsg) ExitStatus() int { 637 return w.status 638 } 639 640 // Signal returns the exit signal of the remote command if 641 // it was terminated violently. 642 func (w Waitmsg) Signal() string { 643 return w.signal 644 } 645 646 // Msg returns the exit message given by the remote command 647 func (w Waitmsg) Msg() string { 648 return w.msg 649 } 650 651 // Lang returns the language tag. See RFC 3066 652 func (w Waitmsg) Lang() string { 653 return w.lang 654 } 655 656 func (w Waitmsg) String() string { 657 str := fmt.Sprintf("Process exited with status %v", w.status) 658 if w.signal != "" { 659 str += fmt.Sprintf(" from signal %v", w.signal) 660 } 661 if w.msg != "" { 662 str += fmt.Sprintf(". Reason was: %v", w.msg) 663 } 664 return str 665 }