I have written a small SSH-Server in golang with the crypto/ssh package.
It supports returning an interactive shell and immediate command execution.
Here is a minimal example of the server:
package main
import (
"fmt"
"io/ioutil"
"log"
"net"
"os/exec"
"golang.org/x/crypto/ssh"
)
func main() {
c := &ssh.ServerConfig{
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
if c.User() == "foo" && string(pass) == "bar" {
return nil, nil
}
return nil, fmt.Errorf("password rejected for %q", c.User())
},
}
keyBytes, _ := ioutil.ReadFile("key")
key, _ := ssh.ParsePrivateKey(keyBytes)
c.AddHostKey(key)
listener, _ := net.Listen("tcp", "0.0.0.0:2200")
for {
tcpConn, _ := listener.Accept()
_, chans, reqs, _ := ssh.NewServerConn(tcpConn, c)
go ssh.DiscardRequests(reqs)
go handleChannels(chans)
}
}
func handleChannels(chans <-chan ssh.NewChannel) {
for newChannel := range chans {
go handleChannel(newChannel)
}
}
func handleChannel(newChannel ssh.NewChannel) {
channel, requests, _ := newChannel.Accept()
for req := range requests {
switch req.Type {
case "shell":
go handleShell(channel)
case "exec":
go handleExec(channel, req)
}
}
}
func handleShell(c ssh.Channel) {}
func handleExec(c ssh.Channel, r *ssh.Request) {
cmdString, args, _ := parseCommand(r.Payload)
log.Printf("exec: %s
", cmdString)
for i := range args {
log.Printf("arg %d: %s
", i, args[i])
}
cmd := exec.Command(cmdString, args...)
cmd.Run()
}
func parseCommand(b []byte) (string, []string, error) {
cmdString := strings.TrimSpace(string(b))
cmdArray := strings.Split(cmdString, " ")
cmd := strings.Trim(cmdArray[0], " ")
args := cmdArray[1:]
return cmd, args, nil
}
If I run the server and execute scp as follows:
scp -P 2200 test.file foo@localhost:~/
the handleExec function is called.
The output of the cmdString shows:
2015/11/22 17:49:14 exec: scp
2015/11/22 17:49:14 arg 0: -t
2015/11/22 17:49:14 arg 1: ~/
But how can I implement the handleExec function to actually save the file/dir I passed via scp?