serve.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package cmd
  5. import (
  6. "fmt"
  7. "os"
  8. "os/exec"
  9. "path/filepath"
  10. "strings"
  11. "time"
  12. "github.com/Unknwon/com"
  13. "github.com/codegangsta/cli"
  14. "github.com/gogits/gogs/models"
  15. "github.com/gogits/gogs/modules/log"
  16. "github.com/gogits/gogs/modules/setting"
  17. "github.com/gogits/gogs/modules/uuid"
  18. )
  19. var CmdServ = cli.Command{
  20. Name: "serv",
  21. Usage: "This command should only be called by SSH shell",
  22. Description: `Serv provide access auth for repositories`,
  23. Action: runServ,
  24. Flags: []cli.Flag{
  25. cli.StringFlag{"config, c", "custom/conf/app.ini", "Custom configuration file path", ""},
  26. },
  27. }
  28. func setup(logPath string) {
  29. setting.NewConfigContext()
  30. log.NewGitLogger(filepath.Join(setting.LogRootPath, logPath))
  31. if setting.DisableSSH {
  32. println("Gogs: SSH has been disabled")
  33. os.Exit(1)
  34. }
  35. models.LoadModelsConfig()
  36. if setting.UseSQLite3 {
  37. workDir, _ := setting.WorkDir()
  38. os.Chdir(workDir)
  39. }
  40. models.SetEngine()
  41. }
  42. func parseCmd(cmd string) (string, string) {
  43. ss := strings.SplitN(cmd, " ", 2)
  44. if len(ss) != 2 {
  45. return "", ""
  46. }
  47. verb, args := ss[0], ss[1]
  48. if verb == "git" {
  49. ss = strings.SplitN(args, " ", 2)
  50. args = ss[1]
  51. verb = fmt.Sprintf("%s %s", verb, ss[0])
  52. }
  53. return verb, strings.Replace(args, "'/", "'", 1)
  54. }
  55. var (
  56. COMMANDS_READONLY = map[string]models.AccessMode{
  57. "git-upload-pack": models.ACCESS_MODE_WRITE,
  58. "git upload-pack": models.ACCESS_MODE_WRITE,
  59. "git-upload-archive": models.ACCESS_MODE_WRITE,
  60. }
  61. COMMANDS_WRITE = map[string]models.AccessMode{
  62. "git-receive-pack": models.ACCESS_MODE_READ,
  63. "git receive-pack": models.ACCESS_MODE_READ,
  64. }
  65. )
  66. func In(b string, sl map[string]models.AccessMode) bool {
  67. _, e := sl[b]
  68. return e
  69. }
  70. func runServ(c *cli.Context) {
  71. if c.IsSet("config") {
  72. setting.CustomConf = c.String("config")
  73. }
  74. setup("serv.log")
  75. if len(c.Args()) < 1 {
  76. log.GitLogger.Fatal(2, "Not enough arguments")
  77. }
  78. keys := strings.Split(c.Args()[0], "-")
  79. if len(keys) != 2 {
  80. println("Gogs: auth file format error")
  81. log.GitLogger.Fatal(2, "Invalid auth file format: %s", os.Args[2])
  82. }
  83. keyId, err := com.StrTo(keys[1]).Int64()
  84. if err != nil {
  85. println("Gogs: auth file format error")
  86. log.GitLogger.Fatal(2, "Invalid auth file format: %v", err)
  87. }
  88. user, err := models.GetUserByKeyId(keyId)
  89. if err != nil {
  90. if err == models.ErrUserNotKeyOwner {
  91. println("Gogs: you are not the owner of SSH key")
  92. log.GitLogger.Fatal(2, "Invalid owner of SSH key: %d", keyId)
  93. }
  94. println("Gogs: internal error:", err.Error())
  95. log.GitLogger.Fatal(2, "Fail to get user by key ID(%d): %v", keyId, err)
  96. }
  97. cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
  98. if cmd == "" {
  99. println("Hi", user.Name, "! You've successfully authenticated, but Gogs does not provide shell access.")
  100. println("If this is what you do not expect, please log in with password and setup Gogs under another user.")
  101. return
  102. }
  103. verb, args := parseCmd(cmd)
  104. repoPath := strings.Trim(args, "'")
  105. rr := strings.SplitN(repoPath, "/", 2)
  106. if len(rr) != 2 {
  107. println("Gogs: unavailable repository", args)
  108. log.GitLogger.Fatal(2, "Unavailable repository: %v", args)
  109. }
  110. repoUserName := rr[0]
  111. repoName := strings.TrimSuffix(rr[1], ".git")
  112. isWrite := In(verb, COMMANDS_WRITE)
  113. isRead := In(verb, COMMANDS_READONLY)
  114. repoUser, err := models.GetUserByName(repoUserName)
  115. if err != nil {
  116. if err == models.ErrUserNotExist {
  117. println("Gogs: given repository owner are not registered")
  118. log.GitLogger.Fatal(2, "Unregistered owner: %s", repoUserName)
  119. }
  120. println("Gogs: internal error:", err.Error())
  121. log.GitLogger.Fatal(2, "Fail to get repository owner(%s): %v", repoUserName, err)
  122. }
  123. // Access check.
  124. repo, err := models.GetRepositoryByName(repoUser.Id, repoName)
  125. if err != nil {
  126. if err == models.ErrRepoNotExist {
  127. println("Gogs: given repository does not exist")
  128. log.GitLogger.Fatal(2, "Repository does not exist: %s/%s", repoUser.Name, repoName)
  129. }
  130. println("Gogs: internal error:", err.Error())
  131. log.GitLogger.Fatal(2, "Fail to get repository: %v", err)
  132. }
  133. switch {
  134. case isWrite:
  135. has, err := models.HasAccess(user, repo, models.ACCESS_MODE_WRITE)
  136. if err != nil {
  137. println("Gogs: internal error:", err.Error())
  138. log.GitLogger.Fatal(2, "Fail to check write access:", err)
  139. } else if !has {
  140. println("You have no right to write this repository")
  141. log.GitLogger.Fatal(2, "User %s has no right to write repository %s", user.Name, repoPath)
  142. }
  143. if repo.IsMirror {
  144. println("You can't write to a mirror repository")
  145. log.GitLogger.Fatal(2, "User %s tried to write to a mirror repository %s", user.Name, repoPath)
  146. }
  147. case isRead:
  148. if !repo.IsPrivate {
  149. break
  150. }
  151. has, err := models.HasAccess(user, repo, models.ACCESS_MODE_READ)
  152. if err != nil {
  153. println("Gogs: internal error:", err.Error())
  154. log.GitLogger.Fatal(2, "Fail to check read access:", err)
  155. } else if !has {
  156. println("You have no right to access this repository")
  157. log.GitLogger.Fatal(2, "User %s has no right to read repository %s", user.Name, repoPath)
  158. }
  159. default:
  160. println("Unknown command: " + cmd)
  161. return
  162. }
  163. uuid := uuid.NewV4().String()
  164. os.Setenv("uuid", uuid)
  165. var gitcmd *exec.Cmd
  166. verbs := strings.Split(verb, " ")
  167. if len(verbs) == 2 {
  168. gitcmd = exec.Command(verbs[0], verbs[1], repoPath)
  169. } else {
  170. gitcmd = exec.Command(verb, repoPath)
  171. }
  172. gitcmd.Dir = setting.RepoRootPath
  173. gitcmd.Stdout = os.Stdout
  174. gitcmd.Stdin = os.Stdin
  175. gitcmd.Stderr = os.Stderr
  176. if err = gitcmd.Run(); err != nil {
  177. println("Gogs: internal error:", err.Error())
  178. log.GitLogger.Fatal(2, "Fail to execute git command: %v", err)
  179. }
  180. if isWrite {
  181. tasks, err := models.GetUpdateTasksByUuid(uuid)
  182. if err != nil {
  183. log.GitLogger.Fatal(2, "GetUpdateTasksByUuid: %v", err)
  184. }
  185. for _, task := range tasks {
  186. err = models.Update(task.RefName, task.OldCommitId, task.NewCommitId,
  187. user.Name, repoUserName, repoName, user.Id)
  188. if err != nil {
  189. log.GitLogger.Error(2, "Fail to update: %v", err)
  190. }
  191. }
  192. if err = models.DelUpdateTasksByUuid(uuid); err != nil {
  193. log.GitLogger.Fatal(2, "DelUpdateTasksByUuid: %v", err)
  194. }
  195. }
  196. // Update key activity.
  197. key, err := models.GetPublicKeyById(keyId)
  198. if err != nil {
  199. log.GitLogger.Fatal(2, "GetPublicKeyById: %v", err)
  200. }
  201. key.Updated = time.Now()
  202. if err = models.UpdatePublicKey(key); err != nil {
  203. log.GitLogger.Fatal(2, "UpdatePublicKey: %v", err)
  204. }
  205. }