session.go 14 KB

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