session.go 15 KB

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