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.

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