1
0

ssh.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. package ssh
  2. import (
  3. "context"
  4. "io"
  5. "net"
  6. "os"
  7. "os/exec"
  8. "path/filepath"
  9. "strconv"
  10. "strings"
  11. "syscall"
  12. "github.com/cockroachdb/errors"
  13. "github.com/sourcegraph/run"
  14. "golang.org/x/crypto/ssh"
  15. log "unknwon.dev/clog/v2"
  16. "gogs.io/gogs/internal/conf"
  17. "gogs.io/gogs/internal/database"
  18. "gogs.io/gogs/internal/osutil"
  19. )
  20. func cleanCommand(cmd string) string {
  21. i := strings.Index(cmd, "git")
  22. if i == -1 {
  23. return cmd
  24. }
  25. return cmd[i:]
  26. }
  27. func handleServerConn(keyID string, chans <-chan ssh.NewChannel) {
  28. for newChan := range chans {
  29. if newChan.ChannelType() != "session" {
  30. _ = newChan.Reject(ssh.UnknownChannelType, "unknown channel type")
  31. continue
  32. }
  33. ch, reqs, err := newChan.Accept()
  34. if err != nil {
  35. log.Error("Error accepting channel: %v", err)
  36. continue
  37. }
  38. go func(in <-chan *ssh.Request) {
  39. defer func() {
  40. _ = ch.Close()
  41. }()
  42. for req := range in {
  43. payload := cleanCommand(string(req.Payload))
  44. switch req.Type {
  45. case "env":
  46. // We only need to accept the request and do nothing since whatever environment
  47. // variables being set here won't be used in subsequent commands anyway.
  48. case "exec":
  49. cmdName := strings.TrimLeft(payload, "'()")
  50. log.Trace("SSH: Payload: %v", cmdName)
  51. args := []string{"serv", "key-" + keyID, "--config=" + conf.CustomConf}
  52. log.Trace("SSH: Arguments: %v", args)
  53. cmd := exec.Command(conf.AppPath(), args...)
  54. cmd.Env = append(os.Environ(), "SSH_ORIGINAL_COMMAND="+cmdName)
  55. stdout, err := cmd.StdoutPipe()
  56. if err != nil {
  57. log.Error("SSH: StdoutPipe: %v", err)
  58. return
  59. }
  60. stderr, err := cmd.StderrPipe()
  61. if err != nil {
  62. log.Error("SSH: StderrPipe: %v", err)
  63. return
  64. }
  65. input, err := cmd.StdinPipe()
  66. if err != nil {
  67. log.Error("SSH: StdinPipe: %v", err)
  68. return
  69. }
  70. // FIXME: check timeout
  71. if err = cmd.Start(); err != nil {
  72. log.Error("SSH: Start: %v", err)
  73. return
  74. }
  75. _ = req.Reply(true, nil)
  76. go func() {
  77. _, _ = io.Copy(input, ch)
  78. input.Close()
  79. }()
  80. _, _ = io.Copy(ch, stdout)
  81. _, _ = io.Copy(ch.Stderr(), stderr)
  82. if err = cmd.Wait(); err != nil {
  83. log.Error("SSH: Wait: %v", err)
  84. return
  85. }
  86. _, _ = ch.SendRequest("exit-status", false, []byte{0, 0, 0, 0})
  87. return
  88. default:
  89. }
  90. }
  91. }(reqs)
  92. }
  93. }
  94. func listen(config *ssh.ServerConfig, host string, port int) {
  95. listener, err := net.Listen("tcp", host+":"+strconv.Itoa(port))
  96. if err != nil {
  97. log.Fatal("Failed to start SSH server: %v", err)
  98. }
  99. for {
  100. // Once a ServerConfig has been configured, connections can be accepted.
  101. conn, err := listener.Accept()
  102. if err != nil {
  103. log.Error("SSH: Error accepting incoming connection: %v", err)
  104. continue
  105. }
  106. // Before use, a handshake must be performed on the incoming net.Conn.
  107. // It must be handled in a separate goroutine,
  108. // otherwise one user could easily block entire loop.
  109. // For example, user could be asked to trust server key fingerprint and hangs.
  110. go func() {
  111. log.Trace("SSH: Handshaking for %s", conn.RemoteAddr())
  112. sConn, chans, reqs, err := ssh.NewServerConn(conn, config)
  113. if err != nil {
  114. if err == io.EOF || errors.Is(err, syscall.ECONNRESET) {
  115. log.Trace("SSH: Handshaking was terminated: %v", err)
  116. } else {
  117. log.Error("SSH: Error on handshaking: %v", err)
  118. }
  119. return
  120. }
  121. log.Trace("SSH: Connection from %s (%s)", sConn.RemoteAddr(), sConn.ClientVersion())
  122. // The incoming Request channel must be serviced.
  123. go ssh.DiscardRequests(reqs)
  124. go handleServerConn(sConn.Permissions.Extensions["key-id"], chans)
  125. }()
  126. }
  127. }
  128. // Listen starts a SSH server listens on given port.
  129. func Listen(opts conf.SSHOpts, appDataPath string) {
  130. config := &ssh.ServerConfig{
  131. Config: ssh.Config{
  132. Ciphers: opts.ServerCiphers,
  133. MACs: opts.ServerMACs,
  134. },
  135. PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
  136. pkey, err := database.SearchPublicKeyByContent(strings.TrimSpace(string(ssh.MarshalAuthorizedKey(key))))
  137. if err != nil {
  138. if !database.IsErrKeyNotExist(err) {
  139. log.Error("SearchPublicKeyByContent: %v", err)
  140. }
  141. return nil, err
  142. }
  143. return &ssh.Permissions{Extensions: map[string]string{"key-id": strconv.FormatInt(pkey.ID, 10)}}, nil
  144. },
  145. }
  146. keys, err := setupHostKeys(appDataPath, opts.ServerAlgorithms)
  147. if err != nil {
  148. log.Fatal("SSH: Failed to setup host keys: %v", err)
  149. }
  150. for _, key := range keys {
  151. config.AddHostKey(key)
  152. }
  153. go listen(config, opts.ListenHost, opts.ListenPort)
  154. }
  155. func setupHostKeys(appDataPath string, algorithms []string) ([]ssh.Signer, error) {
  156. dir := filepath.Join(appDataPath, "ssh")
  157. err := os.MkdirAll(dir, os.ModePerm)
  158. if err != nil {
  159. return nil, errors.Wrapf(err, "create host key directory")
  160. }
  161. var hostKeys []ssh.Signer
  162. for _, algo := range algorithms {
  163. keyPath := filepath.Join(dir, "gogs."+algo)
  164. if !osutil.Exist(keyPath) {
  165. args := []string{
  166. conf.SSH.KeygenPath,
  167. "-t", algo,
  168. "-f", keyPath,
  169. "-N", run.Arg(""),
  170. }
  171. err = run.Cmd(context.Background(), args...).Run().Wait()
  172. if err != nil {
  173. return nil, errors.Wrapf(err, "generate host key with args %v", args)
  174. }
  175. log.Trace("SSH: New private key is generated: %s", keyPath)
  176. }
  177. keyData, err := os.ReadFile(keyPath)
  178. if err != nil {
  179. return nil, errors.Wrapf(err, "read host key %q", keyPath)
  180. }
  181. signer, err := ssh.ParsePrivateKey(keyData)
  182. if err != nil {
  183. return nil, errors.Wrapf(err, "parse host key %q", keyPath)
  184. }
  185. hostKeys = append(hostKeys, signer)
  186. }
  187. return hostKeys, nil
  188. }