session.go 15 KB

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