You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

308 lines
7.6 KiB

  1. package ssh
  2. import (
  3. "bytes"
  4. "context"
  5. "errors"
  6. "fmt"
  7. "net"
  8. "sync"
  9. "github.com/anmitsu/go-shlex"
  10. gossh "golang.org/x/crypto/ssh"
  11. )
  12. // Session provides access to information about an SSH session and methods
  13. // to read and write to the SSH channel with an embedded Channel interface from
  14. // cypto/ssh.
  15. //
  16. // When Command() returns an empty slice, the user requested a shell. Otherwise
  17. // the user is performing an exec with those command arguments.
  18. //
  19. // TODO: Signals
  20. type Session interface {
  21. gossh.Channel
  22. // User returns the username used when establishing the SSH connection.
  23. User() string
  24. // RemoteAddr returns the net.Addr of the client side of the connection.
  25. RemoteAddr() net.Addr
  26. // LocalAddr returns the net.Addr of the server side of the connection.
  27. LocalAddr() net.Addr
  28. // Environ returns a copy of strings representing the environment set by the
  29. // user for this session, in the form "key=value".
  30. Environ() []string
  31. // Exit sends an exit status and then closes the session.
  32. Exit(code int) error
  33. // Command returns a shell parsed slice of arguments that were provided by the
  34. // user. Shell parsing splits the command string according to POSIX shell rules,
  35. // which considers quoting not just whitespace.
  36. Command() []string
  37. // RawCommand returns the exact command that was provided by the user.
  38. RawCommand() string
  39. // PublicKey returns the PublicKey used to authenticate. If a public key was not
  40. // used it will return nil.
  41. PublicKey() PublicKey
  42. // Context returns the connection's context. The returned context is always
  43. // non-nil and holds the same data as the Context passed into auth
  44. // handlers and callbacks.
  45. //
  46. // The context is canceled when the client's connection closes or I/O
  47. // operation fails.
  48. Context() context.Context
  49. // Permissions returns a copy of the Permissions object that was available for
  50. // setup in the auth handlers via the Context.
  51. Permissions() Permissions
  52. // Pty returns PTY information, a channel of window size changes, and a boolean
  53. // of whether or not a PTY was accepted for this session.
  54. Pty() (Pty, <-chan Window, bool)
  55. // Signals registers a channel to receive signals sent from the client. The
  56. // channel must handle signal sends or it will block the SSH request loop.
  57. // Registering nil will unregister the channel from signal sends. During the
  58. // time no channel is registered signals are buffered up to a reasonable amount.
  59. // If there are buffered signals when a channel is registered, they will be
  60. // sent in order on the channel immediately after registering.
  61. Signals(c chan<- Signal)
  62. }
  63. // maxSigBufSize is how many signals will be buffered
  64. // when there is no signal channel specified
  65. const maxSigBufSize = 128
  66. func DefaultSessionHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx Context) {
  67. ch, reqs, err := newChan.Accept()
  68. if err != nil {
  69. // TODO: trigger event callback
  70. return
  71. }
  72. sess := &session{
  73. Channel: ch,
  74. conn: conn,
  75. handler: srv.Handler,
  76. ptyCb: srv.PtyCallback,
  77. sessReqCb: srv.SessionRequestCallback,
  78. ctx: ctx,
  79. }
  80. sess.handleRequests(reqs)
  81. }
  82. type session struct {
  83. sync.Mutex
  84. gossh.Channel
  85. conn *gossh.ServerConn
  86. handler Handler
  87. handled bool
  88. exited bool
  89. pty *Pty
  90. winch chan Window
  91. env []string
  92. ptyCb PtyCallback
  93. sessReqCb SessionRequestCallback
  94. rawCmd string
  95. ctx Context
  96. sigCh chan<- Signal
  97. sigBuf []Signal
  98. }
  99. func (sess *session) Write(p []byte) (n int, err error) {
  100. if sess.pty != nil {
  101. m := len(p)
  102. // normalize \n to \r\n when pty is accepted.
  103. // this is a hardcoded shortcut since we don't support terminal modes.
  104. p = bytes.Replace(p, []byte{'\n'}, []byte{'\r', '\n'}, -1)
  105. p = bytes.Replace(p, []byte{'\r', '\r', '\n'}, []byte{'\r', '\n'}, -1)
  106. n, err = sess.Channel.Write(p)
  107. if n > m {
  108. n = m
  109. }
  110. return
  111. }
  112. return sess.Channel.Write(p)
  113. }
  114. func (sess *session) PublicKey() PublicKey {
  115. sessionkey := sess.ctx.Value(ContextKeyPublicKey)
  116. if sessionkey == nil {
  117. return nil
  118. }
  119. return sessionkey.(PublicKey)
  120. }
  121. func (sess *session) Permissions() Permissions {
  122. // use context permissions because its properly
  123. // wrapped and easier to dereference
  124. perms := sess.ctx.Value(ContextKeyPermissions).(*Permissions)
  125. return *perms
  126. }
  127. func (sess *session) Context() context.Context {
  128. return sess.ctx
  129. }
  130. func (sess *session) Exit(code int) error {
  131. sess.Lock()
  132. defer sess.Unlock()
  133. if sess.exited {
  134. return errors.New("Session.Exit called multiple times")
  135. }
  136. sess.exited = true
  137. status := struct{ Status uint32 }{uint32(code)}
  138. _, err := sess.SendRequest("exit-status", false, gossh.Marshal(&status))
  139. if err != nil {
  140. return err
  141. }
  142. return sess.Close()
  143. }
  144. func (sess *session) User() string {
  145. return sess.conn.User()
  146. }
  147. func (sess *session) RemoteAddr() net.Addr {
  148. return sess.conn.RemoteAddr()
  149. }
  150. func (sess *session) LocalAddr() net.Addr {
  151. return sess.conn.LocalAddr()
  152. }
  153. func (sess *session) Environ() []string {
  154. return append([]string(nil), sess.env...)
  155. }
  156. func (sess *session) RawCommand() string {
  157. return sess.rawCmd
  158. }
  159. func (sess *session) Command() []string {
  160. cmd, _ := shlex.Split(sess.rawCmd, true)
  161. return append([]string(nil), cmd...)
  162. }
  163. func (sess *session) Pty() (Pty, <-chan Window, bool) {
  164. if sess.pty != nil {
  165. return *sess.pty, sess.winch, true
  166. }
  167. return Pty{}, sess.winch, false
  168. }
  169. func (sess *session) Signals(c chan<- Signal) {
  170. sess.Lock()
  171. defer sess.Unlock()
  172. sess.sigCh = c
  173. if len(sess.sigBuf) > 0 {
  174. go func() {
  175. for _, sig := range sess.sigBuf {
  176. sess.sigCh <- sig
  177. }
  178. }()
  179. }
  180. }
  181. func (sess *session) handleRequests(reqs <-chan *gossh.Request) {
  182. for req := range reqs {
  183. switch req.Type {
  184. case "shell", "exec":
  185. if sess.handled {
  186. req.Reply(false, nil)
  187. continue
  188. }
  189. var payload = struct{ Value string }{}
  190. gossh.Unmarshal(req.Payload, &payload)
  191. sess.rawCmd = payload.Value
  192. // If there's a session policy callback, we need to confirm before
  193. // accepting the session.
  194. if sess.sessReqCb != nil && !sess.sessReqCb(sess, req.Type) {
  195. sess.rawCmd = ""
  196. req.Reply(false, nil)
  197. continue
  198. }
  199. sess.handled = true
  200. req.Reply(true, nil)
  201. go func() {
  202. sess.handler(sess)
  203. sess.Exit(0)
  204. }()
  205. case "env":
  206. if sess.handled {
  207. req.Reply(false, nil)
  208. continue
  209. }
  210. var kv struct{ Key, Value string }
  211. gossh.Unmarshal(req.Payload, &kv)
  212. sess.env = append(sess.env, fmt.Sprintf("%s=%s", kv.Key, kv.Value))
  213. req.Reply(true, nil)
  214. case "signal":
  215. var payload struct{ Signal string }
  216. gossh.Unmarshal(req.Payload, &payload)
  217. sess.Lock()
  218. if sess.sigCh != nil {
  219. sess.sigCh <- Signal(payload.Signal)
  220. } else {
  221. if len(sess.sigBuf) < maxSigBufSize {
  222. sess.sigBuf = append(sess.sigBuf, Signal(payload.Signal))
  223. }
  224. }
  225. sess.Unlock()
  226. case "pty-req":
  227. if sess.handled || sess.pty != nil {
  228. req.Reply(false, nil)
  229. continue
  230. }
  231. ptyReq, ok := parsePtyRequest(req.Payload)
  232. if !ok {
  233. req.Reply(false, nil)
  234. continue
  235. }
  236. if sess.ptyCb != nil {
  237. ok := sess.ptyCb(sess.ctx, ptyReq)
  238. if !ok {
  239. req.Reply(false, nil)
  240. continue
  241. }
  242. }
  243. sess.pty = &ptyReq
  244. sess.winch = make(chan Window, 1)
  245. sess.winch <- ptyReq.Window
  246. defer func() {
  247. // when reqs is closed
  248. close(sess.winch)
  249. }()
  250. req.Reply(ok, nil)
  251. case "window-change":
  252. if sess.pty == nil {
  253. req.Reply(false, nil)
  254. continue
  255. }
  256. win, ok := parseWinchRequest(req.Payload)
  257. if ok {
  258. sess.pty.Window = win
  259. sess.winch <- win
  260. }
  261. req.Reply(ok, nil)
  262. case agentRequestType:
  263. // TODO: option/callback to allow agent forwarding
  264. SetAgentRequested(sess.ctx)
  265. req.Reply(true, nil)
  266. default:
  267. // TODO: debug log
  268. req.Reply(false, nil)
  269. }
  270. }
  271. }