2014-03-16 13:24:13 +04:00
// 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.
2014-02-17 19:57:23 +04:00
package models
import (
2014-03-16 13:24:13 +04:00
"bufio"
2014-05-07 00:28:52 +04:00
"bytes"
2014-03-16 13:48:20 +04:00
"errors"
2014-02-17 19:57:23 +04:00
"fmt"
2014-03-17 22:03:58 +04:00
"io/ioutil"
2014-02-17 19:57:23 +04:00
"os"
2014-03-16 14:16:03 +04:00
"path"
2014-02-17 19:57:23 +04:00
"path/filepath"
2014-03-16 13:24:13 +04:00
"strings"
"sync"
2014-02-17 19:57:23 +04:00
"time"
2014-03-03 00:25:09 +04:00
"github.com/Unknwon/com"
2014-05-07 00:28:52 +04:00
qlog "github.com/qiniu/log"
2014-03-22 22:27:03 +04:00
2014-05-07 00:28:52 +04:00
"github.com/gogits/gogs/modules/base"
2014-03-22 22:27:03 +04:00
"github.com/gogits/gogs/modules/log"
2014-02-17 19:57:23 +04:00
)
2014-03-17 22:03:58 +04:00
const (
// "### autogenerated by gitgos, DO NOT EDIT\n"
2014-03-18 01:00:35 +04:00
TPL_PUBLICK_KEY = ` command="%s serv key-%d",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s `
2014-03-17 22:03:58 +04:00
)
2014-02-17 19:57:23 +04:00
var (
2014-03-21 00:04:56 +04:00
ErrKeyAlreadyExist = errors . New ( "Public key already exist" )
2014-05-07 00:28:52 +04:00
ErrKeyNotExist = errors . New ( "Public key does not exist" )
2014-03-21 00:04:56 +04:00
)
2014-03-17 22:03:58 +04:00
2014-03-21 00:04:56 +04:00
var sshOpLocker = sync . Mutex { }
var (
2014-05-07 00:28:52 +04:00
sshPath string // SSH directory.
appPath string // Execution(binary) path.
2014-02-17 19:57:23 +04:00
)
2014-03-17 22:03:58 +04:00
// homeDir returns the home directory of current user.
2014-02-25 14:58:55 +04:00
func homeDir ( ) string {
2014-03-03 00:25:09 +04:00
home , err := com . HomeDir ( )
2014-02-25 14:58:55 +04:00
if err != nil {
2014-05-07 00:28:52 +04:00
qlog . Fatalln ( err )
2014-02-25 14:58:55 +04:00
}
2014-03-03 00:25:09 +04:00
return home
2014-02-25 14:58:55 +04:00
}
2014-02-25 12:13:47 +04:00
func init ( ) {
var err error
2014-03-17 22:03:58 +04:00
2014-05-07 00:28:52 +04:00
if appPath , err = base . ExecDir ( ) ; err != nil {
qlog . Fatalf ( "publickey.init(fail to get app path): %v\n" , err )
2014-02-25 12:13:47 +04:00
}
2014-02-25 14:58:55 +04:00
2014-03-17 22:03:58 +04:00
// Determine and create .ssh path.
2014-02-25 14:58:55 +04:00
sshPath = filepath . Join ( homeDir ( ) , ".ssh" )
2014-03-17 22:03:58 +04:00
if err = os . MkdirAll ( sshPath , os . ModePerm ) ; err != nil {
2014-05-07 00:28:52 +04:00
qlog . Fatalf ( "publickey.init(fail to create sshPath(%s)): %v\n" , sshPath , err )
2014-03-17 22:03:58 +04:00
}
2014-02-25 12:13:47 +04:00
}
2014-03-17 22:03:58 +04:00
// PublicKey represents a SSH key of user.
2014-02-17 19:57:23 +04:00
type PublicKey struct {
2014-03-16 14:16:03 +04:00
Id int64
2014-04-28 01:01:39 +04:00
OwnerId int64 ` xorm:"UNIQUE(s) INDEX NOT NULL" `
Name string ` xorm:"UNIQUE(s) NOT NULL" `
2014-03-16 14:16:03 +04:00
Fingerprint string
2014-04-28 01:01:39 +04:00
Content string ` xorm:"TEXT NOT NULL" `
Created time . Time ` xorm:"CREATED" `
Updated time . Time ` xorm:"UPDATED" `
2014-02-17 19:57:23 +04:00
}
2014-03-17 22:03:58 +04:00
// GenAuthorizedKey returns formatted public key string.
2014-02-25 12:13:47 +04:00
func GenAuthorizedKey ( keyId int64 , key string ) string {
2014-03-17 22:03:58 +04:00
return fmt . Sprintf ( TPL_PUBLICK_KEY + "\n" , appPath , keyId , key )
2014-02-17 19:57:23 +04:00
}
2014-03-17 22:03:58 +04:00
// AddPublicKey adds new public key to database and SSH key file.
2014-03-16 14:16:03 +04:00
func AddPublicKey ( key * PublicKey ) ( err error ) {
2014-03-16 14:25:16 +04:00
// Check if public key name has been used.
has , err := orm . Get ( key )
if err != nil {
return err
} else if has {
return ErrKeyAlreadyExist
}
2014-03-16 14:16:03 +04:00
// Calculate fingerprint.
2014-03-22 22:27:03 +04:00
tmpPath := strings . Replace ( filepath . Join ( os . TempDir ( ) , fmt . Sprintf ( "%d" , time . Now ( ) . Nanosecond ( ) ) ,
"id_rsa.pub" ) , "\\" , "/" , - 1 )
2014-03-16 14:16:03 +04:00
os . MkdirAll ( path . Dir ( tmpPath ) , os . ModePerm )
2014-03-17 22:03:58 +04:00
if err = ioutil . WriteFile ( tmpPath , [ ] byte ( key . Content ) , os . ModePerm ) ; err != nil {
2014-02-17 19:57:23 +04:00
return err
}
2014-04-28 01:01:39 +04:00
stdout , stderr , err := com . ExecCmd ( "ssh-keygen" , "-l" , "-f" , tmpPath )
2014-02-17 19:57:23 +04:00
if err != nil {
2014-04-28 01:01:39 +04:00
return errors . New ( "ssh-keygen -l -f: " + stderr )
2014-03-16 14:16:03 +04:00
} 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
2014-02-17 19:57:23 +04:00
}
return err
}
return nil
}
2014-05-07 00:28:52 +04:00
// rewriteAuthorizedKeys finds and deletes corresponding line in authorized_keys file.
2014-03-22 22:27:03 +04:00
func rewriteAuthorizedKeys ( key * PublicKey , p , tmpP string ) error {
2014-03-16 13:24:13 +04:00
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 ( )
2014-05-07 00:28:52 +04:00
isFound := false
keyword := [ ] byte ( fmt . Sprintf ( "key-%d" , key . Id ) )
content := [ ] byte ( key . Content )
snr := bufio . NewScanner ( fr )
for snr . Scan ( ) {
line := append ( bytes . TrimSpace ( snr . Bytes ( ) ) , '\n' )
if len ( line ) == 0 {
continue
2014-03-16 13:24:13 +04:00
}
// Found the line and copy rest of file.
2014-05-07 00:28:52 +04:00
if ! isFound && bytes . Contains ( line , keyword ) && bytes . Contains ( line , content ) {
isFound = true
2014-03-16 13:48:20 +04:00
continue
2014-03-16 13:24:13 +04:00
}
2014-05-07 00:28:52 +04:00
2014-03-16 13:24:13 +04:00
// Still finding the line, copy the line that currently read.
2014-05-07 00:28:52 +04:00
if _ , err = fw . Write ( line ) ; err != nil {
2014-03-16 13:24:13 +04:00
return err
}
}
2014-05-07 00:28:52 +04:00
2014-03-22 22:27:03 +04:00
return nil
}
2014-03-16 13:24:13 +04:00
2014-03-22 22:27:03 +04:00
// DeletePublicKey deletes SSH key information both in database and authorized_keys file.
2014-05-07 00:28:52 +04:00
func DeletePublicKey ( key * PublicKey ) error {
has , err := orm . Get ( key )
2014-03-22 22:27:03 +04:00
if err != nil {
return err
} else if ! has {
2014-05-07 00:28:52 +04:00
return ErrKeyNotExist
2014-03-22 22:27:03 +04:00
}
2014-05-07 00:28:52 +04:00
2014-03-22 22:27:03 +04:00
if _ , err = orm . Delete ( key ) ; err != nil {
return err
}
p := filepath . Join ( sshPath , "authorized_keys" )
tmpP := filepath . Join ( sshPath , "authorized_keys.tmp" )
2014-05-07 00:28:52 +04:00
log . Trace ( "publickey.DeletePublicKey(authorized_keys): %s" , p )
2014-03-22 22:27:03 +04:00
if err = rewriteAuthorizedKeys ( key , p , tmpP ) ; err != nil {
return err
} else if err = os . Remove ( p ) ; err != nil {
2014-03-16 13:24:13 +04:00
return err
}
return os . Rename ( tmpP , p )
2014-03-10 13:15:02 +04:00
}
2014-03-17 22:03:58 +04:00
// ListPublicKey returns a list of public keys that user has.
2014-03-07 07:34:41 +04:00
func ListPublicKey ( userId int64 ) ( [ ] PublicKey , error ) {
keys := make ( [ ] PublicKey , 0 )
err := orm . Find ( & keys , & PublicKey { OwnerId : userId } )
return keys , err
}
2014-03-17 22:03:58 +04:00
// SaveAuthorizedKeyFile writes SSH key content to SSH key file.
2014-02-25 12:13:47 +04:00
func SaveAuthorizedKeyFile ( key * PublicKey ) error {
2014-03-16 13:24:13 +04:00
sshOpLocker . Lock ( )
defer sshOpLocker . Unlock ( )
2014-02-25 12:13:47 +04:00
p := filepath . Join ( sshPath , "authorized_keys" )
2014-02-25 14:30:48 +04:00
f , err := os . OpenFile ( p , os . O_CREATE | os . O_WRONLY | os . O_APPEND , 0600 )
2014-02-17 19:57:23 +04:00
if err != nil {
return err
}
2014-03-16 13:24:13 +04:00
defer f . Close ( )
2014-02-25 12:13:47 +04:00
_ , err = f . WriteString ( GenAuthorizedKey ( key . Id , key . Content ) )
2014-02-17 19:57:23 +04:00
return err
}