session.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  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. ch Channel // 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. // stdinPipeWriter is non-nil if StdinPipe has not been called
  128. // and Stdin was specified by the user; it is the write end of
  129. // a pipe connecting Session.Stdin to the stdin channel.
  130. stdinPipeWriter io.WriteCloser
  131. exitStatus chan error
  132. }
  133. // SendRequest sends an out-of-band channel request on the SSH channel
  134. // underlying the session.
  135. func (s *Session) SendRequest(name string, wantReply bool, payload []byte) (bool, error) {
  136. return s.ch.SendRequest(name, wantReply, payload)
  137. }
  138. func (s *Session) Close() error {
  139. return s.ch.Close()
  140. }
  141. // RFC 4254 Section 6.4.
  142. type setenvRequest struct {
  143. Name string
  144. Value string
  145. }
  146. // Setenv sets an environment variable that will be applied to any
  147. // command executed by Shell or Run.
  148. func (s *Session) Setenv(name, value string) error {
  149. msg := setenvRequest{
  150. Name: name,
  151. Value: value,
  152. }
  153. ok, err := s.ch.SendRequest("env", true, Marshal(&msg))
  154. if err == nil && !ok {
  155. err = errors.New("ssh: setenv failed")
  156. }
  157. return err
  158. }
  159. // RFC 4254 Section 6.2.
  160. type ptyRequestMsg struct {
  161. Term string
  162. Columns uint32
  163. Rows uint32
  164. Width uint32
  165. Height uint32
  166. Modelist string
  167. }
  168. // RequestPty requests the association of a pty with the session on the remote host.
  169. func (s *Session) RequestPty(term string, h, w int, termmodes TerminalModes) error {
  170. var tm []byte
  171. for k, v := range termmodes {
  172. kv := struct {
  173. Key byte
  174. Val uint32
  175. }{k, v}
  176. tm = append(tm, Marshal(&kv)...)
  177. }
  178. tm = append(tm, tty_OP_END)
  179. req := ptyRequestMsg{
  180. Term: term,
  181. Columns: uint32(w),
  182. Rows: uint32(h),
  183. Width: uint32(w * 8),
  184. Height: uint32(h * 8),
  185. Modelist: string(tm),
  186. }
  187. ok, err := s.ch.SendRequest("pty-req", true, Marshal(&req))
  188. if err == nil && !ok {
  189. err = errors.New("ssh: pty-req failed")
  190. }
  191. return err
  192. }
  193. // RFC 4254 Section 6.5.
  194. type subsystemRequestMsg struct {
  195. Subsystem string
  196. }
  197. // RequestSubsystem requests the association of a subsystem with the session on the remote host.
  198. // A subsystem is a predefined command that runs in the background when the ssh session is initiated
  199. func (s *Session) RequestSubsystem(subsystem string) error {
  200. msg := subsystemRequestMsg{
  201. Subsystem: subsystem,
  202. }
  203. ok, err := s.ch.SendRequest("subsystem", true, Marshal(&msg))
  204. if err == nil && !ok {
  205. err = errors.New("ssh: subsystem request failed")
  206. }
  207. return err
  208. }
  209. // RFC 4254 Section 6.9.
  210. type signalMsg struct {
  211. Signal string
  212. }
  213. // Signal sends the given signal to the remote process.
  214. // sig is one of the SIG* constants.
  215. func (s *Session) Signal(sig Signal) error {
  216. msg := signalMsg{
  217. Signal: string(sig),
  218. }
  219. _, err := s.ch.SendRequest("signal", false, Marshal(&msg))
  220. return err
  221. }
  222. // RFC 4254 Section 6.5.
  223. type execMsg struct {
  224. Command string
  225. }
  226. // Start runs cmd on the remote host. Typically, the remote
  227. // server passes cmd to the shell for interpretation.
  228. // A Session only accepts one call to Run, Start or Shell.
  229. func (s *Session) Start(cmd string) error {
  230. if s.started {
  231. return errors.New("ssh: session already started")
  232. }
  233. req := execMsg{
  234. Command: cmd,
  235. }
  236. ok, err := s.ch.SendRequest("exec", true, Marshal(&req))
  237. if err == nil && !ok {
  238. err = fmt.Errorf("ssh: command %v failed", cmd)
  239. }
  240. if err != nil {
  241. return err
  242. }
  243. return s.start()
  244. }
  245. // Run 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, Shell, Output,
  248. // or CombinedOutput.
  249. //
  250. // The returned error is nil if the command runs, has no problems
  251. // copying stdin, stdout, and stderr, and exits with a zero exit
  252. // status.
  253. //
  254. // If the command fails to run or doesn't complete successfully, the
  255. // error is of type *ExitError. Other error types may be
  256. // returned for I/O problems.
  257. func (s *Session) Run(cmd string) error {
  258. err := s.Start(cmd)
  259. if err != nil {
  260. return err
  261. }
  262. return s.Wait()
  263. }
  264. // Output runs cmd on the remote host and returns its standard output.
  265. func (s *Session) Output(cmd string) ([]byte, error) {
  266. if s.Stdout != nil {
  267. return nil, errors.New("ssh: Stdout already set")
  268. }
  269. var b bytes.Buffer
  270. s.Stdout = &b
  271. err := s.Run(cmd)
  272. return b.Bytes(), err
  273. }
  274. type singleWriter struct {
  275. b bytes.Buffer
  276. mu sync.Mutex
  277. }
  278. func (w *singleWriter) Write(p []byte) (int, error) {
  279. w.mu.Lock()
  280. defer w.mu.Unlock()
  281. return w.b.Write(p)
  282. }
  283. // CombinedOutput runs cmd on the remote host and returns its combined
  284. // standard output and standard error.
  285. func (s *Session) CombinedOutput(cmd string) ([]byte, error) {
  286. if s.Stdout != nil {
  287. return nil, errors.New("ssh: Stdout already set")
  288. }
  289. if s.Stderr != nil {
  290. return nil, errors.New("ssh: Stderr already set")
  291. }
  292. var b singleWriter
  293. s.Stdout = &b
  294. s.Stderr = &b
  295. err := s.Run(cmd)
  296. return b.b.Bytes(), err
  297. }
  298. // Shell starts a login shell on the remote host. A Session only
  299. // accepts one call to Run, Start, Shell, Output, or CombinedOutput.
  300. func (s *Session) Shell() error {
  301. if s.started {
  302. return errors.New("ssh: session already started")
  303. }
  304. ok, err := s.ch.SendRequest("shell", true, nil)
  305. if err == nil && !ok {
  306. return fmt.Errorf("ssh: cound not start shell")
  307. }
  308. if err != nil {
  309. return err
  310. }
  311. return s.start()
  312. }
  313. func (s *Session) start() error {
  314. s.started = true
  315. type F func(*Session)
  316. for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} {
  317. setupFd(s)
  318. }
  319. s.errors = make(chan error, len(s.copyFuncs))
  320. for _, fn := range s.copyFuncs {
  321. go func(fn func() error) {
  322. s.errors <- fn()
  323. }(fn)
  324. }
  325. return nil
  326. }
  327. // Wait waits for the remote command to exit.
  328. //
  329. // The returned error is nil if the command runs, has no problems
  330. // copying stdin, stdout, and stderr, and exits with a zero exit
  331. // status.
  332. //
  333. // If the command fails to run or doesn't complete successfully, the
  334. // error is of type *ExitError. Other error types may be
  335. // returned for I/O problems.
  336. func (s *Session) Wait() error {
  337. if !s.started {
  338. return errors.New("ssh: session not started")
  339. }
  340. waitErr := <-s.exitStatus
  341. if s.stdinPipeWriter != nil {
  342. s.stdinPipeWriter.Close()
  343. }
  344. var copyError error
  345. for _ = range s.copyFuncs {
  346. if err := <-s.errors; err != nil && copyError == nil {
  347. copyError = err
  348. }
  349. }
  350. if waitErr != nil {
  351. return waitErr
  352. }
  353. return copyError
  354. }
  355. func (s *Session) wait(reqs <-chan *Request) error {
  356. wm := Waitmsg{status: -1}
  357. // Wait for msg channel to be closed before returning.
  358. for msg := range reqs {
  359. switch msg.Type {
  360. case "exit-status":
  361. d := msg.Payload
  362. wm.status = int(d[0])<<24 | int(d[1])<<16 | int(d[2])<<8 | int(d[3])
  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. return fmt.Sprintf("Process exited with: %v. Reason was: %v (%v)", w.status, w.msg, w.signal)
  544. }