session.go 14 KB

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