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.

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