mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-03 23:12:26 -06:00 
			
		
		
		
	
		
			
	
	
		
			648 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			648 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 
								 | 
							
								// 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"
							 | 
						||
| 
								 | 
							
									"io/ioutil"
							 | 
						||
| 
								 | 
							
									"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
							 | 
						||
| 
								 | 
							
									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
							 | 
						||
| 
								 | 
							
									// descriptor to an instance of ioutil.Discard. There is a
							 | 
						||
| 
								 | 
							
									// 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 {
							 | 
						||
| 
								 | 
							
										s.Stdout = ioutil.Discard
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									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 {
							 | 
						||
| 
								 | 
							
										s.Stderr = ioutil.Discard
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									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
							 | 
						||
| 
								 | 
							
								}
							 |