Golang的SSH服务器:如何处理文件传输与SCP? [英] Golang SSH-Server: How to handle file transfer with scp?

查看:359
本文介绍了Golang的SSH服务器:如何处理文件传输与SCP?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



它支持返回交互式shell和立即执行命令。

我使用crypto / ssh包编写了golang中的小型SSH服务器。

以下是服务器的一个简单示例:

  package main 

进口(
fmt
io / ioutil
记录

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
}
返回nil,fmt.Errorf(密码拒绝%q,c.User())
$,
}

keyBytes,_:= ioutil.ReadFile(key)
key,_:= ssh.ParsePrivateKey(keyBytes)
c .AddHostKey(key)

监听器,_:= net.Listen(tcp,0.0.0.0:2200)
for {
tcpConn,_:= listener .ccept()
_,chans,reqs,_:= ssh.NewServerConn(tcpConn,c)
go ssh.DiscardRequests(reqs)
go handleChannels(chans)
}

$ b $ func handleChannels(chans< -chan ssh.NewChannel){
for newChannel:= range chans {
go handleChannel(newChannel)
}
}
$ b $ func handleChannel(newChannel ssh.NewChannel){
channel,requests,_:= newChannel.Accept()
for req:= range requests {
switch req.Type {
caseshell:
go handleS hell(channel)
caseexec:
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 \ n,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
}

如果我运行服务器并执行scp,如下所示:

  scp -P 2200 test.file foo @ localhost:〜/ 

handleExec函数i被称为。



cmdString的输出显示:

  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:〜/

但是我怎样才能实现handleExec函数来保存我通过scp传递的文件/目录?

解决方案

我刚刚遇到了通过我的ssh服务器执行scp和自定义命令的问题,因为它没有记录如何执行此操作将crypto.ssh中的一些代码拼凑在一起( https:// github.com/golang/crypto/blob/master/ssh/session.go https://github.com/golang/crypto/blob/master/ssh/session_test.go )它适用于OpenSSH和crypto.ssh客户端。你可以在你的客户端调用session.Run()来处理例如scp或自定义命令。

 类型exitStatusMsg结构{
状态uint32
}

// RFC 4254第6.5节。
类型execMsg结构{
命令字符串
}

去func(in< -chan * ssh.Request,channel ssh.Channel){
如果req.Type ==exec{
var msg execMsg
if err:= ssh.Unmarshal(req.Payload,& msg); err!= nil {
log.Printf(错误解析ssh execMsg:%s \ n,err)
req.Reply(false,nil)
return
}
去func(msg execMsg,ch ssh.Channel){
//如果应该有交互性,ch可以用作ReadWriteCloser
runYourCommand(msg.Command,ch)
ex:= exitStatusMsg {
状态:0,
}
//返回状态码
if _,err:= ch.SendRequest(exit-status,false, ssh.Marshal(安培; EX)); err!= nil {
log.Printf(无法发送状态:%v,err)
}
ch.Close()
}(msg,channel)
req.Reply(true,nil)//告诉另一端我们可以运行请求
} else {
req.Reply(req.Type ==shell,nil)

$ b $(请求,频道)

您需要用任何函数代替runYourCommand然后执行你的命令并设置退出代码为你的命令/进程返回的内容。

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\n", cmdString)
    for i := range args {
        log.Printf("arg %d: %s\n", 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?

解决方案

I just ran into the problem of executing scp and custom commands over my ssh server and as it is undocumented how to do this I pieced together some code from the tests in crypto.ssh (https://github.com/golang/crypto/blob/master/ssh/session.go and https://github.com/golang/crypto/blob/master/ssh/session_test.go) It works with OpenSSH and the crypto.ssh client. You can for instance call session.Run() on your client and handle e.g. scp or custom commands with it.

type exitStatusMsg struct {
    Status uint32
}

// RFC 4254 Section 6.5.
type execMsg struct {
    Command string
}

go func(in <-chan *ssh.Request, channel ssh.Channel) {
        for req := range in {
            if req.Type == "exec" {
                var msg execMsg
                if err := ssh.Unmarshal(req.Payload, &msg); err != nil {
                    log.Printf("error parsing ssh execMsg: %s\n", err)
                    req.Reply(false, nil)
                    return
                }
                go func(msg execMsg, ch ssh.Channel) {
                    // ch can be used as a ReadWriteCloser if there should be interactivity
                    runYourCommand(msg.Command, ch)
                    ex := exitStatusMsg{
                        Status: 0,
                    }
                    // return the status code
                    if _, err := ch.SendRequest("exit-status", false, ssh.Marshal(&ex)); err != nil {
                        log.Printf("unable to send status: %v", err)
                    }
                    ch.Close()
                }(msg, channel)
                req.Reply(true, nil) // tell the other end that we can run the request
            } else {
                req.Reply(req.Type == "shell", nil)
            }
        }
    }(requests, channel)

You need to replace runYourCommand with whatever function then executes your command and set the exit code to whatever your command/process returns.

这篇关于Golang的SSH服务器:如何处理文件传输与SCP?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆