session.go 15 KB

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