util.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. package ssh
  2. import (
  3. "crypto/rand"
  4. "crypto/rsa"
  5. "encoding/binary"
  6. "golang.org/x/crypto/ssh"
  7. )
  8. func generateSigner() (ssh.Signer, error) {
  9. key, err := rsa.GenerateKey(rand.Reader, 2048)
  10. if err != nil {
  11. return nil, err
  12. }
  13. return ssh.NewSignerFromKey(key)
  14. }
  15. func parsePtyRequest(payload []byte) (pty Pty, ok bool) {
  16. // See https://datatracker.ietf.org/doc/html/rfc4254#section-6.2
  17. // 6.2. Requesting a Pseudo-Terminal
  18. // A pseudo-terminal can be allocated for the session by sending the
  19. // following message.
  20. // byte SSH_MSG_CHANNEL_REQUEST
  21. // uint32 recipient channel
  22. // string "pty-req"
  23. // boolean want_reply
  24. // string TERM environment variable value (e.g., vt100)
  25. // uint32 terminal width, characters (e.g., 80)
  26. // uint32 terminal height, rows (e.g., 24)
  27. // uint32 terminal width, pixels (e.g., 640)
  28. // uint32 terminal height, pixels (e.g., 480)
  29. // string encoded terminal modes
  30. // The payload starts from the TERM variable.
  31. term, rem, ok := parseString(payload)
  32. if !ok {
  33. return
  34. }
  35. win, rem, ok := parseWindow(rem)
  36. if !ok {
  37. return
  38. }
  39. modes, ok := parseTerminalModes(rem)
  40. if !ok {
  41. return
  42. }
  43. pty = Pty{
  44. Term: term,
  45. Window: win,
  46. Modes: modes,
  47. }
  48. return
  49. }
  50. func parseTerminalModes(in []byte) (modes ssh.TerminalModes, ok bool) {
  51. // See https://datatracker.ietf.org/doc/html/rfc4254#section-8
  52. // 8. Encoding of Terminal Modes
  53. //
  54. // All 'encoded terminal modes' (as passed in a pty request) are encoded
  55. // into a byte stream. It is intended that the coding be portable
  56. // across different environments. The stream consists of opcode-
  57. // argument pairs wherein the opcode is a byte value. Opcodes 1 to 159
  58. // have a single uint32 argument. Opcodes 160 to 255 are not yet
  59. // defined, and cause parsing to stop (they should only be used after
  60. // any other data). The stream is terminated by opcode TTY_OP_END
  61. // (0x00).
  62. //
  63. // The client SHOULD put any modes it knows about in the stream, and the
  64. // server MAY ignore any modes it does not know about. This allows some
  65. // degree of machine-independence, at least between systems that use a
  66. // POSIX-like tty interface. The protocol can support other systems as
  67. // well, but the client may need to fill reasonable values for a number
  68. // of parameters so the server pty gets set to a reasonable mode (the
  69. // server leaves all unspecified mode bits in their default values, and
  70. // only some combinations make sense).
  71. _, rem, ok := parseUint32(in)
  72. if !ok {
  73. return
  74. }
  75. const ttyOpEnd = 0
  76. for len(rem) > 0 {
  77. if modes == nil {
  78. modes = make(ssh.TerminalModes)
  79. }
  80. code := uint8(rem[0])
  81. rem = rem[1:]
  82. if code == ttyOpEnd || code > 160 {
  83. break
  84. }
  85. var val uint32
  86. val, rem, ok = parseUint32(rem)
  87. if !ok {
  88. return
  89. }
  90. modes[code] = val
  91. }
  92. ok = true
  93. return
  94. }
  95. func parseWindow(s []byte) (win Window, rem []byte, ok bool) {
  96. // See https://datatracker.ietf.org/doc/html/rfc4254#section-6.7
  97. // 6.7. Window Dimension Change Message
  98. // When the window (terminal) size changes on the client side, it MAY
  99. // send a message to the other side to inform it of the new dimensions.
  100. // byte SSH_MSG_CHANNEL_REQUEST
  101. // uint32 recipient channel
  102. // string "window-change"
  103. // boolean FALSE
  104. // uint32 terminal width, columns
  105. // uint32 terminal height, rows
  106. // uint32 terminal width, pixels
  107. // uint32 terminal height, pixels
  108. wCols, rem, ok := parseUint32(s)
  109. if !ok {
  110. return
  111. }
  112. hRows, rem, ok := parseUint32(rem)
  113. if !ok {
  114. return
  115. }
  116. wPixels, rem, ok := parseUint32(rem)
  117. if !ok {
  118. return
  119. }
  120. hPixels, rem, ok := parseUint32(rem)
  121. if !ok {
  122. return
  123. }
  124. win = Window{
  125. Width: int(wCols),
  126. Height: int(hRows),
  127. WidthPixels: int(wPixels),
  128. HeightPixels: int(hPixels),
  129. }
  130. return
  131. }
  132. func parseString(in []byte) (out string, rem []byte, ok bool) {
  133. length, rem, ok := parseUint32(in)
  134. if uint32(len(rem)) < length || !ok {
  135. ok = false
  136. return
  137. }
  138. out, rem = string(rem[:length]), rem[length:]
  139. ok = true
  140. return
  141. }
  142. func parseUint32(in []byte) (uint32, []byte, bool) {
  143. if len(in) < 4 {
  144. return 0, nil, false
  145. }
  146. return binary.BigEndian.Uint32(in), in[4:], true
  147. }