2015-11-09 00:59:56 +03:00
// +build go1.4
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-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
2015-11-09 00:59:56 +03:00
"github.com/gogits/gogs/models"
2014-07-26 11:02:46 +04:00
"github.com/gogits/gogs/modules/log"
2015-11-09 00:59:56 +03:00
"github.com/gogits/gogs/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 {
2015-11-09 00:59:56 +03:00
return
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 , "'()" )
os . Setenv ( "SSH_ORIGINAL_COMMAND" , cmdName )
log . Trace ( "Payload: %v" , cmdName )
cmd := exec . Command ( setting . AppPath , "serv" , "key-" + keyID )
stdout , err := cmd . StdoutPipe ( )
if err != nil {
log . Error ( 3 , "StdoutPipe: %v" , err )
return
}
stderr , err := cmd . StderrPipe ( )
if err != nil {
log . Error ( 3 , "StderrPipe: %v" , err )
return
}
input , err := cmd . StdinPipe ( )
if err != nil {
log . Error ( 3 , "StdinPipe: %v" , err )
return
2014-07-26 08:24:27 +04:00
}
2015-11-09 00:59:56 +03:00
go io . Copy ( ch , stdout )
go io . Copy ( ch . Stderr ( ) , stderr )
go io . Copy ( input , ch )
if err = cmd . Start ( ) ; err != nil {
log . Error ( 3 , "Start: %v" , err )
return
} else if err = cmd . Wait ( ) ; err != nil {
log . Error ( 3 , "Wait: %v" , err )
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 {
2015-11-09 00:59:56 +03:00
log . Error ( 3 , "Error accepting incoming connection: %v" , err )
2014-07-26 08:24:27 +04:00
continue
}
// Before use, a handshake must be performed on the incoming net.Conn.
sConn , chans , reqs , err := ssh . NewServerConn ( conn , config )
if err != nil {
2015-11-09 00:59:56 +03:00
log . Error ( 3 , "Error on handshaking: %v" , err )
2014-07-26 08:24:27 +04:00
continue
}
2015-11-09 00:59:56 +03:00
log . Trace ( "Connection from %s (%s)" , sConn . RemoteAddr ( ) , sConn . ClientVersion ( ) )
2014-07-26 08:24:27 +04:00
// The incoming Request channel must be serviced.
go ssh . DiscardRequests ( reqs )
go handleServerConn ( sConn . Permissions . Extensions [ "key-id" ] , chans )
}
}
// 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-09 00:59:56 +03:00
privateBytes , err := ioutil . ReadFile ( filepath . Join ( models . SSHPath , "id_rsa" ) )
2014-07-26 08:24:27 +04:00
if err != nil {
2015-11-09 00:59:56 +03:00
panic ( "Fail to load private key" )
2014-07-26 08:24:27 +04:00
}
private , err := ssh . ParsePrivateKey ( privateBytes )
if err != nil {
2015-11-09 00:59:56 +03:00
panic ( "Fail to parse private key" )
2014-07-26 08:24:27 +04:00
}
config . AddHostKey ( private )
go listen ( config , port )
}