LEFT | RIGHT |
1 // Copyright 2011 The Go Authors. All rights reserved. | 1 // Copyright 2011 The Go Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style | 2 // Use of this source code is governed by a BSD-style |
3 // license that can be found in the LICENSE file. | 3 // license that can be found in the LICENSE file. |
4 | 4 |
5 package ssh | 5 package ssh |
6 | 6 |
7 // Session implements an interactive session described in | 7 // Session implements an interactive session described in |
8 // "RFC 4254, section 6". | 8 // "RFC 4254, section 6". |
9 | 9 |
10 import ( | 10 import ( |
11 "bytes" | 11 "bytes" |
12 "errors" | 12 "errors" |
13 "fmt" | 13 "fmt" |
14 "io" | 14 "io" |
15 "io/ioutil" | 15 "io/ioutil" |
16 "log" | |
17 "sync" | 16 "sync" |
18 ) | 17 ) |
19 | |
20 var _ = log.Println | |
21 | 18 |
22 type Signal string | 19 type Signal string |
23 | 20 |
24 // POSIX signals as listed in RFC 4254 Section 6.10. | 21 // POSIX signals as listed in RFC 4254 Section 6.10. |
25 const ( | 22 const ( |
26 SIGABRT Signal = "ABRT" | 23 SIGABRT Signal = "ABRT" |
27 SIGALRM Signal = "ALRM" | 24 SIGALRM Signal = "ALRM" |
28 SIGFPE Signal = "FPE" | 25 SIGFPE Signal = "FPE" |
29 SIGHUP Signal = "HUP" | 26 SIGHUP Signal = "HUP" |
30 SIGILL Signal = "ILL" | 27 SIGILL Signal = "ILL" |
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
125 // output and error. | 122 // output and error. |
126 // | 123 // |
127 // If either is nil, Run connects the corresponding file | 124 // If either is nil, Run connects the corresponding file |
128 // descriptor to an instance of ioutil.Discard. There is a | 125 // descriptor to an instance of ioutil.Discard. There is a |
129 // fixed amount of buffering that is shared for the two streams. | 126 // fixed amount of buffering that is shared for the two streams. |
130 // If either blocks it may eventually cause the remote | 127 // If either blocks it may eventually cause the remote |
131 // command to block. | 128 // command to block. |
132 Stdout io.Writer | 129 Stdout io.Writer |
133 Stderr io.Writer | 130 Stderr io.Writer |
134 | 131 |
135 » Channel // the channel backing this session | 132 » ch *channel // the channel backing this session |
136 | 133 |
137 started bool // true once Start, Run or Shell is invoked. | 134 started bool // true once Start, Run or Shell is invoked. |
138 copyFuncs []func() error | 135 copyFuncs []func() error |
139 errors chan error // one send per copyFunc | 136 errors chan error // one send per copyFunc |
140 | 137 |
141 // true if pipe method is active | 138 // true if pipe method is active |
142 stdinpipe, stdoutpipe, stderrpipe bool | 139 stdinpipe, stdoutpipe, stderrpipe bool |
| 140 } |
| 141 |
| 142 func (s *Session) Close() error { |
| 143 return s.ch.Close() |
143 } | 144 } |
144 | 145 |
145 // RFC 4254 Section 6.4. | 146 // RFC 4254 Section 6.4. |
146 type setenvRequest struct { | 147 type setenvRequest struct { |
147 Name string | 148 Name string |
148 Value string | 149 Value string |
149 } | 150 } |
150 | 151 |
151 // Setenv sets an environment variable that will be applied to any | 152 // Setenv sets an environment variable that will be applied to any |
152 // command executed by Shell or Run. | 153 // command executed by Shell or Run. |
153 func (s *Session) Setenv(name, value string) error { | 154 func (s *Session) Setenv(name, value string) error { |
154 msg := setenvRequest{ | 155 msg := setenvRequest{ |
155 Name: name, | 156 Name: name, |
156 Value: value, | 157 Value: value, |
157 } | 158 } |
158 » ok, err := s.Channel.SendRequest("env", true, marshal(0, msg)) | 159 » ok, err := s.ch.SendRequest("env", true, marshal(0, msg)) |
159 if err == nil && !ok { | 160 if err == nil && !ok { |
160 err = errors.New("ssh: setenv failed") | 161 err = errors.New("ssh: setenv failed") |
161 } | 162 } |
162 return err | 163 return err |
163 } | 164 } |
164 | 165 |
165 // RFC 4254 Section 6.2. | 166 // RFC 4254 Section 6.2. |
166 type ptyRequestMsg struct { | 167 type ptyRequestMsg struct { |
167 Term string | 168 Term string |
168 Columns uint32 | 169 Columns uint32 |
(...skipping 12 matching lines...) Expand all Loading... |
181 } | 182 } |
182 tm = append(tm, tty_OP_END) | 183 tm = append(tm, tty_OP_END) |
183 req := ptyRequestMsg{ | 184 req := ptyRequestMsg{ |
184 Term: term, | 185 Term: term, |
185 Columns: uint32(w), | 186 Columns: uint32(w), |
186 Rows: uint32(h), | 187 Rows: uint32(h), |
187 Width: uint32(w * 8), | 188 Width: uint32(w * 8), |
188 Height: uint32(h * 8), | 189 Height: uint32(h * 8), |
189 Modelist: string(tm), | 190 Modelist: string(tm), |
190 } | 191 } |
191 » ok, err := s.SendRequest("pty-req", true, marshal(0, req)) | 192 » ok, err := s.ch.SendRequest("pty-req", true, marshal(0, req)) |
192 if err == nil && !ok { | 193 if err == nil && !ok { |
193 err = errors.New("ssh: pty-req failed") | 194 err = errors.New("ssh: pty-req failed") |
194 } | 195 } |
195 return err | 196 return err |
196 } | 197 } |
197 | 198 |
198 // RFC 4254 Section 6.5. | 199 // RFC 4254 Section 6.5. |
199 type subsystemRequestMsg struct { | 200 type subsystemRequestMsg struct { |
200 Subsystem string | 201 Subsystem string |
201 } | 202 } |
202 | 203 |
203 // RequestSubsystem requests the association of a subsystem with the session on
the remote host. | 204 // RequestSubsystem requests the association of a subsystem with the session on
the remote host. |
204 // A subsystem is a predefined command that runs in the background when the ssh
session is initiated | 205 // A subsystem is a predefined command that runs in the background when the ssh
session is initiated |
205 func (s *Session) RequestSubsystem(subsystem string) error { | 206 func (s *Session) RequestSubsystem(subsystem string) error { |
206 msg := subsystemRequestMsg{ | 207 msg := subsystemRequestMsg{ |
207 Subsystem: subsystem, | 208 Subsystem: subsystem, |
208 } | 209 } |
209 » ok, err := s.SendRequest("subsystem", true, marshal(0, msg)) | 210 » ok, err := s.ch.SendRequest("subsystem", true, marshal(0, msg)) |
210 if err == nil && !ok { | 211 if err == nil && !ok { |
211 err = errors.New("ssh: subsystem request failed") | 212 err = errors.New("ssh: subsystem request failed") |
212 } | 213 } |
213 return err | 214 return err |
214 } | 215 } |
215 | 216 |
216 // RFC 4254 Section 6.9. | 217 // RFC 4254 Section 6.9. |
217 type signalMsg struct { | 218 type signalMsg struct { |
218 Signal string | 219 Signal string |
219 } | 220 } |
220 | 221 |
221 // Signal sends the given signal to the remote process. | 222 // Signal sends the given signal to the remote process. |
222 // sig is one of the SIG* constants. | 223 // sig is one of the SIG* constants. |
223 func (s *Session) Signal(sig Signal) error { | 224 func (s *Session) Signal(sig Signal) error { |
224 msg := signalMsg{ | 225 msg := signalMsg{ |
225 Signal: string(sig), | 226 Signal: string(sig), |
226 } | 227 } |
227 | 228 |
228 » _, err := s.SendRequest("signal", false, marshal(0, msg)) | 229 » _, err := s.ch.SendRequest("signal", false, marshal(0, msg)) |
229 return err | 230 return err |
230 } | 231 } |
231 | 232 |
232 // RFC 4254 Section 6.5. | 233 // RFC 4254 Section 6.5. |
233 type execMsg struct { | 234 type execMsg struct { |
234 Command string | 235 Command string |
235 } | 236 } |
236 | 237 |
237 // Start runs cmd on the remote host. Typically, the remote | 238 // Start runs cmd on the remote host. Typically, the remote |
238 // server passes cmd to the shell for interpretation. | 239 // server passes cmd to the shell for interpretation. |
239 // A Session only accepts one call to Run, Start or Shell. | 240 // A Session only accepts one call to Run, Start or Shell. |
240 func (s *Session) Start(cmd string) error { | 241 func (s *Session) Start(cmd string) error { |
241 if s.started { | 242 if s.started { |
242 return errors.New("ssh: session already started") | 243 return errors.New("ssh: session already started") |
243 } | 244 } |
244 req := execMsg{ | 245 req := execMsg{ |
245 Command: cmd, | 246 Command: cmd, |
246 } | 247 } |
247 | 248 |
248 » ok, err := s.SendRequest("exec", true, marshal(0, req)) | 249 » ok, err := s.ch.SendRequest("exec", true, marshal(0, req)) |
249 if err == nil && !ok { | 250 if err == nil && !ok { |
250 err = fmt.Errorf("ssh: command %v failed", cmd) | 251 err = fmt.Errorf("ssh: command %v failed", cmd) |
251 } | 252 } |
252 if err != nil { | 253 if err != nil { |
253 return err | 254 return err |
254 } | 255 } |
255 return s.start() | 256 return s.start() |
256 } | 257 } |
257 | 258 |
258 // Run runs cmd on the remote host. Typically, the remote | 259 // Run runs cmd on the remote host. Typically, the remote |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
313 return b.b.Bytes(), err | 314 return b.b.Bytes(), err |
314 } | 315 } |
315 | 316 |
316 // Shell starts a login shell on the remote host. A Session only | 317 // Shell starts a login shell on the remote host. A Session only |
317 // accepts one call to Run, Start, Shell, Output, or CombinedOutput. | 318 // accepts one call to Run, Start, Shell, Output, or CombinedOutput. |
318 func (s *Session) Shell() error { | 319 func (s *Session) Shell() error { |
319 if s.started { | 320 if s.started { |
320 return errors.New("ssh: session already started") | 321 return errors.New("ssh: session already started") |
321 } | 322 } |
322 | 323 |
323 » ok, err := s.SendRequest("shell", true, nil) | 324 » ok, err := s.ch.SendRequest("shell", true, nil) |
324 if err == nil && !ok { | 325 if err == nil && !ok { |
325 return fmt.Errorf("ssh: cound not execute") | 326 return fmt.Errorf("ssh: cound not execute") |
326 } | 327 } |
327 if err != nil { | 328 if err != nil { |
328 return err | 329 return err |
329 } | 330 } |
330 return s.start() | 331 return s.start() |
331 } | 332 } |
332 | 333 |
333 func (s *Session) start() error { | 334 func (s *Session) start() error { |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
371 if waitErr != nil { | 372 if waitErr != nil { |
372 return waitErr | 373 return waitErr |
373 } | 374 } |
374 return copyError | 375 return copyError |
375 } | 376 } |
376 | 377 |
377 func (s *Session) wait() error { | 378 func (s *Session) wait() error { |
378 wm := Waitmsg{status: -1} | 379 wm := Waitmsg{status: -1} |
379 | 380 |
380 // Wait for msg channel to be closed before returning. | 381 // Wait for msg channel to be closed before returning. |
381 » for msg := range s.ReceivedRequests() { | 382 » for msg := range s.ch.incomingRequests { |
382 switch msg.Request { | 383 switch msg.Request { |
383 case "exit-status": | 384 case "exit-status": |
384 d := msg.Payload | 385 d := msg.Payload |
385 wm.status = int(d[0])<<24 | int(d[1])<<16 | int(d[2])<<8
| int(d[3]) | 386 wm.status = int(d[0])<<24 | int(d[1])<<16 | int(d[2])<<8
| int(d[3]) |
386 case "exit-signal": | 387 case "exit-signal": |
387 signal, rest, ok := parseString(msg.Payload) | 388 signal, rest, ok := parseString(msg.Payload) |
388 if !ok { | 389 if !ok { |
389 return fmt.Errorf("wait: could not parse request
data: %v", msg.Payload) | 390 return fmt.Errorf("wait: could not parse request
data: %v", msg.Payload) |
390 } | 391 } |
391 wm.signal = safeString(string(signal)) | 392 wm.signal = safeString(string(signal)) |
(...skipping 12 matching lines...) Expand all Loading... |
404 | 405 |
405 lang, _, ok := parseString(rest) | 406 lang, _, ok := parseString(rest) |
406 if !ok { | 407 if !ok { |
407 return fmt.Errorf("wait: could not parse request
data: %v", msg.Payload) | 408 return fmt.Errorf("wait: could not parse request
data: %v", msg.Payload) |
408 } | 409 } |
409 wm.lang = safeString(string(lang)) | 410 wm.lang = safeString(string(lang)) |
410 default: | 411 default: |
411 // This handles keepalives and matches | 412 // This handles keepalives and matches |
412 // OpenSSH's behaviour. | 413 // OpenSSH's behaviour. |
413 if msg.WantReply { | 414 if msg.WantReply { |
414 » » » » s.AckRequest(false) | 415 » » » » s.ch.AckRequest(false) |
415 } | 416 } |
416 } | 417 } |
417 } | 418 } |
418 if wm.status == 0 { | 419 if wm.status == 0 { |
419 return nil | 420 return nil |
420 } | 421 } |
421 if wm.status == -1 { | 422 if wm.status == -1 { |
422 // exit-status was never sent from server | 423 // exit-status was never sent from server |
423 if wm.signal == "" { | 424 if wm.signal == "" { |
424 return errors.New("wait: remote command exited without e
xit status or exit signal") | 425 return errors.New("wait: remote command exited without e
xit status or exit signal") |
425 } | 426 } |
426 wm.status = 128 | 427 wm.status = 128 |
427 if _, ok := signals[Signal(wm.signal)]; ok { | 428 if _, ok := signals[Signal(wm.signal)]; ok { |
428 wm.status += signals[Signal(wm.signal)] | 429 wm.status += signals[Signal(wm.signal)] |
429 } | 430 } |
430 } | 431 } |
431 return &ExitError{wm} | 432 return &ExitError{wm} |
432 } | 433 } |
433 | 434 |
434 func (s *Session) stdin() { | 435 func (s *Session) stdin() { |
435 if s.stdinpipe { | 436 if s.stdinpipe { |
436 return | 437 return |
437 } | 438 } |
438 if s.Stdin == nil { | 439 if s.Stdin == nil { |
439 s.Stdin = new(bytes.Buffer) | 440 s.Stdin = new(bytes.Buffer) |
440 } | 441 } |
441 s.copyFuncs = append(s.copyFuncs, func() error { | 442 s.copyFuncs = append(s.copyFuncs, func() error { |
442 » » _, err := io.Copy(s.Channel, s.Stdin) | 443 » » _, err := io.Copy(s.ch, s.Stdin) |
443 » » if err1 := s.Channel.CloseWrite(); err == nil && err1 != io.EOF
{ | 444 » » if err1 := s.ch.CloseWrite(); err == nil && err1 != io.EOF { |
444 err = err1 | 445 err = err1 |
445 } | 446 } |
446 return err | 447 return err |
447 }) | 448 }) |
448 } | 449 } |
449 | 450 |
450 func (s *Session) stdout() { | 451 func (s *Session) stdout() { |
451 if s.stdoutpipe { | 452 if s.stdoutpipe { |
452 return | 453 return |
453 } | 454 } |
454 if s.Stdout == nil { | 455 if s.Stdout == nil { |
455 s.Stdout = ioutil.Discard | 456 s.Stdout = ioutil.Discard |
456 } | 457 } |
457 s.copyFuncs = append(s.copyFuncs, func() error { | 458 s.copyFuncs = append(s.copyFuncs, func() error { |
458 » » _, err := io.Copy(s.Stdout, s.Channel) | 459 » » _, err := io.Copy(s.Stdout, s.ch) |
459 return err | 460 return err |
460 }) | 461 }) |
461 } | 462 } |
462 | 463 |
463 func (s *Session) stderr() { | 464 func (s *Session) stderr() { |
464 if s.stderrpipe { | 465 if s.stderrpipe { |
465 return | 466 return |
466 } | 467 } |
467 if s.Stderr == nil { | 468 if s.Stderr == nil { |
468 s.Stderr = ioutil.Discard | 469 s.Stderr = ioutil.Discard |
469 } | 470 } |
470 s.copyFuncs = append(s.copyFuncs, func() error { | 471 s.copyFuncs = append(s.copyFuncs, func() error { |
471 » » _, err := io.Copy(s.Stderr, s.Channel.Stderr()) | 472 » » _, err := io.Copy(s.Stderr, s.ch.Extended(1)) |
472 return err | 473 return err |
473 }) | 474 }) |
| 475 } |
| 476 |
| 477 // sessionStdin reroutes Close to CloseWrite. |
| 478 type sessionStdin struct { |
| 479 io.Writer |
| 480 ch *channel |
| 481 } |
| 482 |
| 483 func (s *sessionStdin) Close() error { |
| 484 return s.ch.CloseWrite() |
474 } | 485 } |
475 | 486 |
476 // StdinPipe returns a pipe that will be connected to the | 487 // StdinPipe returns a pipe that will be connected to the |
477 // remote command's standard input when the command starts. | 488 // remote command's standard input when the command starts. |
478 func (s *Session) StdinPipe() (io.WriteCloser, error) { | 489 func (s *Session) StdinPipe() (io.WriteCloser, error) { |
479 if s.Stdin != nil { | 490 if s.Stdin != nil { |
480 return nil, errors.New("ssh: Stdin already set") | 491 return nil, errors.New("ssh: Stdin already set") |
481 } | 492 } |
482 if s.started { | 493 if s.started { |
483 return nil, errors.New("ssh: StdinPipe after process started") | 494 return nil, errors.New("ssh: StdinPipe after process started") |
484 } | 495 } |
485 s.stdinpipe = true | 496 s.stdinpipe = true |
486 » return s.Channel, nil | 497 » return &sessionStdin{s.ch, s.ch}, nil |
487 } | 498 } |
488 | 499 |
489 // StdoutPipe returns a pipe that will be connected to the | 500 // StdoutPipe returns a pipe that will be connected to the |
490 // remote command's standard output when the command starts. | 501 // remote command's standard output when the command starts. |
491 // There is a fixed amount of buffering that is shared between | 502 // There is a fixed amount of buffering that is shared between |
492 // stdout and stderr streams. If the StdoutPipe reader is | 503 // stdout and stderr streams. If the StdoutPipe reader is |
493 // not serviced fast enought it may eventually cause the | 504 // not serviced fast enough it may eventually cause the |
494 // remote command to block. | 505 // remote command to block. |
495 func (s *Session) StdoutPipe() (io.Reader, error) { | 506 func (s *Session) StdoutPipe() (io.Reader, error) { |
496 if s.Stdout != nil { | 507 if s.Stdout != nil { |
497 return nil, errors.New("ssh: Stdout already set") | 508 return nil, errors.New("ssh: Stdout already set") |
498 } | 509 } |
499 if s.started { | 510 if s.started { |
500 return nil, errors.New("ssh: StdoutPipe after process started") | 511 return nil, errors.New("ssh: StdoutPipe after process started") |
501 } | 512 } |
502 s.stdoutpipe = true | 513 s.stdoutpipe = true |
503 » return s.Channel, nil | 514 » return s.ch, nil |
504 } | 515 } |
505 | 516 |
506 // StderrPipe returns a pipe that will be connected to the | 517 // StderrPipe returns a pipe that will be connected to the |
507 // remote command's standard error when the command starts. | 518 // remote command's standard error when the command starts. |
508 // There is a fixed amount of buffering that is shared between | 519 // There is a fixed amount of buffering that is shared between |
509 // stdout and stderr streams. If the StderrPipe reader is | 520 // stdout and stderr streams. If the StderrPipe reader is |
510 // not serviced fast enought it may eventually cause the | 521 // not serviced fast enough it may eventually cause the |
511 // remote command to block. | 522 // remote command to block. |
512 func (s *Session) StderrPipe() (io.Reader, error) { | 523 func (s *Session) StderrPipe() (io.Reader, error) { |
513 if s.Stderr != nil { | 524 if s.Stderr != nil { |
514 return nil, errors.New("ssh: Stderr already set") | 525 return nil, errors.New("ssh: Stderr already set") |
515 } | 526 } |
516 if s.started { | 527 if s.started { |
517 return nil, errors.New("ssh: StderrPipe after process started") | 528 return nil, errors.New("ssh: StderrPipe after process started") |
518 } | 529 } |
519 s.stderrpipe = true | 530 s.stderrpipe = true |
520 » return s.Channel.Stderr(), nil | 531 » return s.ch.Extended(1), nil |
521 } | 532 } |
522 | 533 |
523 // NewSession returns a new interactive session on the remote host. | 534 // NewSession returns a new interactive session on the remote host. |
524 func (c *ClientConn) NewSession() (*Session, error) { | 535 func (c *ClientConn) NewSession() (*Session, error) { |
525 ch, err := c.mux.OpenChannel("session", nil) | 536 ch, err := c.mux.OpenChannel("session", nil) |
526 if err != nil { | 537 if err != nil { |
527 return nil, err | 538 return nil, err |
528 } | 539 } |
529 return &Session{ | 540 return &Session{ |
530 » » Channel: ch, | 541 » » ch: ch, |
531 }, nil | 542 }, nil |
532 } | 543 } |
533 | 544 |
534 // An ExitError reports unsuccessful completion of a remote command. | 545 // An ExitError reports unsuccessful completion of a remote command. |
535 type ExitError struct { | 546 type ExitError struct { |
536 Waitmsg | 547 Waitmsg |
537 } | 548 } |
538 | 549 |
539 func (e *ExitError) Error() string { | 550 func (e *ExitError) Error() string { |
540 return e.Waitmsg.String() | 551 return e.Waitmsg.String() |
(...skipping 25 matching lines...) Expand all Loading... |
566 } | 577 } |
567 | 578 |
568 // Lang returns the language tag. See RFC 3066 | 579 // Lang returns the language tag. See RFC 3066 |
569 func (w Waitmsg) Lang() string { | 580 func (w Waitmsg) Lang() string { |
570 return w.lang | 581 return w.lang |
571 } | 582 } |
572 | 583 |
573 func (w Waitmsg) String() string { | 584 func (w Waitmsg) String() string { |
574 return fmt.Sprintf("Process exited with: %v. Reason was: %v (%v)", w.sta
tus, w.msg, w.signal) | 585 return fmt.Sprintf("Process exited with: %v. Reason was: %v (%v)", w.sta
tus, w.msg, w.signal) |
575 } | 586 } |
LEFT | RIGHT |