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