duanquan1876 2015-11-21 18:22
浏览 208

Golang SSH服务器:如何使用scp处理文件传输?

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?

  • 写回答

1条回答 默认 最新

  • duanjiu1950 2017-03-03 11:23
    关注

    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
    ", 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.

    评论

报告相同问题?

悬赏问题

  • ¥15 安卓adb backup备份应用数据失败
  • ¥15 eclipse运行项目时遇到的问题
  • ¥15 关于#c##的问题:最近需要用CAT工具Trados进行一些开发
  • ¥15 南大pa1 小游戏没有界面,并且报了如下错误,尝试过换显卡驱动,但是好像不行
  • ¥15 没有证书,nginx怎么反向代理到只能接受https的公网网站
  • ¥50 成都蓉城足球俱乐部小程序抢票
  • ¥15 yolov7训练自己的数据集
  • ¥15 esp8266与51单片机连接问题(标签-单片机|关键词-串口)(相关搜索:51单片机|单片机|测试代码)
  • ¥15 电力市场出清matlab yalmip kkt 双层优化问题
  • ¥30 ros小车路径规划实现不了,如何解决?(操作系统-ubuntu)