github.com/inflatablewoman/deis@v1.0.1-0.20141111034523-a4511c46a6ce/deisctl/Godeps/_workspace/src/code.google.com/p/go.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 "errors" 13 "fmt" 14 "io" 15 "io/ioutil" 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 ISIG = 50 88 ICANON = 51 89 XCASE = 52 90 ECHO = 53 91 ECHOE = 54 92 ECHOK = 55 93 ECHONL = 56 94 NOFLSH = 57 95 TOSTOP = 58 96 IEXTEN = 59 97 ECHOCTL = 60 98 ECHOKE = 61 99 PENDIN = 62 100 OPOST = 70 101 OLCUC = 71 102 ONLCR = 72 103 OCRNL = 73 104 ONOCR = 74 105 ONLRET = 75 106 CS7 = 90 107 CS8 = 91 108 PARENB = 92 109 PARODD = 93 110 TTY_OP_ISPEED = 128 111 TTY_OP_OSPEED = 129 112 ) 113 114 // A Session represents a connection to a remote command or shell. 115 type Session struct { 116 // Stdin specifies the remote process's standard input. 117 // If Stdin is nil, the remote process reads from an empty 118 // bytes.Buffer. 119 Stdin io.Reader 120 121 // Stdout and Stderr specify the remote process's standard 122 // output and error. 123 // 124 // If either is nil, Run connects the corresponding file 125 // descriptor to an instance of ioutil.Discard. There is a 126 // fixed amount of buffering that is shared for the two streams. 127 // If either blocks it may eventually cause the remote 128 // command to block. 129 Stdout io.Writer 130 Stderr io.Writer 131 132 ch Channel // the channel backing this session 133 started bool // true once Start, Run or Shell is invoked. 134 copyFuncs []func() error 135 errors chan error // one send per copyFunc 136 137 // true if pipe method is active 138 stdinpipe, stdoutpipe, stderrpipe bool 139 140 // stdinPipeWriter is non-nil if StdinPipe has not been called 141 // and Stdin was specified by the user; it is the write end of 142 // a pipe connecting Session.Stdin to the stdin channel. 143 stdinPipeWriter io.WriteCloser 144 145 exitStatus chan error 146 } 147 148 // SendRequest sends an out-of-band channel request on the SSH channel 149 // underlying the session. 150 func (s *Session) SendRequest(name string, wantReply bool, payload []byte) (bool, error) { 151 return s.ch.SendRequest(name, wantReply, payload) 152 } 153 154 func (s *Session) Close() error { 155 return s.ch.Close() 156 } 157 158 // RFC 4254 Section 6.4. 159 type setenvRequest struct { 160 Name string 161 Value string 162 } 163 164 // Setenv sets an environment variable that will be applied to any 165 // command executed by Shell or Run. 166 func (s *Session) Setenv(name, value string) error { 167 msg := setenvRequest{ 168 Name: name, 169 Value: value, 170 } 171 ok, err := s.ch.SendRequest("env", true, Marshal(&msg)) 172 if err == nil && !ok { 173 err = errors.New("ssh: setenv failed") 174 } 175 return err 176 } 177 178 // RFC 4254 Section 6.2. 179 type ptyRequestMsg struct { 180 Term string 181 Columns uint32 182 Rows uint32 183 Width uint32 184 Height uint32 185 Modelist string 186 } 187 188 // RequestPty requests the association of a pty with the session on the remote host. 189 func (s *Session) RequestPty(term string, h, w int, termmodes TerminalModes) error { 190 var tm []byte 191 for k, v := range termmodes { 192 kv := struct { 193 Key byte 194 Val uint32 195 }{k, v} 196 197 tm = append(tm, Marshal(&kv)...) 198 } 199 tm = append(tm, tty_OP_END) 200 req := ptyRequestMsg{ 201 Term: term, 202 Columns: uint32(w), 203 Rows: uint32(h), 204 Width: uint32(w * 8), 205 Height: uint32(h * 8), 206 Modelist: string(tm), 207 } 208 ok, err := s.ch.SendRequest("pty-req", true, Marshal(&req)) 209 if err == nil && !ok { 210 err = errors.New("ssh: pty-req failed") 211 } 212 return err 213 } 214 215 // RFC 4254 Section 6.5. 216 type subsystemRequestMsg struct { 217 Subsystem string 218 } 219 220 // RequestSubsystem requests the association of a subsystem with the session on the remote host. 221 // A subsystem is a predefined command that runs in the background when the ssh session is initiated 222 func (s *Session) RequestSubsystem(subsystem string) error { 223 msg := subsystemRequestMsg{ 224 Subsystem: subsystem, 225 } 226 ok, err := s.ch.SendRequest("subsystem", true, Marshal(&msg)) 227 if err == nil && !ok { 228 err = errors.New("ssh: subsystem request failed") 229 } 230 return err 231 } 232 233 // RFC 4254 Section 6.9. 234 type signalMsg struct { 235 Signal string 236 } 237 238 // Signal sends the given signal to the remote process. 239 // sig is one of the SIG* constants. 240 func (s *Session) Signal(sig Signal) error { 241 msg := signalMsg{ 242 Signal: string(sig), 243 } 244 245 _, err := s.ch.SendRequest("signal", false, Marshal(&msg)) 246 return err 247 } 248 249 // RFC 4254 Section 6.5. 250 type execMsg struct { 251 Command string 252 } 253 254 // Start runs cmd on the remote host. Typically, the remote 255 // server passes cmd to the shell for interpretation. 256 // A Session only accepts one call to Run, Start or Shell. 257 func (s *Session) Start(cmd string) error { 258 if s.started { 259 return errors.New("ssh: session already started") 260 } 261 req := execMsg{ 262 Command: cmd, 263 } 264 265 ok, err := s.ch.SendRequest("exec", true, Marshal(&req)) 266 if err == nil && !ok { 267 err = fmt.Errorf("ssh: command %v failed", cmd) 268 } 269 if err != nil { 270 return err 271 } 272 return s.start() 273 } 274 275 // Run 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, Shell, Output, 278 // or CombinedOutput. 279 // 280 // The returned error is nil if the command runs, has no problems 281 // copying stdin, stdout, and stderr, and exits with a zero exit 282 // status. 283 // 284 // If the command fails to run or doesn't complete successfully, the 285 // error is of type *ExitError. Other error types may be 286 // returned for I/O problems. 287 func (s *Session) Run(cmd string) error { 288 err := s.Start(cmd) 289 if err != nil { 290 return err 291 } 292 return s.Wait() 293 } 294 295 // Output runs cmd on the remote host and returns its standard output. 296 func (s *Session) Output(cmd string) ([]byte, error) { 297 if s.Stdout != nil { 298 return nil, errors.New("ssh: Stdout already set") 299 } 300 var b bytes.Buffer 301 s.Stdout = &b 302 err := s.Run(cmd) 303 return b.Bytes(), err 304 } 305 306 type singleWriter struct { 307 b bytes.Buffer 308 mu sync.Mutex 309 } 310 311 func (w *singleWriter) Write(p []byte) (int, error) { 312 w.mu.Lock() 313 defer w.mu.Unlock() 314 return w.b.Write(p) 315 } 316 317 // CombinedOutput runs cmd on the remote host and returns its combined 318 // standard output and standard error. 319 func (s *Session) CombinedOutput(cmd string) ([]byte, error) { 320 if s.Stdout != nil { 321 return nil, errors.New("ssh: Stdout already set") 322 } 323 if s.Stderr != nil { 324 return nil, errors.New("ssh: Stderr already set") 325 } 326 var b singleWriter 327 s.Stdout = &b 328 s.Stderr = &b 329 err := s.Run(cmd) 330 return b.b.Bytes(), err 331 } 332 333 // Shell starts a login shell on the remote host. A Session only 334 // accepts one call to Run, Start, Shell, Output, or CombinedOutput. 335 func (s *Session) Shell() error { 336 if s.started { 337 return errors.New("ssh: session already started") 338 } 339 340 ok, err := s.ch.SendRequest("shell", true, nil) 341 if err == nil && !ok { 342 return fmt.Errorf("ssh: cound not start shell") 343 } 344 if err != nil { 345 return err 346 } 347 return s.start() 348 } 349 350 func (s *Session) start() error { 351 s.started = true 352 353 type F func(*Session) 354 for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} { 355 setupFd(s) 356 } 357 358 s.errors = make(chan error, len(s.copyFuncs)) 359 for _, fn := range s.copyFuncs { 360 go func(fn func() error) { 361 s.errors <- fn() 362 }(fn) 363 } 364 return nil 365 } 366 367 // Wait waits for the remote command to exit. 368 // 369 // The returned error is nil if the command runs, has no problems 370 // copying stdin, stdout, and stderr, and exits with a zero exit 371 // status. 372 // 373 // If the command fails to run or doesn't complete successfully, the 374 // error is of type *ExitError. Other error types may be 375 // returned for I/O problems. 376 func (s *Session) Wait() error { 377 if !s.started { 378 return errors.New("ssh: session not started") 379 } 380 waitErr := <-s.exitStatus 381 382 if s.stdinPipeWriter != nil { 383 s.stdinPipeWriter.Close() 384 } 385 var copyError error 386 for _ = range s.copyFuncs { 387 if err := <-s.errors; err != nil && copyError == nil { 388 copyError = err 389 } 390 } 391 if waitErr != nil { 392 return waitErr 393 } 394 return copyError 395 } 396 397 func (s *Session) wait(reqs <-chan *Request) error { 398 wm := Waitmsg{status: -1} 399 // Wait for msg channel to be closed before returning. 400 for msg := range reqs { 401 switch msg.Type { 402 case "exit-status": 403 d := msg.Payload 404 wm.status = int(d[0])<<24 | int(d[1])<<16 | int(d[2])<<8 | int(d[3]) 405 case "exit-signal": 406 var sigval struct { 407 Signal string 408 CoreDumped bool 409 Error string 410 Lang string 411 } 412 if err := Unmarshal(msg.Payload, &sigval); err != nil { 413 return err 414 } 415 416 // Must sanitize strings? 417 wm.signal = sigval.Signal 418 wm.msg = sigval.Error 419 wm.lang = sigval.Lang 420 default: 421 // This handles keepalives and matches 422 // OpenSSH's behaviour. 423 if msg.WantReply { 424 msg.Reply(false, nil) 425 } 426 } 427 } 428 if wm.status == 0 { 429 return nil 430 } 431 if wm.status == -1 { 432 // exit-status was never sent from server 433 if wm.signal == "" { 434 return errors.New("wait: remote command exited without exit status or exit signal") 435 } 436 wm.status = 128 437 if _, ok := signals[Signal(wm.signal)]; ok { 438 wm.status += signals[Signal(wm.signal)] 439 } 440 } 441 return &ExitError{wm} 442 } 443 444 func (s *Session) stdin() { 445 if s.stdinpipe { 446 return 447 } 448 var stdin io.Reader 449 if s.Stdin == nil { 450 stdin = new(bytes.Buffer) 451 } else { 452 r, w := io.Pipe() 453 go func() { 454 _, err := io.Copy(w, s.Stdin) 455 w.CloseWithError(err) 456 }() 457 stdin, s.stdinPipeWriter = r, w 458 } 459 s.copyFuncs = append(s.copyFuncs, func() error { 460 _, err := io.Copy(s.ch, stdin) 461 if err1 := s.ch.CloseWrite(); err == nil && err1 != io.EOF { 462 err = err1 463 } 464 return err 465 }) 466 } 467 468 func (s *Session) stdout() { 469 if s.stdoutpipe { 470 return 471 } 472 if s.Stdout == nil { 473 s.Stdout = ioutil.Discard 474 } 475 s.copyFuncs = append(s.copyFuncs, func() error { 476 _, err := io.Copy(s.Stdout, s.ch) 477 return err 478 }) 479 } 480 481 func (s *Session) stderr() { 482 if s.stderrpipe { 483 return 484 } 485 if s.Stderr == nil { 486 s.Stderr = ioutil.Discard 487 } 488 s.copyFuncs = append(s.copyFuncs, func() error { 489 _, err := io.Copy(s.Stderr, s.ch.Stderr()) 490 return err 491 }) 492 } 493 494 // sessionStdin reroutes Close to CloseWrite. 495 type sessionStdin struct { 496 io.Writer 497 ch Channel 498 } 499 500 func (s *sessionStdin) Close() error { 501 return s.ch.CloseWrite() 502 } 503 504 // StdinPipe returns a pipe that will be connected to the 505 // remote command's standard input when the command starts. 506 func (s *Session) StdinPipe() (io.WriteCloser, error) { 507 if s.Stdin != nil { 508 return nil, errors.New("ssh: Stdin already set") 509 } 510 if s.started { 511 return nil, errors.New("ssh: StdinPipe after process started") 512 } 513 s.stdinpipe = true 514 return &sessionStdin{s.ch, s.ch}, nil 515 } 516 517 // StdoutPipe returns a pipe that will be connected to the 518 // remote command's standard output when the command starts. 519 // There is a fixed amount of buffering that is shared between 520 // stdout and stderr streams. If the StdoutPipe reader is 521 // not serviced fast enough it may eventually cause the 522 // remote command to block. 523 func (s *Session) StdoutPipe() (io.Reader, error) { 524 if s.Stdout != nil { 525 return nil, errors.New("ssh: Stdout already set") 526 } 527 if s.started { 528 return nil, errors.New("ssh: StdoutPipe after process started") 529 } 530 s.stdoutpipe = true 531 return s.ch, nil 532 } 533 534 // StderrPipe returns a pipe that will be connected to the 535 // remote command's standard error when the command starts. 536 // There is a fixed amount of buffering that is shared between 537 // stdout and stderr streams. If the StderrPipe reader is 538 // not serviced fast enough it may eventually cause the 539 // remote command to block. 540 func (s *Session) StderrPipe() (io.Reader, error) { 541 if s.Stderr != nil { 542 return nil, errors.New("ssh: Stderr already set") 543 } 544 if s.started { 545 return nil, errors.New("ssh: StderrPipe after process started") 546 } 547 s.stderrpipe = true 548 return s.ch.Stderr(), nil 549 } 550 551 // newSession returns a new interactive session on the remote host. 552 func newSession(ch Channel, reqs <-chan *Request) (*Session, error) { 553 s := &Session{ 554 ch: ch, 555 } 556 s.exitStatus = make(chan error, 1) 557 go func() { 558 s.exitStatus <- s.wait(reqs) 559 }() 560 561 return s, nil 562 } 563 564 // An ExitError reports unsuccessful completion of a remote command. 565 type ExitError struct { 566 Waitmsg 567 } 568 569 func (e *ExitError) Error() string { 570 return e.Waitmsg.String() 571 } 572 573 // Waitmsg stores the information about an exited remote command 574 // as reported by Wait. 575 type Waitmsg struct { 576 status int 577 signal string 578 msg string 579 lang string 580 } 581 582 // ExitStatus returns the exit status of the remote command. 583 func (w Waitmsg) ExitStatus() int { 584 return w.status 585 } 586 587 // Signal returns the exit signal of the remote command if 588 // it was terminated violently. 589 func (w Waitmsg) Signal() string { 590 return w.signal 591 } 592 593 // Msg returns the exit message given by the remote command 594 func (w Waitmsg) Msg() string { 595 return w.msg 596 } 597 598 // Lang returns the language tag. See RFC 3066 599 func (w Waitmsg) Lang() string { 600 return w.lang 601 } 602 603 func (w Waitmsg) String() string { 604 return fmt.Sprintf("Process exited with: %v. Reason was: %v (%v)", w.status, w.msg, w.signal) 605 }