diff options
Diffstat (limited to 'models/publickey.go')
-rw-r--r-- | models/publickey.go | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/models/publickey.go b/models/publickey.go new file mode 100644 index 00000000..ed47ff20 --- /dev/null +++ b/models/publickey.go @@ -0,0 +1,228 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package models + +import ( + "bufio" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "path" + "path/filepath" + "strings" + "sync" + "time" + + "github.com/Unknwon/com" + + "github.com/gogits/gogs/modules/log" +) + +const ( + // "### autogenerated by gitgos, DO NOT EDIT\n" + TPL_PUBLICK_KEY = `command="%s serv key-%d",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` +) + +var ( + ErrKeyAlreadyExist = errors.New("Public key already exist") +) + +var sshOpLocker = sync.Mutex{} + +var ( + sshPath string + appPath string +) + +// exePath returns the executable path. +func exePath() (string, error) { + file, err := exec.LookPath(os.Args[0]) + if err != nil { + return "", err + } + return filepath.Abs(file) +} + +// homeDir returns the home directory of current user. +func homeDir() string { + home, err := com.HomeDir() + if err != nil { + return "/" + } + return home +} + +func init() { + var err error + + appPath, err = exePath() + if err != nil { + fmt.Printf("publickey.init(fail to get app path): %v\n", err) + os.Exit(2) + } + + // Determine and create .ssh path. + sshPath = filepath.Join(homeDir(), ".ssh") + if err = os.MkdirAll(sshPath, os.ModePerm); err != nil { + fmt.Printf("publickey.init(fail to create sshPath(%s)): %v\n", sshPath, err) + os.Exit(2) + } +} + +// PublicKey represents a SSH key of user. +type PublicKey struct { + Id int64 + OwnerId int64 `xorm:"unique(s) index not null"` + Name string `xorm:"unique(s) not null"` + Fingerprint string + Content string `xorm:"TEXT not null"` + Created time.Time `xorm:"created"` + Updated time.Time `xorm:"updated"` +} + +// GenAuthorizedKey returns formatted public key string. +func GenAuthorizedKey(keyId int64, key string) string { + return fmt.Sprintf(TPL_PUBLICK_KEY+"\n", appPath, keyId, key) +} + +// AddPublicKey adds new public key to database and SSH key file. +func AddPublicKey(key *PublicKey) (err error) { + // Check if public key name has been used. + has, err := orm.Get(key) + if err != nil { + return err + } else if has { + return ErrKeyAlreadyExist + } + + // Calculate fingerprint. + tmpPath := strings.Replace(filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()), + "id_rsa.pub"), "\\", "/", -1) + os.MkdirAll(path.Dir(tmpPath), os.ModePerm) + if err = ioutil.WriteFile(tmpPath, []byte(key.Content), os.ModePerm); err != nil { + return err + } + stdout, _, err := com.ExecCmd("ssh-keygen", "-l", "-f", tmpPath) + if err != nil { + return err + } else if len(stdout) < 2 { + return errors.New("Not enough output for calculating fingerprint") + } + key.Fingerprint = strings.Split(stdout, " ")[1] + + // Save SSH key. + if _, err = orm.Insert(key); err != nil { + return err + } + if err = SaveAuthorizedKeyFile(key); err != nil { + if _, err2 := orm.Delete(key); err2 != nil { + return err2 + } + return err + } + + return nil +} + +func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error { + // Delete SSH key in SSH key file. + sshOpLocker.Lock() + defer sshOpLocker.Unlock() + + fr, err := os.Open(p) + if err != nil { + return err + } + defer fr.Close() + + fw, err := os.Create(tmpP) + if err != nil { + return err + } + defer fw.Close() + + buf := bufio.NewReader(fr) + for { + line, errRead := buf.ReadString('\n') + line = strings.TrimSpace(line) + + if errRead != nil { + if errRead != io.EOF { + return errRead + } + + // Reached end of file, if nothing to read then break, + // otherwise handle the last line. + if len(line) == 0 { + break + } + } + + // Found the line and copy rest of file. + if strings.Contains(line, fmt.Sprintf("key-%d", key.Id)) && strings.Contains(line, key.Content) { + continue + } + // Still finding the line, copy the line that currently read. + if _, err = fw.WriteString(line + "\n"); err != nil { + return err + } + + if errRead == io.EOF { + break + } + } + return nil +} + +// DeletePublicKey deletes SSH key information both in database and authorized_keys file. +func DeletePublicKey(key *PublicKey) (err error) { + // Delete SSH key in database. + has, err := orm.Id(key.Id).Get(key) + if err != nil { + return err + } else if !has { + return errors.New("Public key does not exist") + } + if _, err = orm.Delete(key); err != nil { + return err + } + + p := filepath.Join(sshPath, "authorized_keys") + tmpP := filepath.Join(sshPath, "authorized_keys.tmp") + log.Trace("ssh.DeletePublicKey(authorized_keys): %s", p) + + if err = rewriteAuthorizedKeys(key, p, tmpP); err != nil { + return err + } else if err = os.Remove(p); err != nil { + return err + } + return os.Rename(tmpP, p) +} + +// ListPublicKey returns a list of public keys that user has. +func ListPublicKey(userId int64) ([]PublicKey, error) { + keys := make([]PublicKey, 0) + err := orm.Find(&keys, &PublicKey{OwnerId: userId}) + return keys, err +} + +// SaveAuthorizedKeyFile writes SSH key content to SSH key file. +func SaveAuthorizedKeyFile(key *PublicKey) error { + sshOpLocker.Lock() + defer sshOpLocker.Unlock() + + p := filepath.Join(sshPath, "authorized_keys") + f, err := os.OpenFile(p, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) + if err != nil { + return err + } + defer f.Close() + + _, err = f.WriteString(GenAuthorizedKey(key.Id, key.Content)) + return err +} |