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.

229 lines
5.4 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  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 models
  5. import (
  6. "bufio"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "io/ioutil"
  11. "os"
  12. "os/exec"
  13. "path"
  14. "path/filepath"
  15. "strings"
  16. "sync"
  17. "time"
  18. "github.com/Unknwon/com"
  19. qlog "github.com/qiniu/log"
  20. "github.com/gogits/gogs/modules/log"
  21. )
  22. const (
  23. // "### autogenerated by gitgos, DO NOT EDIT\n"
  24. _TPL_PUBLICK_KEY = `command="%s serv key-%d",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` + "\n"
  25. )
  26. var (
  27. ErrKeyAlreadyExist = errors.New("Public key already exist")
  28. ErrKeyNotExist = errors.New("Public key does not exist")
  29. )
  30. var sshOpLocker = sync.Mutex{}
  31. var (
  32. sshPath string // SSH directory.
  33. appPath string // Execution(binary) path.
  34. )
  35. // exePath returns the executable path.
  36. func exePath() (string, error) {
  37. file, err := exec.LookPath(os.Args[0])
  38. if err != nil {
  39. return "", err
  40. }
  41. return filepath.Abs(file)
  42. }
  43. // homeDir returns the home directory of current user.
  44. func homeDir() string {
  45. home, err := com.HomeDir()
  46. if err != nil {
  47. qlog.Fatalln(err)
  48. }
  49. return home
  50. }
  51. func init() {
  52. var err error
  53. if appPath, err = exePath(); err != nil {
  54. qlog.Fatalf("publickey.init(fail to get app path): %v\n", err)
  55. }
  56. // Determine and create .ssh path.
  57. sshPath = filepath.Join(homeDir(), ".ssh")
  58. if err = os.MkdirAll(sshPath, os.ModePerm); err != nil {
  59. qlog.Fatalf("publickey.init(fail to create sshPath(%s)): %v\n", sshPath, err)
  60. }
  61. }
  62. // PublicKey represents a SSH key.
  63. type PublicKey struct {
  64. Id int64
  65. OwnerId int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
  66. Name string `xorm:"UNIQUE(s) NOT NULL"`
  67. Fingerprint string
  68. Content string `xorm:"TEXT NOT NULL"`
  69. Created time.Time `xorm:"CREATED"`
  70. Updated time.Time `xorm:"UPDATED"`
  71. }
  72. // GetAuthorizedString generates and returns formatted public key string for authorized_keys file.
  73. func (key *PublicKey) GetAuthorizedString() string {
  74. return fmt.Sprintf(_TPL_PUBLICK_KEY, appPath, key.Id, key.Content)
  75. }
  76. // saveAuthorizedKeyFile writes SSH key content to authorized_keys file.
  77. func saveAuthorizedKeyFile(key *PublicKey) error {
  78. sshOpLocker.Lock()
  79. defer sshOpLocker.Unlock()
  80. fpath := filepath.Join(sshPath, "authorized_keys")
  81. f, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
  82. if err != nil {
  83. return err
  84. }
  85. defer f.Close()
  86. _, err = f.WriteString(key.GetAuthorizedString())
  87. return err
  88. }
  89. // AddPublicKey adds new public key to database and authorized_keys file.
  90. func AddPublicKey(key *PublicKey) (err error) {
  91. has, err := orm.Get(key)
  92. if err != nil {
  93. return err
  94. } else if has {
  95. return ErrKeyAlreadyExist
  96. }
  97. // Calculate fingerprint.
  98. tmpPath := strings.Replace(path.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()),
  99. "id_rsa.pub"), "\\", "/", -1)
  100. os.MkdirAll(path.Dir(tmpPath), os.ModePerm)
  101. if err = ioutil.WriteFile(tmpPath, []byte(key.Content), os.ModePerm); err != nil {
  102. return err
  103. }
  104. stdout, stderr, err := com.ExecCmd("ssh-keygen", "-l", "-f", tmpPath)
  105. if err != nil {
  106. return errors.New("ssh-keygen -l -f: " + stderr)
  107. } else if len(stdout) < 2 {
  108. return errors.New("Not enough output for calculating fingerprint")
  109. }
  110. key.Fingerprint = strings.Split(stdout, " ")[1]
  111. // Save SSH key.
  112. if _, err = orm.Insert(key); err != nil {
  113. return err
  114. } else if err = saveAuthorizedKeyFile(key); err != nil {
  115. // Roll back.
  116. if _, err2 := orm.Delete(key); err2 != nil {
  117. return err2
  118. }
  119. return err
  120. }
  121. return nil
  122. }
  123. // ListPublicKey returns a list of all public keys that user has.
  124. func ListPublicKey(uid int64) ([]PublicKey, error) {
  125. keys := make([]PublicKey, 0, 5)
  126. err := orm.Find(&keys, &PublicKey{OwnerId: uid})
  127. return keys, err
  128. }
  129. // rewriteAuthorizedKeys finds and deletes corresponding line in authorized_keys file.
  130. func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error {
  131. sshOpLocker.Lock()
  132. defer sshOpLocker.Unlock()
  133. fr, err := os.Open(p)
  134. if err != nil {
  135. return err
  136. }
  137. defer fr.Close()
  138. fw, err := os.Create(tmpP)
  139. if err != nil {
  140. return err
  141. }
  142. defer fw.Close()
  143. isFound := false
  144. keyword := fmt.Sprintf("key-%d", key.Id)
  145. buf := bufio.NewReader(fr)
  146. for {
  147. line, errRead := buf.ReadString('\n')
  148. line = strings.TrimSpace(line)
  149. if errRead != nil {
  150. if errRead != io.EOF {
  151. return errRead
  152. }
  153. // Reached end of file, if nothing to read then break,
  154. // otherwise handle the last line.
  155. if len(line) == 0 {
  156. break
  157. }
  158. }
  159. // Found the line and copy rest of file.
  160. if !isFound && strings.Contains(line, keyword) && strings.Contains(line, key.Content) {
  161. isFound = true
  162. continue
  163. }
  164. // Still finding the line, copy the line that currently read.
  165. if _, err = fw.WriteString(line + "\n"); err != nil {
  166. return err
  167. }
  168. if errRead == io.EOF {
  169. break
  170. }
  171. }
  172. return nil
  173. }
  174. // DeletePublicKey deletes SSH key information both in database and authorized_keys file.
  175. func DeletePublicKey(key *PublicKey) error {
  176. has, err := orm.Get(key)
  177. if err != nil {
  178. return err
  179. } else if !has {
  180. return ErrKeyNotExist
  181. }
  182. if _, err = orm.Delete(key); err != nil {
  183. return err
  184. }
  185. fpath := filepath.Join(sshPath, "authorized_keys")
  186. tmpPath := filepath.Join(sshPath, "authorized_keys.tmp")
  187. log.Trace("publickey.DeletePublicKey(authorized_keys): %s", fpath)
  188. if err = rewriteAuthorizedKeys(key, fpath, tmpPath); err != nil {
  189. return err
  190. } else if err = os.Remove(fpath); err != nil {
  191. return err
  192. }
  193. return os.Rename(tmpPath, fpath)
  194. }