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 5package ssh 6 7// Session implements an interactive session described in 8// "RFC 4254, section 6". 9 10import ( 11 "bytes" 12 "encoding/binary" 13 "errors" 14 "fmt" 15 "io" 16 "io/ioutil" 17 "sync" 18) 19 20type Signal string 21 22// POSIX signals as listed in RFC 4254 Section 6.10. 23const ( 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 39var 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 53type TerminalModes map[uint8]uint32 54 55// POSIX terminal mode flags as listed in RFC 4254 Section 8. 56const ( 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. 116type 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. 151func (s *Session) SendRequest(name string, wantReply bool, payload []byte) (bool, error) { 152 return s.ch.SendRequest(name, wantReply, payload) 153} 154 155func (s *Session) Close() error { 156 return s.ch.Close() 157} 158 159// RFC 4254 Section 6.4. 160type 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. 167func (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. 180type 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. 190func (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. 217type 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 223func (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. 235type 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. 241func (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. 251type 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. 258func (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. 289func (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. 298func (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 308type singleWriter struct { 309 b bytes.Buffer 310 mu sync.Mutex 311} 312 313func (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. 321func (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. 337func (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 352func (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. 379func (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 400func (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. 453type ExitMissingError struct{} 454 455func (e *ExitMissingError) Error() string { 456 return "wait: remote command exited without exit status or exit signal" 457} 458 459func (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 483func (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 496func (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. 510type sessionStdin struct { 511 io.Writer 512 ch Channel 513} 514 515func (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. 521func (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. 538func (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. 555func (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. 567func 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. 580type ExitError struct { 581 Waitmsg 582} 583 584func (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. 590type 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. 598func (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. 604func (w Waitmsg) Signal() string { 605 return w.signal 606} 607 608// Msg returns the exit message given by the remote command 609func (w Waitmsg) Msg() string { 610 return w.msg 611} 612 613// Lang returns the language tag. See RFC 3066 614func (w Waitmsg) Lang() string { 615 return w.lang 616} 617 618func (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} 628