@ -1,21 +1,32 @@
// 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
package models
import (
import (
"bufio"
"errors"
"fmt"
"fmt"
"io"
"os"
"os"
"os/exec"
"os/exec"
"path"
"path/filepath"
"path/filepath"
"strings"
"sync"
"time"
"time"
"github.com/Unknwon/com"
"github.com/Unknwon/com"
)
)
var (
var (
sshOpLocker = sync . Mutex { }
//publicKeyRootPath string
//publicKeyRootPath string
sshPath string
sshPath string
appPath string
appPath string
tmplPublicKey = "### autogenerated by gitgos, DO NOT EDIT\n" +
// "### autogenerated by gitgos, DO NOT EDIT\n"
"command=\"%s serv key-%d\",no-port-forwarding," +
tmplPublicKey = "command=\"%s serv key-%d\",no-port-forwarding," +
"no-X11-forwarding,no-agent-forwarding,no-pty %s\n"
"no-X11-forwarding,no-agent-forwarding,no-pty %s\n"
)
)
@ -50,26 +61,57 @@ type PublicKey struct {
Id int64
Id int64
OwnerId int64 ` xorm:"index" `
OwnerId int64 ` xorm:"index" `
Name string ` xorm:"unique not null" `
Name string ` xorm:"unique not null" `
Fingerprint string
Content string ` xorm:"text not null" `
Content string ` xorm:"text not null" `
Created time . Time ` xorm:"created" `
Created time . Time ` xorm:"created" `
Updated time . Time ` xorm:"updated" `
Updated time . Time ` xorm:"updated" `
}
}
var (
ErrKeyAlreadyExist = errors . New ( "Public key already exist" )
)
func GenAuthorizedKey ( keyId int64 , key string ) string {
func GenAuthorizedKey ( keyId int64 , key string ) string {
return fmt . Sprintf ( tmplPublicKey , appPath , keyId , key )
return fmt . Sprintf ( tmplPublicKey , appPath , keyId , key )
}
}
func AddPublicKey ( key * PublicKey ) error {
func AddPublicKey ( key * PublicKey ) ( err error ) {
_ , err := orm . Insert ( key )
// Check if public key name has been used.
has , err := orm . Get ( key )
if err != nil {
if err != nil {
return err
return err
} else if has {
return ErrKeyAlreadyExist
}
}
err = SaveAuthorizedKeyFile ( key )
// Calculate fingerprint.
tmpPath := filepath . Join ( os . TempDir ( ) , fmt . Sprintf ( "%d" , time . Now ( ) . Nanosecond ( ) ) ,
"id_rsa.pub" )
os . MkdirAll ( path . Dir ( tmpPath ) , os . ModePerm )
f , err := os . Create ( tmpPath )
if err != nil {
return
}
if _ , err = f . WriteString ( key . Content ) ; err != nil {
return err
}
f . Close ( )
stdout , _ , err := com . ExecCmd ( "ssh-keygen" , "-l" , "-f" , tmpPath )
if err != nil {
if err != nil {
_ , err2 := orm . Delete ( key )
return err
if err2 != nil {
} else if len ( stdout ) < 2 {
// TODO: log the error
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 err
}
}
@ -77,10 +119,72 @@ func AddPublicKey(key *PublicKey) error {
return nil
return nil
}
}
func DeletePublicKey ( key * PublicKey ) error {
// DeletePublicKey deletes SSH key information both in database and authorized_keys file.
_ , err := orm . Delete ( key )
func DeletePublicKey ( key * PublicKey ) ( err error ) {
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
}
sshOpLocker . Lock ( )
defer sshOpLocker . Unlock ( )
p := filepath . Join ( sshPath , "authorized_keys" )
tmpP := filepath . Join ( sshPath , "authorized_keys.tmp" )
fr , err := os . Open ( p )
if err != nil {
return err
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
}
}
if err = os . Remove ( p ) ; err != nil {
return err
}
return os . Rename ( tmpP , p )
}
func ListPublicKey ( userId int64 ) ( [ ] PublicKey , error ) {
func ListPublicKey ( userId int64 ) ( [ ] PublicKey , error ) {
keys := make ( [ ] PublicKey , 0 )
keys := make ( [ ] PublicKey , 0 )
@ -89,11 +193,16 @@ func ListPublicKey(userId int64) ([]PublicKey, error) {
}
}
func SaveAuthorizedKeyFile ( key * PublicKey ) error {
func SaveAuthorizedKeyFile ( key * PublicKey ) error {
sshOpLocker . Lock ( )
defer sshOpLocker . Unlock ( )
p := filepath . Join ( sshPath , "authorized_keys" )
p := filepath . Join ( sshPath , "authorized_keys" )
f , err := os . OpenFile ( p , os . O_CREATE | os . O_WRONLY | os . O_APPEND , 0600 )
f , err := os . OpenFile ( p , os . O_CREATE | os . O_WRONLY | os . O_APPEND , 0600 )
if err != nil {
if err != nil {
return err
return err
}
}
defer f . Close ( )
//os.Chmod(p, 0600)
//os.Chmod(p, 0600)
_ , err = f . WriteString ( GenAuthorizedKey ( key . Id , key . Content ) )
_ , err = f . WriteString ( GenAuthorizedKey ( key . Id , key . Content ) )
return err
return err