gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

session.go (15437B)


      1 // Copyright 2011 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package ssh
      6 
      7 // Session implements an interactive session described in
      8 // "RFC 4254, section 6".
      9 
     10 import (
     11 	"bytes"
     12 	"encoding/binary"
     13 	"errors"
     14 	"fmt"
     15 	"io"
     16 	"sync"
     17 )
     18 
     19 type Signal string
     20 
     21 // POSIX signals as listed in RFC 4254 Section 6.10.
     22 const (
     23 	SIGABRT Signal = "ABRT"
     24 	SIGALRM Signal = "ALRM"
     25 	SIGFPE  Signal = "FPE"
     26 	SIGHUP  Signal = "HUP"
     27 	SIGILL  Signal = "ILL"
     28 	SIGINT  Signal = "INT"
     29 	SIGKILL Signal = "KILL"
     30 	SIGPIPE Signal = "PIPE"
     31 	SIGQUIT Signal = "QUIT"
     32 	SIGSEGV Signal = "SEGV"
     33 	SIGTERM Signal = "TERM"
     34 	SIGUSR1 Signal = "USR1"
     35 	SIGUSR2 Signal = "USR2"
     36 )
     37 
     38 var signals = map[Signal]int{
     39 	SIGABRT: 6,
     40 	SIGALRM: 14,
     41 	SIGFPE:  8,
     42 	SIGHUP:  1,
     43 	SIGILL:  4,
     44 	SIGINT:  2,
     45 	SIGKILL: 9,
     46 	SIGPIPE: 13,
     47 	SIGQUIT: 3,
     48 	SIGSEGV: 11,
     49 	SIGTERM: 15,
     50 }
     51 
     52 type TerminalModes map[uint8]uint32
     53 
     54 // POSIX terminal mode flags as listed in RFC 4254 Section 8.
     55 const (
     56 	tty_OP_END    = 0
     57 	VINTR         = 1
     58 	VQUIT         = 2
     59 	VERASE        = 3
     60 	VKILL         = 4
     61 	VEOF          = 5
     62 	VEOL          = 6
     63 	VEOL2         = 7
     64 	VSTART        = 8
     65 	VSTOP         = 9
     66 	VSUSP         = 10
     67 	VDSUSP        = 11
     68 	VREPRINT      = 12
     69 	VWERASE       = 13
     70 	VLNEXT        = 14
     71 	VFLUSH        = 15
     72 	VSWTCH        = 16
     73 	VSTATUS       = 17
     74 	VDISCARD      = 18
     75 	IGNPAR        = 30
     76 	PARMRK        = 31
     77 	INPCK         = 32
     78 	ISTRIP        = 33
     79 	INLCR         = 34
     80 	IGNCR         = 35
     81 	ICRNL         = 36
     82 	IUCLC         = 37
     83 	IXON          = 38
     84 	IXANY         = 39
     85 	IXOFF         = 40
     86 	IMAXBEL       = 41
     87 	IUTF8         = 42 // RFC 8160
     88 	ISIG          = 50
     89 	ICANON        = 51
     90 	XCASE         = 52
     91 	ECHO          = 53
     92 	ECHOE         = 54
     93 	ECHOK         = 55
     94 	ECHONL        = 56
     95 	NOFLSH        = 57
     96 	TOSTOP        = 58
     97 	IEXTEN        = 59
     98 	ECHOCTL       = 60
     99 	ECHOKE        = 61
    100 	PENDIN        = 62
    101 	OPOST         = 70
    102 	OLCUC         = 71
    103 	ONLCR         = 72
    104 	OCRNL         = 73
    105 	ONOCR         = 74
    106 	ONLRET        = 75
    107 	CS7           = 90
    108 	CS8           = 91
    109 	PARENB        = 92
    110 	PARODD        = 93
    111 	TTY_OP_ISPEED = 128
    112 	TTY_OP_OSPEED = 129
    113 )
    114 
    115 // A Session represents a connection to a remote command or shell.
    116 type Session struct {
    117 	// Stdin specifies the remote process's standard input.
    118 	// If Stdin is nil, the remote process reads from an empty
    119 	// bytes.Buffer.
    120 	Stdin io.Reader
    121 
    122 	// Stdout and Stderr specify the remote process's standard
    123 	// output and error.
    124 	//
    125 	// If either is nil, Run connects the corresponding file
    126 	// descriptor to an instance of io.Discard. There is a
    127 	// fixed amount of buffering that is shared for the two streams.
    128 	// If either blocks it may eventually cause the remote
    129 	// command to block.
    130 	Stdout io.Writer
    131 	Stderr io.Writer
    132 
    133 	ch        Channel // the channel backing this session
    134 	started   bool    // true once Start, Run or Shell is invoked.
    135 	copyFuncs []func() error
    136 	errors    chan error // one send per copyFunc
    137 
    138 	// true if pipe method is active
    139 	stdinpipe, stdoutpipe, stderrpipe bool
    140 
    141 	// stdinPipeWriter is non-nil if StdinPipe has not been called
    142 	// and Stdin was specified by the user; it is the write end of
    143 	// a pipe connecting Session.Stdin to the stdin channel.
    144 	stdinPipeWriter io.WriteCloser
    145 
    146 	exitStatus chan error
    147 }
    148 
    149 // SendRequest sends an out-of-band channel request on the SSH channel
    150 // underlying the session.
    151 func (s *Session) SendRequest(name string, wantReply bool, payload []byte) (bool, error) {
    152 	return s.ch.SendRequest(name, wantReply, payload)
    153 }
    154 
    155 func (s *Session) Close() error {
    156 	return s.ch.Close()
    157 }
    158 
    159 // RFC 4254 Section 6.4.
    160 type setenvRequest struct {
    161 	Name  string
    162 	Value string
    163 }
    164 
    165 // Setenv sets an environment variable that will be applied to any
    166 // command executed by Shell or Run.
    167 func (s *Session) Setenv(name, value string) error {
    168 	msg := setenvRequest{
    169 		Name:  name,
    170 		Value: value,
    171 	}
    172 	ok, err := s.ch.SendRequest("env", true, Marshal(&msg))
    173 	if err == nil && !ok {
    174 		err = errors.New("ssh: setenv failed")
    175 	}
    176 	return err
    177 }
    178 
    179 // RFC 4254 Section 6.2.
    180 type ptyRequestMsg struct {
    181 	Term     string
    182 	Columns  uint32
    183 	Rows     uint32
    184 	Width    uint32
    185 	Height   uint32
    186 	Modelist string
    187 }
    188 
    189 // RequestPty requests the association of a pty with the session on the remote host.
    190 func (s *Session) RequestPty(term string, h, w int, termmodes TerminalModes) error {
    191 	var tm []byte
    192 	for k, v := range termmodes {
    193 		kv := struct {
    194 			Key byte
    195 			Val uint32
    196 		}{k, v}
    197 
    198 		tm = append(tm, Marshal(&kv)...)
    199 	}
    200 	tm = append(tm, tty_OP_END)
    201 	req := ptyRequestMsg{
    202 		Term:     term,
    203 		Columns:  uint32(w),
    204 		Rows:     uint32(h),
    205 		Width:    uint32(w * 8),
    206 		Height:   uint32(h * 8),
    207 		Modelist: string(tm),
    208 	}
    209 	ok, err := s.ch.SendRequest("pty-req", true, Marshal(&req))
    210 	if err == nil && !ok {
    211 		err = errors.New("ssh: pty-req failed")
    212 	}
    213 	return err
    214 }
    215 
    216 // RFC 4254 Section 6.5.
    217 type subsystemRequestMsg struct {
    218 	Subsystem string
    219 }
    220 
    221 // RequestSubsystem requests the association of a subsystem with the session on the remote host.
    222 // A subsystem is a predefined command that runs in the background when the ssh session is initiated
    223 func (s *Session) RequestSubsystem(subsystem string) error {
    224 	msg := subsystemRequestMsg{
    225 		Subsystem: subsystem,
    226 	}
    227 	ok, err := s.ch.SendRequest("subsystem", true, Marshal(&msg))
    228 	if err == nil && !ok {
    229 		err = errors.New("ssh: subsystem request failed")
    230 	}
    231 	return err
    232 }
    233 
    234 // RFC 4254 Section 6.7.
    235 type ptyWindowChangeMsg struct {
    236 	Columns uint32
    237 	Rows    uint32
    238 	Width   uint32
    239 	Height  uint32
    240 }
    241 
    242 // WindowChange informs the remote host about a terminal window dimension change to h rows and w columns.
    243 func (s *Session) WindowChange(h, w int) error {
    244 	req := ptyWindowChangeMsg{
    245 		Columns: uint32(w),
    246 		Rows:    uint32(h),
    247 		Width:   uint32(w * 8),
    248 		Height:  uint32(h * 8),
    249 	}
    250 	_, err := s.ch.SendRequest("window-change", false, Marshal(&req))
    251 	return err
    252 }
    253 
    254 // RFC 4254 Section 6.9.
    255 type signalMsg struct {
    256 	Signal string
    257 }
    258 
    259 // Signal sends the given signal to the remote process.
    260 // sig is one of the SIG* constants.
    261 func (s *Session) Signal(sig Signal) error {
    262 	msg := signalMsg{
    263 		Signal: string(sig),
    264 	}
    265 
    266 	_, err := s.ch.SendRequest("signal", false, Marshal(&msg))
    267 	return err
    268 }
    269 
    270 // RFC 4254 Section 6.5.
    271 type execMsg struct {
    272 	Command string
    273 }
    274 
    275 // Start runs cmd on the remote host. Typically, the remote
    276 // server passes cmd to the shell for interpretation.
    277 // A Session only accepts one call to Run, Start or Shell.
    278 func (s *Session) Start(cmd string) error {
    279 	if s.started {
    280 		return errors.New("ssh: session already started")
    281 	}
    282 	req := execMsg{
    283 		Command: cmd,
    284 	}
    285 
    286 	ok, err := s.ch.SendRequest("exec", true, Marshal(&req))
    287 	if err == nil && !ok {
    288 		err = fmt.Errorf("ssh: command %v failed", cmd)
    289 	}
    290 	if err != nil {
    291 		return err
    292 	}
    293 	return s.start()
    294 }
    295 
    296 // Run runs cmd on the remote host. Typically, the remote
    297 // server passes cmd to the shell for interpretation.
    298 // A Session only accepts one call to Run, Start, Shell, Output,
    299 // or CombinedOutput.
    300 //
    301 // The returned error is nil if the command runs, has no problems
    302 // copying stdin, stdout, and stderr, and exits with a zero exit
    303 // status.
    304 //
    305 // If the remote server does not send an exit status, an error of type
    306 // *ExitMissingError is returned. If the command completes
    307 // unsuccessfully or is interrupted by a signal, the error is of type
    308 // *ExitError. Other error types may be returned for I/O problems.
    309 func (s *Session) Run(cmd string) error {
    310 	err := s.Start(cmd)
    311 	if err != nil {
    312 		return err
    313 	}
    314 	return s.Wait()
    315 }
    316 
    317 // Output runs cmd on the remote host and returns its standard output.
    318 func (s *Session) Output(cmd string) ([]byte, error) {
    319 	if s.Stdout != nil {
    320 		return nil, errors.New("ssh: Stdout already set")
    321 	}
    322 	var b bytes.Buffer
    323 	s.Stdout = &b
    324 	err := s.Run(cmd)
    325 	return b.Bytes(), err
    326 }
    327 
    328 type singleWriter struct {
    329 	b  bytes.Buffer
    330 	mu sync.Mutex
    331 }
    332 
    333 func (w *singleWriter) Write(p []byte) (int, error) {
    334 	w.mu.Lock()
    335 	defer w.mu.Unlock()
    336 	return w.b.Write(p)
    337 }
    338 
    339 // CombinedOutput runs cmd on the remote host and returns its combined
    340 // standard output and standard error.
    341 func (s *Session) CombinedOutput(cmd string) ([]byte, error) {
    342 	if s.Stdout != nil {
    343 		return nil, errors.New("ssh: Stdout already set")
    344 	}
    345 	if s.Stderr != nil {
    346 		return nil, errors.New("ssh: Stderr already set")
    347 	}
    348 	var b singleWriter
    349 	s.Stdout = &b
    350 	s.Stderr = &b
    351 	err := s.Run(cmd)
    352 	return b.b.Bytes(), err
    353 }
    354 
    355 // Shell starts a login shell on the remote host. A Session only
    356 // accepts one call to Run, Start, Shell, Output, or CombinedOutput.
    357 func (s *Session) Shell() error {
    358 	if s.started {
    359 		return errors.New("ssh: session already started")
    360 	}
    361 
    362 	ok, err := s.ch.SendRequest("shell", true, nil)
    363 	if err == nil && !ok {
    364 		return errors.New("ssh: could not start shell")
    365 	}
    366 	if err != nil {
    367 		return err
    368 	}
    369 	return s.start()
    370 }
    371 
    372 func (s *Session) start() error {
    373 	s.started = true
    374 
    375 	type F func(*Session)
    376 	for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} {
    377 		setupFd(s)
    378 	}
    379 
    380 	s.errors = make(chan error, len(s.copyFuncs))
    381 	for _, fn := range s.copyFuncs {
    382 		go func(fn func() error) {
    383 			s.errors <- fn()
    384 		}(fn)
    385 	}
    386 	return nil
    387 }
    388 
    389 // Wait waits for the remote command to exit.
    390 //
    391 // The returned error is nil if the command runs, has no problems
    392 // copying stdin, stdout, and stderr, and exits with a zero exit
    393 // status.
    394 //
    395 // If the remote server does not send an exit status, an error of type
    396 // *ExitMissingError is returned. If the command completes
    397 // unsuccessfully or is interrupted by a signal, the error is of type
    398 // *ExitError. Other error types may be returned for I/O problems.
    399 func (s *Session) Wait() error {
    400 	if !s.started {
    401 		return errors.New("ssh: session not started")
    402 	}
    403 	waitErr := <-s.exitStatus
    404 
    405 	if s.stdinPipeWriter != nil {
    406 		s.stdinPipeWriter.Close()
    407 	}
    408 	var copyError error
    409 	for range s.copyFuncs {
    410 		if err := <-s.errors; err != nil && copyError == nil {
    411 			copyError = err
    412 		}
    413 	}
    414 	if waitErr != nil {
    415 		return waitErr
    416 	}
    417 	return copyError
    418 }
    419 
    420 func (s *Session) wait(reqs <-chan *Request) error {
    421 	wm := Waitmsg{status: -1}
    422 	// Wait for msg channel to be closed before returning.
    423 	for msg := range reqs {
    424 		switch msg.Type {
    425 		case "exit-status":
    426 			wm.status = int(binary.BigEndian.Uint32(msg.Payload))
    427 		case "exit-signal":
    428 			var sigval struct {
    429 				Signal     string
    430 				CoreDumped bool
    431 				Error      string
    432 				Lang       string
    433 			}
    434 			if err := Unmarshal(msg.Payload, &sigval); err != nil {
    435 				return err
    436 			}
    437 
    438 			// Must sanitize strings?
    439 			wm.signal = sigval.Signal
    440 			wm.msg = sigval.Error
    441 			wm.lang = sigval.Lang
    442 		default:
    443 			// This handles keepalives and matches
    444 			// OpenSSH's behaviour.
    445 			if msg.WantReply {
    446 				msg.Reply(false, nil)
    447 			}
    448 		}
    449 	}
    450 	if wm.status == 0 {
    451 		return nil
    452 	}
    453 	if wm.status == -1 {
    454 		// exit-status was never sent from server
    455 		if wm.signal == "" {
    456 			// signal was not sent either.  RFC 4254
    457 			// section 6.10 recommends against this
    458 			// behavior, but it is allowed, so we let
    459 			// clients handle it.
    460 			return &ExitMissingError{}
    461 		}
    462 		wm.status = 128
    463 		if _, ok := signals[Signal(wm.signal)]; ok {
    464 			wm.status += signals[Signal(wm.signal)]
    465 		}
    466 	}
    467 
    468 	return &ExitError{wm}
    469 }
    470 
    471 // ExitMissingError is returned if a session is torn down cleanly, but
    472 // the server sends no confirmation of the exit status.
    473 type ExitMissingError struct{}
    474 
    475 func (e *ExitMissingError) Error() string {
    476 	return "wait: remote command exited without exit status or exit signal"
    477 }
    478 
    479 func (s *Session) stdin() {
    480 	if s.stdinpipe {
    481 		return
    482 	}
    483 	var stdin io.Reader
    484 	if s.Stdin == nil {
    485 		stdin = new(bytes.Buffer)
    486 	} else {
    487 		r, w := io.Pipe()
    488 		go func() {
    489 			_, err := io.Copy(w, s.Stdin)
    490 			w.CloseWithError(err)
    491 		}()
    492 		stdin, s.stdinPipeWriter = r, w
    493 	}
    494 	s.copyFuncs = append(s.copyFuncs, func() error {
    495 		_, err := io.Copy(s.ch, stdin)
    496 		if err1 := s.ch.CloseWrite(); err == nil && err1 != io.EOF {
    497 			err = err1
    498 		}
    499 		return err
    500 	})
    501 }
    502 
    503 func (s *Session) stdout() {
    504 	if s.stdoutpipe {
    505 		return
    506 	}
    507 	if s.Stdout == nil {
    508 		s.Stdout = io.Discard
    509 	}
    510 	s.copyFuncs = append(s.copyFuncs, func() error {
    511 		_, err := io.Copy(s.Stdout, s.ch)
    512 		return err
    513 	})
    514 }
    515 
    516 func (s *Session) stderr() {
    517 	if s.stderrpipe {
    518 		return
    519 	}
    520 	if s.Stderr == nil {
    521 		s.Stderr = io.Discard
    522 	}
    523 	s.copyFuncs = append(s.copyFuncs, func() error {
    524 		_, err := io.Copy(s.Stderr, s.ch.Stderr())
    525 		return err
    526 	})
    527 }
    528 
    529 // sessionStdin reroutes Close to CloseWrite.
    530 type sessionStdin struct {
    531 	io.Writer
    532 	ch Channel
    533 }
    534 
    535 func (s *sessionStdin) Close() error {
    536 	return s.ch.CloseWrite()
    537 }
    538 
    539 // StdinPipe returns a pipe that will be connected to the
    540 // remote command's standard input when the command starts.
    541 func (s *Session) StdinPipe() (io.WriteCloser, error) {
    542 	if s.Stdin != nil {
    543 		return nil, errors.New("ssh: Stdin already set")
    544 	}
    545 	if s.started {
    546 		return nil, errors.New("ssh: StdinPipe after process started")
    547 	}
    548 	s.stdinpipe = true
    549 	return &sessionStdin{s.ch, s.ch}, nil
    550 }
    551 
    552 // StdoutPipe returns a pipe that will be connected to the
    553 // remote command's standard output when the command starts.
    554 // There is a fixed amount of buffering that is shared between
    555 // stdout and stderr streams. If the StdoutPipe reader is
    556 // not serviced fast enough it may eventually cause the
    557 // remote command to block.
    558 func (s *Session) StdoutPipe() (io.Reader, error) {
    559 	if s.Stdout != nil {
    560 		return nil, errors.New("ssh: Stdout already set")
    561 	}
    562 	if s.started {
    563 		return nil, errors.New("ssh: StdoutPipe after process started")
    564 	}
    565 	s.stdoutpipe = true
    566 	return s.ch, nil
    567 }
    568 
    569 // StderrPipe returns a pipe that will be connected to the
    570 // remote command's standard error when the command starts.
    571 // There is a fixed amount of buffering that is shared between
    572 // stdout and stderr streams. If the StderrPipe reader is
    573 // not serviced fast enough it may eventually cause the
    574 // remote command to block.
    575 func (s *Session) StderrPipe() (io.Reader, error) {
    576 	if s.Stderr != nil {
    577 		return nil, errors.New("ssh: Stderr already set")
    578 	}
    579 	if s.started {
    580 		return nil, errors.New("ssh: StderrPipe after process started")
    581 	}
    582 	s.stderrpipe = true
    583 	return s.ch.Stderr(), nil
    584 }
    585 
    586 // newSession returns a new interactive session on the remote host.
    587 func newSession(ch Channel, reqs <-chan *Request) (*Session, error) {
    588 	s := &Session{
    589 		ch: ch,
    590 	}
    591 	s.exitStatus = make(chan error, 1)
    592 	go func() {
    593 		s.exitStatus <- s.wait(reqs)
    594 	}()
    595 
    596 	return s, nil
    597 }
    598 
    599 // An ExitError reports unsuccessful completion of a remote command.
    600 type ExitError struct {
    601 	Waitmsg
    602 }
    603 
    604 func (e *ExitError) Error() string {
    605 	return e.Waitmsg.String()
    606 }
    607 
    608 // Waitmsg stores the information about an exited remote command
    609 // as reported by Wait.
    610 type Waitmsg struct {
    611 	status int
    612 	signal string
    613 	msg    string
    614 	lang   string
    615 }
    616 
    617 // ExitStatus returns the exit status of the remote command.
    618 func (w Waitmsg) ExitStatus() int {
    619 	return w.status
    620 }
    621 
    622 // Signal returns the exit signal of the remote command if
    623 // it was terminated violently.
    624 func (w Waitmsg) Signal() string {
    625 	return w.signal
    626 }
    627 
    628 // Msg returns the exit message given by the remote command
    629 func (w Waitmsg) Msg() string {
    630 	return w.msg
    631 }
    632 
    633 // Lang returns the language tag. See RFC 3066
    634 func (w Waitmsg) Lang() string {
    635 	return w.lang
    636 }
    637 
    638 func (w Waitmsg) String() string {
    639 	str := fmt.Sprintf("Process exited with status %v", w.status)
    640 	if w.signal != "" {
    641 		str += fmt.Sprintf(" from signal %v", w.signal)
    642 	}
    643 	if w.msg != "" {
    644 		str += fmt.Sprintf(". Reason was: %v", w.msg)
    645 	}
    646 	return str
    647 }