github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/third_party/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 ) 17 18 type Signal string 19 20 // POSIX signals as listed in RFC 4254 Section 6.10. 21 const ( 22 SIGABRT Signal = "ABRT" 23 SIGALRM Signal = "ALRM" 24 SIGFPE Signal = "FPE" 25 SIGHUP Signal = "HUP" 26 SIGILL Signal = "ILL" 27 SIGINT Signal = "INT" 28 SIGKILL Signal = "KILL" 29 SIGPIPE Signal = "PIPE" 30 SIGQUIT Signal = "QUIT" 31 SIGSEGV Signal = "SEGV" 32 SIGTERM Signal = "TERM" 33 SIGUSR1 Signal = "USR1" 34 SIGUSR2 Signal = "USR2" 35 ) 36 37 var signals = map[Signal]int{ 38 SIGABRT: 6, 39 SIGALRM: 14, 40 SIGFPE: 8, 41 SIGHUP: 1, 42 SIGILL: 4, 43 SIGINT: 2, 44 SIGKILL: 9, 45 SIGPIPE: 13, 46 SIGQUIT: 3, 47 SIGSEGV: 11, 48 SIGTERM: 15, 49 } 50 51 // A Session represents a connection to a remote command or shell. 52 type Session struct { 53 // Stdin specifies the remote process's standard input. 54 // If Stdin is nil, the remote process reads from an empty 55 // bytes.Buffer. 56 Stdin io.Reader 57 58 // Stdout and Stderr specify the remote process's standard 59 // output and error. 60 // 61 // If either is nil, Run connects the corresponding file 62 // descriptor to an instance of ioutil.Discard. There is a 63 // fixed amount of buffering that is shared for the two streams. 64 // If either blocks it may eventually cause the remote 65 // command to block. 66 Stdout io.Writer 67 Stderr io.Writer 68 69 *clientChan // the channel backing this session 70 71 started bool // true once Start, Run or Shell is invoked. 72 copyFuncs []func() error 73 errors chan error // one send per copyFunc 74 75 // true if pipe method is active 76 stdinpipe, stdoutpipe, stderrpipe bool 77 } 78 79 // RFC 4254 Section 6.4. 80 type setenvRequest struct { 81 PeersId uint32 82 Request string 83 WantReply bool 84 Name string 85 Value string 86 } 87 88 // Setenv sets an environment variable that will be applied to any 89 // command executed by Shell or Run. 90 func (s *Session) Setenv(name, value string) error { 91 req := setenvRequest{ 92 PeersId: s.peersId, 93 Request: "env", 94 WantReply: true, 95 Name: name, 96 Value: value, 97 } 98 if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil { 99 return err 100 } 101 return s.waitForResponse() 102 } 103 104 // An empty mode list, see RFC 4254 Section 8. 105 var emptyModelist = "\x00" 106 107 // RFC 4254 Section 6.2. 108 type ptyRequestMsg struct { 109 PeersId uint32 110 Request string 111 WantReply bool 112 Term string 113 Columns uint32 114 Rows uint32 115 Width uint32 116 Height uint32 117 Modelist string 118 } 119 120 // RequestPty requests the association of a pty with the session on the remote host. 121 func (s *Session) RequestPty(term string, h, w int) error { 122 req := ptyRequestMsg{ 123 PeersId: s.peersId, 124 Request: "pty-req", 125 WantReply: true, 126 Term: term, 127 Columns: uint32(w), 128 Rows: uint32(h), 129 Width: uint32(w * 8), 130 Height: uint32(h * 8), 131 Modelist: emptyModelist, 132 } 133 if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil { 134 return err 135 } 136 return s.waitForResponse() 137 } 138 139 // RFC 4254 Section 6.9. 140 type signalMsg struct { 141 PeersId uint32 142 Request string 143 WantReply bool 144 Signal string 145 } 146 147 // Signal sends the given signal to the remote process. 148 // sig is one of the SIG* constants. 149 func (s *Session) Signal(sig Signal) error { 150 req := signalMsg{ 151 PeersId: s.peersId, 152 Request: "signal", 153 WantReply: false, 154 Signal: string(sig), 155 } 156 return s.writePacket(marshal(msgChannelRequest, req)) 157 } 158 159 // RFC 4254 Section 6.5. 160 type execMsg struct { 161 PeersId uint32 162 Request string 163 WantReply bool 164 Command string 165 } 166 167 // Start runs cmd on the remote host. Typically, the remote 168 // server passes cmd to the shell for interpretation. 169 // A Session only accepts one call to Run, Start or Shell. 170 func (s *Session) Start(cmd string) error { 171 if s.started { 172 return errors.New("ssh: session already started") 173 } 174 req := execMsg{ 175 PeersId: s.peersId, 176 Request: "exec", 177 WantReply: true, 178 Command: cmd, 179 } 180 if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil { 181 return err 182 } 183 if err := s.waitForResponse(); err != nil { 184 return fmt.Errorf("ssh: could not execute command %s: %v", cmd, err) 185 } 186 return s.start() 187 } 188 189 // Run runs cmd on the remote host. Typically, the remote 190 // server passes cmd to the shell for interpretation. 191 // A Session only accepts one call to Run, Start or Shell. 192 // 193 // The returned error is nil if the command runs, has no problems 194 // copying stdin, stdout, and stderr, and exits with a zero exit 195 // status. 196 // 197 // If the command fails to run or doesn't complete successfully, the 198 // error is of type *ExitError. Other error types may be 199 // returned for I/O problems. 200 func (s *Session) Run(cmd string) error { 201 err := s.Start(cmd) 202 if err != nil { 203 return err 204 } 205 return s.Wait() 206 } 207 208 // Shell starts a login shell on the remote host. A Session only 209 // accepts one call to Run, Start or Shell. 210 func (s *Session) Shell() error { 211 if s.started { 212 return errors.New("ssh: session already started") 213 } 214 req := channelRequestMsg{ 215 PeersId: s.peersId, 216 Request: "shell", 217 WantReply: true, 218 } 219 if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil { 220 return err 221 } 222 if err := s.waitForResponse(); err != nil { 223 return fmt.Errorf("ssh: cound not execute shell: %v", err) 224 } 225 return s.start() 226 } 227 228 func (s *Session) waitForResponse() error { 229 msg := <-s.msg 230 switch msg.(type) { 231 case *channelRequestSuccessMsg: 232 return nil 233 case *channelRequestFailureMsg: 234 return errors.New("request failed") 235 } 236 return fmt.Errorf("unknown packet %T received: %v", msg, msg) 237 } 238 239 func (s *Session) start() error { 240 s.started = true 241 242 type F func(*Session) 243 for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} { 244 setupFd(s) 245 } 246 247 s.errors = make(chan error, len(s.copyFuncs)) 248 for _, fn := range s.copyFuncs { 249 go func(fn func() error) { 250 s.errors <- fn() 251 }(fn) 252 } 253 return nil 254 } 255 256 // Wait waits for the remote command to exit. 257 // 258 // The returned error is nil if the command runs, has no problems 259 // copying stdin, stdout, and stderr, and exits with a zero exit 260 // status. 261 // 262 // If the command fails to run or doesn't complete successfully, the 263 // error is of type *ExitError. Other error types may be 264 // returned for I/O problems. 265 func (s *Session) Wait() error { 266 if !s.started { 267 return errors.New("ssh: session not started") 268 } 269 waitErr := s.wait() 270 271 var copyError error 272 for _ = range s.copyFuncs { 273 if err := <-s.errors; err != nil && copyError == nil { 274 copyError = err 275 } 276 } 277 if waitErr != nil { 278 return waitErr 279 } 280 return copyError 281 } 282 283 func (s *Session) wait() error { 284 wm := Waitmsg{status: -1} 285 286 // Wait for msg channel to be closed before returning. 287 for msg := range s.msg { 288 switch msg := msg.(type) { 289 case *channelRequestMsg: 290 switch msg.Request { 291 case "exit-status": 292 d := msg.RequestSpecificData 293 wm.status = int(d[0])<<24 | int(d[1])<<16 | int(d[2])<<8 | int(d[3]) 294 case "exit-signal": 295 signal, rest, ok := parseString(msg.RequestSpecificData) 296 if !ok { 297 return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData) 298 } 299 wm.signal = safeString(string(signal)) 300 301 // skip coreDumped bool 302 if len(rest) == 0 { 303 return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData) 304 } 305 rest = rest[1:] 306 307 errmsg, rest, ok := parseString(rest) 308 if !ok { 309 return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData) 310 } 311 wm.msg = safeString(string(errmsg)) 312 313 lang, _, ok := parseString(rest) 314 if !ok { 315 return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData) 316 } 317 wm.lang = safeString(string(lang)) 318 default: 319 return fmt.Errorf("wait: unexpected channel request: %v", msg) 320 } 321 default: 322 return fmt.Errorf("wait: unexpected packet %T received: %v", msg, msg) 323 } 324 } 325 if wm.status == 0 { 326 return nil 327 } 328 if wm.status == -1 { 329 // exit-status was never sent from server 330 if wm.signal == "" { 331 return errors.New("wait: remote command exited without exit status or exit signal") 332 } 333 wm.status = 128 334 if _, ok := signals[Signal(wm.signal)]; ok { 335 wm.status += signals[Signal(wm.signal)] 336 } 337 } 338 return &ExitError{wm} 339 } 340 341 func (s *Session) stdin() { 342 if s.stdinpipe { 343 return 344 } 345 if s.Stdin == nil { 346 s.Stdin = new(bytes.Buffer) 347 } 348 s.copyFuncs = append(s.copyFuncs, func() error { 349 _, err := io.Copy(s.clientChan.stdin, s.Stdin) 350 if err1 := s.clientChan.stdin.Close(); err == nil { 351 err = err1 352 } 353 return err 354 }) 355 } 356 357 func (s *Session) stdout() { 358 if s.stdoutpipe { 359 return 360 } 361 if s.Stdout == nil { 362 s.Stdout = ioutil.Discard 363 } 364 s.copyFuncs = append(s.copyFuncs, func() error { 365 _, err := io.Copy(s.Stdout, s.clientChan.stdout) 366 return err 367 }) 368 } 369 370 func (s *Session) stderr() { 371 if s.stderrpipe { 372 return 373 } 374 if s.Stderr == nil { 375 s.Stderr = ioutil.Discard 376 } 377 s.copyFuncs = append(s.copyFuncs, func() error { 378 _, err := io.Copy(s.Stderr, s.clientChan.stderr) 379 return err 380 }) 381 } 382 383 // StdinPipe returns a pipe that will be connected to the 384 // remote command's standard input when the command starts. 385 func (s *Session) StdinPipe() (io.WriteCloser, error) { 386 if s.Stdin != nil { 387 return nil, errors.New("ssh: Stdin already set") 388 } 389 if s.started { 390 return nil, errors.New("ssh: StdinPipe after process started") 391 } 392 s.stdinpipe = true 393 return s.clientChan.stdin, nil 394 } 395 396 // StdoutPipe returns a pipe that will be connected to the 397 // remote command's standard output when the command starts. 398 // There is a fixed amount of buffering that is shared between 399 // stdout and stderr streams. If the StdoutPipe reader is 400 // not serviced fast enought it may eventually cause the 401 // remote command to block. 402 func (s *Session) StdoutPipe() (io.Reader, error) { 403 if s.Stdout != nil { 404 return nil, errors.New("ssh: Stdout already set") 405 } 406 if s.started { 407 return nil, errors.New("ssh: StdoutPipe after process started") 408 } 409 s.stdoutpipe = true 410 return s.clientChan.stdout, nil 411 } 412 413 // StderrPipe returns a pipe that will be connected to the 414 // remote command's standard error when the command starts. 415 // There is a fixed amount of buffering that is shared between 416 // stdout and stderr streams. If the StderrPipe reader is 417 // not serviced fast enought it may eventually cause the 418 // remote command to block. 419 func (s *Session) StderrPipe() (io.Reader, error) { 420 if s.Stderr != nil { 421 return nil, errors.New("ssh: Stderr already set") 422 } 423 if s.started { 424 return nil, errors.New("ssh: StderrPipe after process started") 425 } 426 s.stderrpipe = true 427 return s.clientChan.stderr, nil 428 } 429 430 // TODO(dfc) add Output and CombinedOutput helpers 431 432 // NewSession returns a new interactive session on the remote host. 433 func (c *ClientConn) NewSession() (*Session, error) { 434 ch := c.newChan(c.transport) 435 if err := c.writePacket(marshal(msgChannelOpen, channelOpenMsg{ 436 ChanType: "session", 437 PeersId: ch.id, 438 PeersWindow: 1 << 14, 439 MaxPacketSize: 1 << 15, // RFC 4253 6.1 440 })); err != nil { 441 c.chanlist.remove(ch.id) 442 return nil, err 443 } 444 if err := ch.waitForChannelOpenResponse(); err != nil { 445 c.chanlist.remove(ch.id) 446 return nil, fmt.Errorf("ssh: unable to open session: %v", err) 447 } 448 return &Session{ 449 clientChan: ch, 450 }, nil 451 } 452 453 // An ExitError reports unsuccessful completion of a remote command. 454 type ExitError struct { 455 Waitmsg 456 } 457 458 func (e *ExitError) Error() string { 459 return e.Waitmsg.String() 460 } 461 462 // Waitmsg stores the information about an exited remote command 463 // as reported by Wait. 464 type Waitmsg struct { 465 status int 466 signal string 467 msg string 468 lang string 469 } 470 471 // ExitStatus returns the exit status of the remote command. 472 func (w Waitmsg) ExitStatus() int { 473 return w.status 474 } 475 476 // Signal returns the exit signal of the remote command if 477 // it was terminated violently. 478 func (w Waitmsg) Signal() string { 479 return w.signal 480 } 481 482 // Msg returns the exit message given by the remote command 483 func (w Waitmsg) Msg() string { 484 return w.msg 485 } 486 487 // Lang returns the language tag. See RFC 3066 488 func (w Waitmsg) Lang() string { 489 return w.lang 490 } 491 492 func (w Waitmsg) String() string { 493 return fmt.Sprintf("Process exited with: %v. Reason was: %v (%v)", w.status, w.msg, w.signal) 494 }