| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 | // 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 modelsimport (	"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:"index"`	Name        string `xorm:"unique 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}
 |