2014-07-26 08:24:27 +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.
package ssh
import (
2015-11-14 21:21:31 +03:00
"fmt"
2015-11-09 00:59:56 +03:00
"io"
2014-07-26 08:24:27 +04:00
"io/ioutil"
"net"
"os"
"os/exec"
2015-11-09 00:59:56 +03:00
"path/filepath"
2014-07-26 08:24:27 +04:00
"strings"
"github.com/Unknwon/com"
2015-11-09 00:59:56 +03:00
"golang.org/x/crypto/ssh"
2014-07-26 08:24:27 +04:00
2016-11-03 15:29:56 +03:00
"github.com/go-gitea/gitea/models"
"github.com/go-gitea/gitea/modules/log"
"github.com/go-gitea/gitea/modules/setting"
2014-07-26 08:24:27 +04:00
)
2015-11-09 00:59:56 +03:00
func cleanCommand ( cmd string ) string {
i := strings . Index ( cmd , "git" )
if i == - 1 {
return cmd
}
return cmd [ i : ]
}
func handleServerConn ( keyID string , chans <- chan ssh . NewChannel ) {
2014-07-26 08:24:27 +04:00
for newChan := range chans {
if newChan . ChannelType ( ) != "session" {
newChan . Reject ( ssh . UnknownChannelType , "unknown channel type" )
continue
}
2015-11-09 00:59:56 +03:00
ch , reqs , err := newChan . Accept ( )
2014-07-26 08:24:27 +04:00
if err != nil {
2015-11-09 00:59:56 +03:00
log . Error ( 3 , "Error accepting channel: %v" , err )
2014-07-26 08:24:27 +04:00
continue
}
go func ( in <- chan * ssh . Request ) {
2015-11-09 00:59:56 +03:00
defer ch . Close ( )
2014-07-26 08:24:27 +04:00
for req := range in {
2015-11-09 00:59:56 +03:00
payload := cleanCommand ( string ( req . Payload ) )
2014-07-26 08:24:27 +04:00
switch req . Type {
case "env" :
args := strings . Split ( strings . Replace ( payload , "\x00" , "" , - 1 ) , "\v" )
if len ( args ) != 2 {
2016-02-01 20:10:49 +03:00
log . Warn ( "SSH: Invalid env arguments: '%#v'" , args )
2015-12-13 14:15:10 +03:00
continue
2014-07-26 08:24:27 +04:00
}
args [ 0 ] = strings . TrimLeft ( args [ 0 ] , "\x04" )
_ , _ , err := com . ExecCmdBytes ( "env" , args [ 0 ] + "=" + args [ 1 ] )
if err != nil {
log . Error ( 3 , "env: %v" , err )
2015-11-09 00:59:56 +03:00
return
2014-07-26 08:24:27 +04:00
}
case "exec" :
2015-11-09 00:59:56 +03:00
cmdName := strings . TrimLeft ( payload , "'()" )
2016-02-01 20:10:49 +03:00
log . Trace ( "SSH: Payload: %v" , cmdName )
2015-11-30 23:40:05 +03:00
args := [ ] string { "serv" , "key-" + keyID , "--config=" + setting . CustomConf }
2016-02-01 20:10:49 +03:00
log . Trace ( "SSH: Arguments: %v" , args )
2015-11-30 23:40:05 +03:00
cmd := exec . Command ( setting . AppPath , args ... )
2016-03-18 13:13:16 +03:00
cmd . Env = append ( os . Environ ( ) , "SSH_ORIGINAL_COMMAND=" + cmdName )
2015-11-09 00:59:56 +03:00
stdout , err := cmd . StdoutPipe ( )
if err != nil {
2016-02-01 20:10:49 +03:00
log . Error ( 3 , "SSH: StdoutPipe: %v" , err )
2015-11-09 00:59:56 +03:00
return
}
stderr , err := cmd . StderrPipe ( )
if err != nil {
2016-02-01 20:10:49 +03:00
log . Error ( 3 , "SSH: StderrPipe: %v" , err )
2015-11-09 00:59:56 +03:00
return
}
input , err := cmd . StdinPipe ( )
if err != nil {
2016-02-01 20:10:49 +03:00
log . Error ( 3 , "SSH: StdinPipe: %v" , err )
2015-11-09 00:59:56 +03:00
return
2014-07-26 08:24:27 +04:00
}
2015-11-09 00:59:56 +03:00
2015-11-16 01:07:44 +03:00
// FIXME: check timeout
2015-11-09 00:59:56 +03:00
if err = cmd . Start ( ) ; err != nil {
2016-02-01 20:10:49 +03:00
log . Error ( 3 , "SSH: Start: %v" , err )
2015-11-09 00:59:56 +03:00
return
2015-11-14 21:21:31 +03:00
}
2016-01-25 22:04:46 +03:00
req . Reply ( true , nil )
2015-11-14 21:21:31 +03:00
go io . Copy ( input , ch )
io . Copy ( ch , stdout )
io . Copy ( ch . Stderr ( ) , stderr )
if err = cmd . Wait ( ) ; err != nil {
2016-02-01 20:10:49 +03:00
log . Error ( 3 , "SSH: Wait: %v" , err )
2015-11-09 00:59:56 +03:00
return
}
ch . SendRequest ( "exit-status" , false , [ ] byte { 0 , 0 , 0 , 0 } )
return
default :
2014-07-26 08:24:27 +04:00
}
}
2015-11-09 00:59:56 +03:00
} ( reqs )
2014-07-26 08:24:27 +04:00
}
}
2015-11-09 00:59:56 +03:00
func listen ( config * ssh . ServerConfig , port int ) {
listener , err := net . Listen ( "tcp" , "0.0.0.0:" + com . ToStr ( port ) )
2014-07-26 08:24:27 +04:00
if err != nil {
panic ( err )
}
for {
// Once a ServerConfig has been configured, connections can be accepted.
conn , err := listener . Accept ( )
if err != nil {
2016-02-01 20:10:49 +03:00
log . Error ( 3 , "SSH: Error accepting incoming connection: %v" , err )
2014-07-26 08:24:27 +04:00
continue
}
2016-02-01 20:10:49 +03:00
2014-07-26 08:24:27 +04:00
// Before use, a handshake must be performed on the incoming net.Conn.
2016-02-01 20:10:49 +03:00
// It must be handled in a separate goroutine,
// otherwise one user could easily block entire loop.
// For example, user could be asked to trust server key fingerprint and hangs.
go func ( ) {
log . Trace ( "SSH: Handshaking for %s" , conn . RemoteAddr ( ) )
sConn , chans , reqs , err := ssh . NewServerConn ( conn , config )
if err != nil {
if err == io . EOF {
log . Warn ( "SSH: Handshaking was terminated: %v" , err )
} else {
log . Error ( 3 , "SSH: Error on handshaking: %v" , err )
}
return
}
2015-11-09 00:59:56 +03:00
2016-02-01 20:10:49 +03:00
log . Trace ( "SSH: Connection from %s (%s)" , sConn . RemoteAddr ( ) , sConn . ClientVersion ( ) )
// The incoming Request channel must be serviced.
go ssh . DiscardRequests ( reqs )
go handleServerConn ( sConn . Permissions . Extensions [ "key-id" ] , chans )
} ( )
2014-07-26 08:24:27 +04:00
}
}
// Listen starts a SSH server listens on given port.
2015-11-09 00:59:56 +03:00
func Listen ( port int ) {
2014-07-26 08:24:27 +04:00
config := & ssh . ServerConfig {
PublicKeyCallback : func ( conn ssh . ConnMetadata , key ssh . PublicKey ) ( * ssh . Permissions , error ) {
2015-11-09 00:59:56 +03:00
pkey , err := models . SearchPublicKeyByContent ( strings . TrimSpace ( string ( ssh . MarshalAuthorizedKey ( key ) ) ) )
if err != nil {
log . Error ( 3 , "SearchPublicKeyByContent: %v" , err )
return nil , err
}
return & ssh . Permissions { Extensions : map [ string ] string { "key-id" : com . ToStr ( pkey . ID ) } } , nil
2014-07-26 08:24:27 +04:00
} ,
}
2015-11-14 21:21:31 +03:00
keyPath := filepath . Join ( setting . AppDataPath , "ssh/gogs.rsa" )
if ! com . IsExist ( keyPath ) {
os . MkdirAll ( filepath . Dir ( keyPath ) , os . ModePerm )
_ , stderr , err := com . ExecCmd ( "ssh-keygen" , "-f" , keyPath , "-t" , "rsa" , "-N" , "" )
if err != nil {
panic ( fmt . Sprintf ( "Fail to generate private key: %v - %s" , err , stderr ) )
}
2016-02-01 20:10:49 +03:00
log . Trace ( "SSH: New private key is generateed: %s" , keyPath )
2015-11-14 21:21:31 +03:00
}
privateBytes , err := ioutil . ReadFile ( keyPath )
2014-07-26 08:24:27 +04:00
if err != nil {
2016-02-01 20:10:49 +03:00
panic ( "SSH: Fail to load private key" )
2014-07-26 08:24:27 +04:00
}
private , err := ssh . ParsePrivateKey ( privateBytes )
if err != nil {
2016-02-01 20:10:49 +03:00
panic ( "SSH: Fail to parse private key" )
2014-07-26 08:24:27 +04:00
}
config . AddHostKey ( private )
go listen ( config , port )
}