session.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  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.9.
  211. type signalMsg struct {
  212. Signal string
  213. }
  214. // Signal sends the given signal to the remote process.
  215. // sig is one of the SIG* constants.
  216. func (s *Session) Signal(sig Signal) error {
  217. msg := signalMsg{
  218. Signal: string(sig),
  219. }
  220. _, err := s.ch.SendRequest("signal", false, Marshal(&msg))
  221. return err
  222. }
  223. // RFC 4254 Section 6.5.
  224. type execMsg struct {
  225. Command string
  226. }
  227. // Start runs cmd on the remote host. Typically, the remote
  228. // server passes cmd to the shell for interpretation.
  229. // A Session only accepts one call to Run, Start or Shell.
  230. func (s *Session) Start(cmd string) error {
  231. if s.started {
  232. return errors.New("ssh: session already started")
  233. }
  234. req := execMsg{
  235. Command: cmd,
  236. }
  237. ok, err := s.ch.SendRequest("exec", true, Marshal(&req))
  238. if err == nil && !ok {
  239. err = fmt.Errorf("ssh: command %v failed", cmd)
  240. }
  241. if err != nil {
  242. return err
  243. }
  244. return s.start()
  245. }
  246. // Run 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, Shell, Output,
  249. // or CombinedOutput.
  250. //
  251. // The returned error is nil if the command runs, has no problems
  252. // copying stdin, stdout, and stderr, and exits with a zero exit
  253. // status.
  254. //
  255. // If the remote server does not send an exit status, an error of type
  256. // *ExitMissingError is returned. If the command completes
  257. // unsuccessfully or is interrupted by a signal, the error is of type
  258. // *ExitError. Other error types may be returned for I/O problems.
  259. func (s *Session) Run(cmd string) error {
  260. err := s.Start(cmd)
  261. if err != nil {
  262. return err
  263. }
  264. return s.Wait()
  265. }
  266. // Output runs cmd on the remote host and returns its standard output.
  267. func (s *Session) Output(cmd string) ([]byte, error) {
  268. if s.Stdout != nil {
  269. return nil, errors.New("ssh: Stdout already set")
  270. }
  271. var b bytes.Buffer
  272. s.Stdout = &b
  273. err := s.Run(cmd)
  274. return b.Bytes(), err
  275. }
  276. type singleWriter struct {
  277. b bytes.Buffer
  278. mu sync.Mutex
  279. }
  280. func (w *singleWriter) Write(p []byte) (int, error) {
  281. w.mu.Lock()
  282. defer w.mu.Unlock()
  283. return w.b.Write(p)
  284. }
  285. // CombinedOutput runs cmd on the remote host and returns its combined
  286. // standard output and standard error.
  287. func (s *Session) CombinedOutput(cmd string) ([]byte, error) {
  288. if s.Stdout != nil {
  289. return nil, errors.New("ssh: Stdout already set")
  290. }
  291. if s.Stderr != nil {
  292. return nil, errors.New("ssh: Stderr already set")
  293. }
  294. var b singleWriter
  295. s.Stdout = &b
  296. s.Stderr = &b
  297. err := s.Run(cmd)
  298. return b.b.Bytes(), err
  299. }
  300. // Shell starts a login shell on the remote host. A Session only
  301. // accepts one call to Run, Start, Shell, Output, or CombinedOutput.
  302. func (s *Session) Shell() error {
  303. if s.started {
  304. return errors.New("ssh: session already started")
  305. }
  306. ok, err := s.ch.SendRequest("shell", true, nil)
  307. if err == nil && !ok {
  308. return errors.New("ssh: could not start shell")
  309. }
  310. if err != nil {
  311. return err
  312. }
  313. return s.start()
  314. }
  315. func (s *Session) start() error {
  316. s.started = true
  317. type F func(*Session)
  318. for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} {
  319. setupFd(s)
  320. }
  321. s.errors = make(chan error, len(s.copyFuncs))
  322. for _, fn := range s.copyFuncs {
  323. go func(fn func() error) {
  324. s.errors <- fn()
  325. }(fn)
  326. }
  327. return nil
  328. }
  329. // Wait waits for the remote command to exit.
  330. //
  331. // The returned error is nil if the command runs, has no problems
  332. // copying stdin, stdout, and stderr, and exits with a zero exit
  333. // status.
  334. //
  335. // If the remote server does not send an exit status, an error of type
  336. // *ExitMissingError is returned. If the command completes
  337. // unsuccessfully or is interrupted by a signal, the error is of type
  338. // *ExitError. Other error types may be returned for I/O problems.
  339. func (s *Session) Wait() error {
  340. if !s.started {
  341. return errors.New("ssh: session not started")
  342. }
  343. waitErr := <-s.exitStatus
  344. if s.stdinPipeWriter != nil {
  345. s.stdinPipeWriter.Close()
  346. }
  347. var copyError error
  348. for _ = range s.copyFuncs {
  349. if err := <-s.errors; err != nil && copyError == nil {
  350. copyError = err
  351. }
  352. }
  353. if waitErr != nil {
  354. return waitErr
  355. }
  356. return copyError
  357. }
  358. func (s *Session) wait(reqs <-chan *Request) error {
  359. wm := Waitmsg{status: -1}
  360. // Wait for msg channel to be closed before returning.
  361. for msg := range reqs {
  362. switch msg.Type {
  363. case "exit-status":
  364. wm.status = int(binary.BigEndian.Uint32(msg.Payload))
  365. case "exit-signal":
  366. var sigval struct {
  367. Signal string
  368. CoreDumped bool
  369. Error string
  370. Lang string
  371. }
  372. if err := Unmarshal(msg.Payload, &sigval); err != nil {
  373. return err
  374. }
  375. // Must sanitize strings?
  376. wm.signal = sigval.Signal
  377. wm.msg = sigval.Error
  378. wm.lang = sigval.Lang
  379. default:
  380. // This handles keepalives and matches
  381. // OpenSSH's behaviour.
  382. if msg.WantReply {
  383. msg.Reply(false, nil)
  384. }
  385. }
  386. }
  387. if wm.status == 0 {
  388. return nil
  389. }
  390. if wm.status == -1 {
  391. // exit-status was never sent from server
  392. if wm.signal == "" {
  393. // signal was not sent either. RFC 4254
  394. // section 6.10 recommends against this
  395. // behavior, but it is allowed, so we let
  396. // clients handle it.
  397. return &ExitMissingError{}
  398. }
  399. wm.status = 128
  400. if _, ok := signals[Signal(wm.signal)]; ok {
  401. wm.status += signals[Signal(wm.signal)]
  402. }
  403. }
  404. return &ExitError{wm}
  405. }
  406. // ExitMissingError is returned if a session is torn down cleanly, but
  407. // the server sends no confirmation of the exit status.
  408. type ExitMissingError struct{}
  409. func (e *ExitMissingError) Error() string {
  410. return "wait: remote command exited without exit status or exit signal"
  411. }
  412. func (s *Session) stdin() {
  413. if s.stdinpipe {
  414. return
  415. }
  416. var stdin io.Reader
  417. if s.Stdin == nil {
  418. stdin = new(bytes.Buffer)
  419. } else {
  420. r, w := io.Pipe()
  421. go func() {
  422. _, err := io.Copy(w, s.Stdin)
  423. w.CloseWithError(err)
  424. }()
  425. stdin, s.stdinPipeWriter = r, w
  426. }
  427. s.copyFuncs = append(s.copyFuncs, func() error {
  428. _, err := io.Copy(s.ch, stdin)
  429. if err1 := s.ch.CloseWrite(); err == nil && err1 != io.EOF {
  430. err = err1
  431. }
  432. return err
  433. })
  434. }
  435. func (s *Session) stdout() {
  436. if s.stdoutpipe {
  437. return
  438. }
  439. if s.Stdout == nil {
  440. s.Stdout = ioutil.Discard
  441. }
  442. s.copyFuncs = append(s.copyFuncs, func() error {
  443. _, err := io.Copy(s.Stdout, s.ch)
  444. return err
  445. })
  446. }
  447. func (s *Session) stderr() {
  448. if s.stderrpipe {
  449. return
  450. }
  451. if s.Stderr == nil {
  452. s.Stderr = ioutil.Discard
  453. }
  454. s.copyFuncs = append(s.copyFuncs, func() error {
  455. _, err := io.Copy(s.Stderr, s.ch.Stderr())
  456. return err
  457. })
  458. }
  459. // sessionStdin reroutes Close to CloseWrite.
  460. type sessionStdin struct {
  461. io.Writer
  462. ch Channel
  463. }
  464. func (s *sessionStdin) Close() error {
  465. return s.ch.CloseWrite()
  466. }
  467. // StdinPipe returns a pipe that will be connected to the
  468. // remote command's standard input when the command starts.
  469. func (s *Session) StdinPipe() (io.WriteCloser, error) {
  470. if s.Stdin != nil {
  471. return nil, errors.New("ssh: Stdin already set")
  472. }
  473. if s.started {
  474. return nil, errors.New("ssh: StdinPipe after process started")
  475. }
  476. s.stdinpipe = true
  477. return &sessionStdin{s.ch, s.ch}, nil
  478. }
  479. // StdoutPipe returns a pipe that will be connected to the
  480. // remote command's standard output when the command starts.
  481. // There is a fixed amount of buffering that is shared between
  482. // stdout and stderr streams. If the StdoutPipe reader is
  483. // not serviced fast enough it may eventually cause the
  484. // remote command to block.
  485. func (s *Session) StdoutPipe() (io.Reader, error) {
  486. if s.Stdout != nil {
  487. return nil, errors.New("ssh: Stdout already set")
  488. }
  489. if s.started {
  490. return nil, errors.New("ssh: StdoutPipe after process started")
  491. }
  492. s.stdoutpipe = true
  493. return s.ch, nil
  494. }
  495. // StderrPipe returns a pipe that will be connected to the
  496. // remote command's standard error when the command starts.
  497. // There is a fixed amount of buffering that is shared between
  498. // stdout and stderr streams. If the StderrPipe reader is
  499. // not serviced fast enough it may eventually cause the
  500. // remote command to block.
  501. func (s *Session) StderrPipe() (io.Reader, error) {
  502. if s.Stderr != nil {
  503. return nil, errors.New("ssh: Stderr already set")
  504. }
  505. if s.started {
  506. return nil, errors.New("ssh: StderrPipe after process started")
  507. }
  508. s.stderrpipe = true
  509. return s.ch.Stderr(), nil
  510. }
  511. // newSession returns a new interactive session on the remote host.
  512. func newSession(ch Channel, reqs <-chan *Request) (*Session, error) {
  513. s := &Session{
  514. ch: ch,
  515. }
  516. s.exitStatus = make(chan error, 1)
  517. go func() {
  518. s.exitStatus <- s.wait(reqs)
  519. }()
  520. return s, nil
  521. }
  522. // An ExitError reports unsuccessful completion of a remote command.
  523. type ExitError struct {
  524. Waitmsg
  525. }
  526. func (e *ExitError) Error() string {
  527. return e.Waitmsg.String()
  528. }
  529. // Waitmsg stores the information about an exited remote command
  530. // as reported by Wait.
  531. type Waitmsg struct {
  532. status int
  533. signal string
  534. msg string
  535. lang string
  536. }
  537. // ExitStatus returns the exit status of the remote command.
  538. func (w Waitmsg) ExitStatus() int {
  539. return w.status
  540. }
  541. // Signal returns the exit signal of the remote command if
  542. // it was terminated violently.
  543. func (w Waitmsg) Signal() string {
  544. return w.signal
  545. }
  546. // Msg returns the exit message given by the remote command
  547. func (w Waitmsg) Msg() string {
  548. return w.msg
  549. }
  550. // Lang returns the language tag. See RFC 3066
  551. func (w Waitmsg) Lang() string {
  552. return w.lang
  553. }
  554. func (w Waitmsg) String() string {
  555. str := fmt.Sprintf("Process exited with status %v", w.status)
  556. if w.signal != "" {
  557. str += fmt.Sprintf(" from signal %v", w.signal)
  558. }
  559. if w.msg != "" {
  560. str += fmt.Sprintf(". Reason was: %v", w.msg)
  561. }
  562. return str
  563. }