| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | // Copyright 2011 The Go Authors. All rights reserved. | 
					
						
							|  |  |  | // Use of this source code is governed by a BSD-style | 
					
						
							|  |  |  | // license that can be found in the LICENSE file. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package ssh | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Session implements an interactive session described in | 
					
						
							|  |  |  | // "RFC 4254, section 6". | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"bytes" | 
					
						
							|  |  |  | 	"encoding/binary" | 
					
						
							|  |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"io" | 
					
						
							|  |  |  | 	"sync" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type Signal string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // POSIX signals as listed in RFC 4254 Section 6.10. | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	SIGABRT Signal = "ABRT" | 
					
						
							|  |  |  | 	SIGALRM Signal = "ALRM" | 
					
						
							|  |  |  | 	SIGFPE  Signal = "FPE" | 
					
						
							|  |  |  | 	SIGHUP  Signal = "HUP" | 
					
						
							|  |  |  | 	SIGILL  Signal = "ILL" | 
					
						
							|  |  |  | 	SIGINT  Signal = "INT" | 
					
						
							|  |  |  | 	SIGKILL Signal = "KILL" | 
					
						
							|  |  |  | 	SIGPIPE Signal = "PIPE" | 
					
						
							|  |  |  | 	SIGQUIT Signal = "QUIT" | 
					
						
							|  |  |  | 	SIGSEGV Signal = "SEGV" | 
					
						
							|  |  |  | 	SIGTERM Signal = "TERM" | 
					
						
							|  |  |  | 	SIGUSR1 Signal = "USR1" | 
					
						
							|  |  |  | 	SIGUSR2 Signal = "USR2" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var signals = map[Signal]int{ | 
					
						
							|  |  |  | 	SIGABRT: 6, | 
					
						
							|  |  |  | 	SIGALRM: 14, | 
					
						
							|  |  |  | 	SIGFPE:  8, | 
					
						
							|  |  |  | 	SIGHUP:  1, | 
					
						
							|  |  |  | 	SIGILL:  4, | 
					
						
							|  |  |  | 	SIGINT:  2, | 
					
						
							|  |  |  | 	SIGKILL: 9, | 
					
						
							|  |  |  | 	SIGPIPE: 13, | 
					
						
							|  |  |  | 	SIGQUIT: 3, | 
					
						
							|  |  |  | 	SIGSEGV: 11, | 
					
						
							|  |  |  | 	SIGTERM: 15, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type TerminalModes map[uint8]uint32 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // POSIX terminal mode flags as listed in RFC 4254 Section 8. | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	tty_OP_END    = 0 | 
					
						
							|  |  |  | 	VINTR         = 1 | 
					
						
							|  |  |  | 	VQUIT         = 2 | 
					
						
							|  |  |  | 	VERASE        = 3 | 
					
						
							|  |  |  | 	VKILL         = 4 | 
					
						
							|  |  |  | 	VEOF          = 5 | 
					
						
							|  |  |  | 	VEOL          = 6 | 
					
						
							|  |  |  | 	VEOL2         = 7 | 
					
						
							|  |  |  | 	VSTART        = 8 | 
					
						
							|  |  |  | 	VSTOP         = 9 | 
					
						
							|  |  |  | 	VSUSP         = 10 | 
					
						
							|  |  |  | 	VDSUSP        = 11 | 
					
						
							|  |  |  | 	VREPRINT      = 12 | 
					
						
							|  |  |  | 	VWERASE       = 13 | 
					
						
							|  |  |  | 	VLNEXT        = 14 | 
					
						
							|  |  |  | 	VFLUSH        = 15 | 
					
						
							|  |  |  | 	VSWTCH        = 16 | 
					
						
							|  |  |  | 	VSTATUS       = 17 | 
					
						
							|  |  |  | 	VDISCARD      = 18 | 
					
						
							|  |  |  | 	IGNPAR        = 30 | 
					
						
							|  |  |  | 	PARMRK        = 31 | 
					
						
							|  |  |  | 	INPCK         = 32 | 
					
						
							|  |  |  | 	ISTRIP        = 33 | 
					
						
							|  |  |  | 	INLCR         = 34 | 
					
						
							|  |  |  | 	IGNCR         = 35 | 
					
						
							|  |  |  | 	ICRNL         = 36 | 
					
						
							|  |  |  | 	IUCLC         = 37 | 
					
						
							|  |  |  | 	IXON          = 38 | 
					
						
							|  |  |  | 	IXANY         = 39 | 
					
						
							|  |  |  | 	IXOFF         = 40 | 
					
						
							|  |  |  | 	IMAXBEL       = 41 | 
					
						
							| 
									
										
										
										
											2022-05-02 14:05:18 +01:00
										 |  |  | 	IUTF8         = 42 // RFC 8160 | 
					
						
							| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | 	ISIG          = 50 | 
					
						
							|  |  |  | 	ICANON        = 51 | 
					
						
							|  |  |  | 	XCASE         = 52 | 
					
						
							|  |  |  | 	ECHO          = 53 | 
					
						
							|  |  |  | 	ECHOE         = 54 | 
					
						
							|  |  |  | 	ECHOK         = 55 | 
					
						
							|  |  |  | 	ECHONL        = 56 | 
					
						
							|  |  |  | 	NOFLSH        = 57 | 
					
						
							|  |  |  | 	TOSTOP        = 58 | 
					
						
							|  |  |  | 	IEXTEN        = 59 | 
					
						
							|  |  |  | 	ECHOCTL       = 60 | 
					
						
							|  |  |  | 	ECHOKE        = 61 | 
					
						
							|  |  |  | 	PENDIN        = 62 | 
					
						
							|  |  |  | 	OPOST         = 70 | 
					
						
							|  |  |  | 	OLCUC         = 71 | 
					
						
							|  |  |  | 	ONLCR         = 72 | 
					
						
							|  |  |  | 	OCRNL         = 73 | 
					
						
							|  |  |  | 	ONOCR         = 74 | 
					
						
							|  |  |  | 	ONLRET        = 75 | 
					
						
							|  |  |  | 	CS7           = 90 | 
					
						
							|  |  |  | 	CS8           = 91 | 
					
						
							|  |  |  | 	PARENB        = 92 | 
					
						
							|  |  |  | 	PARODD        = 93 | 
					
						
							|  |  |  | 	TTY_OP_ISPEED = 128 | 
					
						
							|  |  |  | 	TTY_OP_OSPEED = 129 | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // A Session represents a connection to a remote command or shell. | 
					
						
							|  |  |  | type Session struct { | 
					
						
							|  |  |  | 	// Stdin specifies the remote process's standard input. | 
					
						
							|  |  |  | 	// If Stdin is nil, the remote process reads from an empty | 
					
						
							|  |  |  | 	// bytes.Buffer. | 
					
						
							|  |  |  | 	Stdin io.Reader | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Stdout and Stderr specify the remote process's standard | 
					
						
							|  |  |  | 	// output and error. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// If either is nil, Run connects the corresponding file | 
					
						
							| 
									
										
										
										
											2023-01-30 10:45:34 +01:00
										 |  |  | 	// descriptor to an instance of io.Discard. There is a | 
					
						
							| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | 	// fixed amount of buffering that is shared for the two streams. | 
					
						
							|  |  |  | 	// If either blocks it may eventually cause the remote | 
					
						
							|  |  |  | 	// command to block. | 
					
						
							|  |  |  | 	Stdout io.Writer | 
					
						
							|  |  |  | 	Stderr io.Writer | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ch        Channel // the channel backing this session | 
					
						
							|  |  |  | 	started   bool    // true once Start, Run or Shell is invoked. | 
					
						
							|  |  |  | 	copyFuncs []func() error | 
					
						
							|  |  |  | 	errors    chan error // one send per copyFunc | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// true if pipe method is active | 
					
						
							|  |  |  | 	stdinpipe, stdoutpipe, stderrpipe bool | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// stdinPipeWriter is non-nil if StdinPipe has not been called | 
					
						
							|  |  |  | 	// and Stdin was specified by the user; it is the write end of | 
					
						
							|  |  |  | 	// a pipe connecting Session.Stdin to the stdin channel. | 
					
						
							|  |  |  | 	stdinPipeWriter io.WriteCloser | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	exitStatus chan error | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // SendRequest sends an out-of-band channel request on the SSH channel | 
					
						
							|  |  |  | // underlying the session. | 
					
						
							|  |  |  | func (s *Session) SendRequest(name string, wantReply bool, payload []byte) (bool, error) { | 
					
						
							|  |  |  | 	return s.ch.SendRequest(name, wantReply, payload) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *Session) Close() error { | 
					
						
							|  |  |  | 	return s.ch.Close() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RFC 4254 Section 6.4. | 
					
						
							|  |  |  | type setenvRequest struct { | 
					
						
							|  |  |  | 	Name  string | 
					
						
							|  |  |  | 	Value string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Setenv sets an environment variable that will be applied to any | 
					
						
							|  |  |  | // command executed by Shell or Run. | 
					
						
							|  |  |  | func (s *Session) Setenv(name, value string) error { | 
					
						
							|  |  |  | 	msg := setenvRequest{ | 
					
						
							|  |  |  | 		Name:  name, | 
					
						
							|  |  |  | 		Value: value, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	ok, err := s.ch.SendRequest("env", true, Marshal(&msg)) | 
					
						
							|  |  |  | 	if err == nil && !ok { | 
					
						
							|  |  |  | 		err = errors.New("ssh: setenv failed") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RFC 4254 Section 6.2. | 
					
						
							|  |  |  | type ptyRequestMsg struct { | 
					
						
							|  |  |  | 	Term     string | 
					
						
							|  |  |  | 	Columns  uint32 | 
					
						
							|  |  |  | 	Rows     uint32 | 
					
						
							|  |  |  | 	Width    uint32 | 
					
						
							|  |  |  | 	Height   uint32 | 
					
						
							|  |  |  | 	Modelist string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RequestPty requests the association of a pty with the session on the remote host. | 
					
						
							|  |  |  | func (s *Session) RequestPty(term string, h, w int, termmodes TerminalModes) error { | 
					
						
							|  |  |  | 	var tm []byte | 
					
						
							|  |  |  | 	for k, v := range termmodes { | 
					
						
							|  |  |  | 		kv := struct { | 
					
						
							|  |  |  | 			Key byte | 
					
						
							|  |  |  | 			Val uint32 | 
					
						
							|  |  |  | 		}{k, v} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		tm = append(tm, Marshal(&kv)...) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	tm = append(tm, tty_OP_END) | 
					
						
							|  |  |  | 	req := ptyRequestMsg{ | 
					
						
							|  |  |  | 		Term:     term, | 
					
						
							|  |  |  | 		Columns:  uint32(w), | 
					
						
							|  |  |  | 		Rows:     uint32(h), | 
					
						
							|  |  |  | 		Width:    uint32(w * 8), | 
					
						
							|  |  |  | 		Height:   uint32(h * 8), | 
					
						
							|  |  |  | 		Modelist: string(tm), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	ok, err := s.ch.SendRequest("pty-req", true, Marshal(&req)) | 
					
						
							|  |  |  | 	if err == nil && !ok { | 
					
						
							|  |  |  | 		err = errors.New("ssh: pty-req failed") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RFC 4254 Section 6.5. | 
					
						
							|  |  |  | type subsystemRequestMsg struct { | 
					
						
							|  |  |  | 	Subsystem string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RequestSubsystem requests the association of a subsystem with the session on the remote host. | 
					
						
							|  |  |  | // A subsystem is a predefined command that runs in the background when the ssh session is initiated | 
					
						
							|  |  |  | func (s *Session) RequestSubsystem(subsystem string) error { | 
					
						
							|  |  |  | 	msg := subsystemRequestMsg{ | 
					
						
							|  |  |  | 		Subsystem: subsystem, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	ok, err := s.ch.SendRequest("subsystem", true, Marshal(&msg)) | 
					
						
							|  |  |  | 	if err == nil && !ok { | 
					
						
							|  |  |  | 		err = errors.New("ssh: subsystem request failed") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RFC 4254 Section 6.7. | 
					
						
							|  |  |  | type ptyWindowChangeMsg struct { | 
					
						
							|  |  |  | 	Columns uint32 | 
					
						
							|  |  |  | 	Rows    uint32 | 
					
						
							|  |  |  | 	Width   uint32 | 
					
						
							|  |  |  | 	Height  uint32 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // WindowChange informs the remote host about a terminal window dimension change to h rows and w columns. | 
					
						
							|  |  |  | func (s *Session) WindowChange(h, w int) error { | 
					
						
							|  |  |  | 	req := ptyWindowChangeMsg{ | 
					
						
							|  |  |  | 		Columns: uint32(w), | 
					
						
							|  |  |  | 		Rows:    uint32(h), | 
					
						
							|  |  |  | 		Width:   uint32(w * 8), | 
					
						
							|  |  |  | 		Height:  uint32(h * 8), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	_, err := s.ch.SendRequest("window-change", false, Marshal(&req)) | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RFC 4254 Section 6.9. | 
					
						
							|  |  |  | type signalMsg struct { | 
					
						
							|  |  |  | 	Signal string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Signal sends the given signal to the remote process. | 
					
						
							|  |  |  | // sig is one of the SIG* constants. | 
					
						
							|  |  |  | func (s *Session) Signal(sig Signal) error { | 
					
						
							|  |  |  | 	msg := signalMsg{ | 
					
						
							|  |  |  | 		Signal: string(sig), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_, err := s.ch.SendRequest("signal", false, Marshal(&msg)) | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RFC 4254 Section 6.5. | 
					
						
							|  |  |  | type execMsg struct { | 
					
						
							|  |  |  | 	Command string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Start runs cmd on the remote host. Typically, the remote | 
					
						
							|  |  |  | // server passes cmd to the shell for interpretation. | 
					
						
							|  |  |  | // A Session only accepts one call to Run, Start or Shell. | 
					
						
							|  |  |  | func (s *Session) Start(cmd string) error { | 
					
						
							|  |  |  | 	if s.started { | 
					
						
							|  |  |  | 		return errors.New("ssh: session already started") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	req := execMsg{ | 
					
						
							|  |  |  | 		Command: cmd, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ok, err := s.ch.SendRequest("exec", true, Marshal(&req)) | 
					
						
							|  |  |  | 	if err == nil && !ok { | 
					
						
							|  |  |  | 		err = fmt.Errorf("ssh: command %v failed", cmd) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return s.start() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Run runs cmd on the remote host. Typically, the remote | 
					
						
							|  |  |  | // server passes cmd to the shell for interpretation. | 
					
						
							|  |  |  | // A Session only accepts one call to Run, Start, Shell, Output, | 
					
						
							|  |  |  | // or CombinedOutput. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // The returned error is nil if the command runs, has no problems | 
					
						
							|  |  |  | // copying stdin, stdout, and stderr, and exits with a zero exit | 
					
						
							|  |  |  | // status. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // If the remote server does not send an exit status, an error of type | 
					
						
							|  |  |  | // *ExitMissingError is returned. If the command completes | 
					
						
							|  |  |  | // unsuccessfully or is interrupted by a signal, the error is of type | 
					
						
							|  |  |  | // *ExitError. Other error types may be returned for I/O problems. | 
					
						
							|  |  |  | func (s *Session) Run(cmd string) error { | 
					
						
							|  |  |  | 	err := s.Start(cmd) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return s.Wait() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Output runs cmd on the remote host and returns its standard output. | 
					
						
							|  |  |  | func (s *Session) Output(cmd string) ([]byte, error) { | 
					
						
							|  |  |  | 	if s.Stdout != nil { | 
					
						
							|  |  |  | 		return nil, errors.New("ssh: Stdout already set") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	var b bytes.Buffer | 
					
						
							|  |  |  | 	s.Stdout = &b | 
					
						
							|  |  |  | 	err := s.Run(cmd) | 
					
						
							|  |  |  | 	return b.Bytes(), err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type singleWriter struct { | 
					
						
							|  |  |  | 	b  bytes.Buffer | 
					
						
							|  |  |  | 	mu sync.Mutex | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (w *singleWriter) Write(p []byte) (int, error) { | 
					
						
							|  |  |  | 	w.mu.Lock() | 
					
						
							|  |  |  | 	defer w.mu.Unlock() | 
					
						
							|  |  |  | 	return w.b.Write(p) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // CombinedOutput runs cmd on the remote host and returns its combined | 
					
						
							|  |  |  | // standard output and standard error. | 
					
						
							|  |  |  | func (s *Session) CombinedOutput(cmd string) ([]byte, error) { | 
					
						
							|  |  |  | 	if s.Stdout != nil { | 
					
						
							|  |  |  | 		return nil, errors.New("ssh: Stdout already set") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if s.Stderr != nil { | 
					
						
							|  |  |  | 		return nil, errors.New("ssh: Stderr already set") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	var b singleWriter | 
					
						
							|  |  |  | 	s.Stdout = &b | 
					
						
							|  |  |  | 	s.Stderr = &b | 
					
						
							|  |  |  | 	err := s.Run(cmd) | 
					
						
							|  |  |  | 	return b.b.Bytes(), err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Shell starts a login shell on the remote host. A Session only | 
					
						
							|  |  |  | // accepts one call to Run, Start, Shell, Output, or CombinedOutput. | 
					
						
							|  |  |  | func (s *Session) Shell() error { | 
					
						
							|  |  |  | 	if s.started { | 
					
						
							|  |  |  | 		return errors.New("ssh: session already started") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ok, err := s.ch.SendRequest("shell", true, nil) | 
					
						
							|  |  |  | 	if err == nil && !ok { | 
					
						
							|  |  |  | 		return errors.New("ssh: could not start shell") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return s.start() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *Session) start() error { | 
					
						
							|  |  |  | 	s.started = true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	type F func(*Session) | 
					
						
							|  |  |  | 	for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} { | 
					
						
							|  |  |  | 		setupFd(s) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	s.errors = make(chan error, len(s.copyFuncs)) | 
					
						
							|  |  |  | 	for _, fn := range s.copyFuncs { | 
					
						
							|  |  |  | 		go func(fn func() error) { | 
					
						
							|  |  |  | 			s.errors <- fn() | 
					
						
							|  |  |  | 		}(fn) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Wait waits for the remote command to exit. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // The returned error is nil if the command runs, has no problems | 
					
						
							|  |  |  | // copying stdin, stdout, and stderr, and exits with a zero exit | 
					
						
							|  |  |  | // status. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // If the remote server does not send an exit status, an error of type | 
					
						
							|  |  |  | // *ExitMissingError is returned. If the command completes | 
					
						
							|  |  |  | // unsuccessfully or is interrupted by a signal, the error is of type | 
					
						
							|  |  |  | // *ExitError. Other error types may be returned for I/O problems. | 
					
						
							|  |  |  | func (s *Session) Wait() error { | 
					
						
							|  |  |  | 	if !s.started { | 
					
						
							|  |  |  | 		return errors.New("ssh: session not started") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	waitErr := <-s.exitStatus | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if s.stdinPipeWriter != nil { | 
					
						
							|  |  |  | 		s.stdinPipeWriter.Close() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	var copyError error | 
					
						
							|  |  |  | 	for range s.copyFuncs { | 
					
						
							|  |  |  | 		if err := <-s.errors; err != nil && copyError == nil { | 
					
						
							|  |  |  | 			copyError = err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if waitErr != nil { | 
					
						
							|  |  |  | 		return waitErr | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return copyError | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *Session) wait(reqs <-chan *Request) error { | 
					
						
							|  |  |  | 	wm := Waitmsg{status: -1} | 
					
						
							|  |  |  | 	// Wait for msg channel to be closed before returning. | 
					
						
							|  |  |  | 	for msg := range reqs { | 
					
						
							|  |  |  | 		switch msg.Type { | 
					
						
							|  |  |  | 		case "exit-status": | 
					
						
							|  |  |  | 			wm.status = int(binary.BigEndian.Uint32(msg.Payload)) | 
					
						
							|  |  |  | 		case "exit-signal": | 
					
						
							|  |  |  | 			var sigval struct { | 
					
						
							|  |  |  | 				Signal     string | 
					
						
							|  |  |  | 				CoreDumped bool | 
					
						
							|  |  |  | 				Error      string | 
					
						
							|  |  |  | 				Lang       string | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if err := Unmarshal(msg.Payload, &sigval); err != nil { | 
					
						
							|  |  |  | 				return err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// Must sanitize strings? | 
					
						
							|  |  |  | 			wm.signal = sigval.Signal | 
					
						
							|  |  |  | 			wm.msg = sigval.Error | 
					
						
							|  |  |  | 			wm.lang = sigval.Lang | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			// This handles keepalives and matches | 
					
						
							|  |  |  | 			// OpenSSH's behaviour. | 
					
						
							|  |  |  | 			if msg.WantReply { | 
					
						
							|  |  |  | 				msg.Reply(false, nil) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if wm.status == 0 { | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if wm.status == -1 { | 
					
						
							|  |  |  | 		// exit-status was never sent from server | 
					
						
							|  |  |  | 		if wm.signal == "" { | 
					
						
							|  |  |  | 			// signal was not sent either.  RFC 4254 | 
					
						
							|  |  |  | 			// section 6.10 recommends against this | 
					
						
							|  |  |  | 			// behavior, but it is allowed, so we let | 
					
						
							|  |  |  | 			// clients handle it. | 
					
						
							|  |  |  | 			return &ExitMissingError{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		wm.status = 128 | 
					
						
							|  |  |  | 		if _, ok := signals[Signal(wm.signal)]; ok { | 
					
						
							|  |  |  | 			wm.status += signals[Signal(wm.signal)] | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return &ExitError{wm} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ExitMissingError is returned if a session is torn down cleanly, but | 
					
						
							|  |  |  | // the server sends no confirmation of the exit status. | 
					
						
							|  |  |  | type ExitMissingError struct{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (e *ExitMissingError) Error() string { | 
					
						
							|  |  |  | 	return "wait: remote command exited without exit status or exit signal" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *Session) stdin() { | 
					
						
							|  |  |  | 	if s.stdinpipe { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	var stdin io.Reader | 
					
						
							|  |  |  | 	if s.Stdin == nil { | 
					
						
							|  |  |  | 		stdin = new(bytes.Buffer) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		r, w := io.Pipe() | 
					
						
							|  |  |  | 		go func() { | 
					
						
							|  |  |  | 			_, err := io.Copy(w, s.Stdin) | 
					
						
							|  |  |  | 			w.CloseWithError(err) | 
					
						
							|  |  |  | 		}() | 
					
						
							|  |  |  | 		stdin, s.stdinPipeWriter = r, w | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	s.copyFuncs = append(s.copyFuncs, func() error { | 
					
						
							|  |  |  | 		_, err := io.Copy(s.ch, stdin) | 
					
						
							|  |  |  | 		if err1 := s.ch.CloseWrite(); err == nil && err1 != io.EOF { | 
					
						
							|  |  |  | 			err = err1 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *Session) stdout() { | 
					
						
							|  |  |  | 	if s.stdoutpipe { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if s.Stdout == nil { | 
					
						
							| 
									
										
										
										
											2023-01-30 10:45:34 +01:00
										 |  |  | 		s.Stdout = io.Discard | 
					
						
							| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	s.copyFuncs = append(s.copyFuncs, func() error { | 
					
						
							|  |  |  | 		_, err := io.Copy(s.Stdout, s.ch) | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *Session) stderr() { | 
					
						
							|  |  |  | 	if s.stderrpipe { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if s.Stderr == nil { | 
					
						
							| 
									
										
										
										
											2023-01-30 10:45:34 +01:00
										 |  |  | 		s.Stderr = io.Discard | 
					
						
							| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	s.copyFuncs = append(s.copyFuncs, func() error { | 
					
						
							|  |  |  | 		_, err := io.Copy(s.Stderr, s.ch.Stderr()) | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // sessionStdin reroutes Close to CloseWrite. | 
					
						
							|  |  |  | type sessionStdin struct { | 
					
						
							|  |  |  | 	io.Writer | 
					
						
							|  |  |  | 	ch Channel | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *sessionStdin) Close() error { | 
					
						
							|  |  |  | 	return s.ch.CloseWrite() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // StdinPipe returns a pipe that will be connected to the | 
					
						
							|  |  |  | // remote command's standard input when the command starts. | 
					
						
							|  |  |  | func (s *Session) StdinPipe() (io.WriteCloser, error) { | 
					
						
							|  |  |  | 	if s.Stdin != nil { | 
					
						
							|  |  |  | 		return nil, errors.New("ssh: Stdin already set") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if s.started { | 
					
						
							|  |  |  | 		return nil, errors.New("ssh: StdinPipe after process started") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	s.stdinpipe = true | 
					
						
							|  |  |  | 	return &sessionStdin{s.ch, s.ch}, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // StdoutPipe returns a pipe that will be connected to the | 
					
						
							|  |  |  | // remote command's standard output when the command starts. | 
					
						
							|  |  |  | // There is a fixed amount of buffering that is shared between | 
					
						
							|  |  |  | // stdout and stderr streams. If the StdoutPipe reader is | 
					
						
							|  |  |  | // not serviced fast enough it may eventually cause the | 
					
						
							|  |  |  | // remote command to block. | 
					
						
							|  |  |  | func (s *Session) StdoutPipe() (io.Reader, error) { | 
					
						
							|  |  |  | 	if s.Stdout != nil { | 
					
						
							|  |  |  | 		return nil, errors.New("ssh: Stdout already set") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if s.started { | 
					
						
							|  |  |  | 		return nil, errors.New("ssh: StdoutPipe after process started") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	s.stdoutpipe = true | 
					
						
							|  |  |  | 	return s.ch, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // StderrPipe returns a pipe that will be connected to the | 
					
						
							|  |  |  | // remote command's standard error when the command starts. | 
					
						
							|  |  |  | // There is a fixed amount of buffering that is shared between | 
					
						
							|  |  |  | // stdout and stderr streams. If the StderrPipe reader is | 
					
						
							|  |  |  | // not serviced fast enough it may eventually cause the | 
					
						
							|  |  |  | // remote command to block. | 
					
						
							|  |  |  | func (s *Session) StderrPipe() (io.Reader, error) { | 
					
						
							|  |  |  | 	if s.Stderr != nil { | 
					
						
							|  |  |  | 		return nil, errors.New("ssh: Stderr already set") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if s.started { | 
					
						
							|  |  |  | 		return nil, errors.New("ssh: StderrPipe after process started") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	s.stderrpipe = true | 
					
						
							|  |  |  | 	return s.ch.Stderr(), nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // newSession returns a new interactive session on the remote host. | 
					
						
							|  |  |  | func newSession(ch Channel, reqs <-chan *Request) (*Session, error) { | 
					
						
							|  |  |  | 	s := &Session{ | 
					
						
							|  |  |  | 		ch: ch, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	s.exitStatus = make(chan error, 1) | 
					
						
							|  |  |  | 	go func() { | 
					
						
							|  |  |  | 		s.exitStatus <- s.wait(reqs) | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return s, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // An ExitError reports unsuccessful completion of a remote command. | 
					
						
							|  |  |  | type ExitError struct { | 
					
						
							|  |  |  | 	Waitmsg | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (e *ExitError) Error() string { | 
					
						
							|  |  |  | 	return e.Waitmsg.String() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Waitmsg stores the information about an exited remote command | 
					
						
							|  |  |  | // as reported by Wait. | 
					
						
							|  |  |  | type Waitmsg struct { | 
					
						
							|  |  |  | 	status int | 
					
						
							|  |  |  | 	signal string | 
					
						
							|  |  |  | 	msg    string | 
					
						
							|  |  |  | 	lang   string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ExitStatus returns the exit status of the remote command. | 
					
						
							|  |  |  | func (w Waitmsg) ExitStatus() int { | 
					
						
							|  |  |  | 	return w.status | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Signal returns the exit signal of the remote command if | 
					
						
							|  |  |  | // it was terminated violently. | 
					
						
							|  |  |  | func (w Waitmsg) Signal() string { | 
					
						
							|  |  |  | 	return w.signal | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Msg returns the exit message given by the remote command | 
					
						
							|  |  |  | func (w Waitmsg) Msg() string { | 
					
						
							|  |  |  | 	return w.msg | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Lang returns the language tag. See RFC 3066 | 
					
						
							|  |  |  | func (w Waitmsg) Lang() string { | 
					
						
							|  |  |  | 	return w.lang | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (w Waitmsg) String() string { | 
					
						
							|  |  |  | 	str := fmt.Sprintf("Process exited with status %v", w.status) | 
					
						
							|  |  |  | 	if w.signal != "" { | 
					
						
							|  |  |  | 		str += fmt.Sprintf(" from signal %v", w.signal) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if w.msg != "" { | 
					
						
							|  |  |  | 		str += fmt.Sprintf(". Reason was: %v", w.msg) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return str | 
					
						
							|  |  |  | } |