Go 实现SFTP连接服务

我们将SFTP连接和处理逻辑,以及登录账户信息封装,这样可以在不同的地方重用代码,并且可以轻松地更改登录凭据。下面我将演示如何使用Go语言中的结构体来封装这些信息,并实现一个简单的SFTP服务器:

package mainimport ("errors""fmt""io""io/ioutil""log""net""os""path/filepath""strings""github.com/pkg/sftp""golang.org/x/crypto/ssh"
)// 实现自定义请求处理程序
type CustomHandler struct {baseDir string // 基础目录,用于限制SFTP操作在特定目录下
}func (h *CustomHandler) Fileread(request *sftp.Request) (io.ReaderAt, error) {path := filepath.Join(h.baseDir, request.Filepath)file, err := os.Open(path)if err != nil {return nil, err}return file, nil
}func (h *CustomHandler) Filewrite(request *sftp.Request) (io.WriterAt, error) {path := filepath.Join(h.baseDir, request.Filepath)file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm)if err != nil {return nil, err}return file, nil
}func (h *CustomHandler) Filecmd(request *sftp.Request) error {path := filepath.Join(h.baseDir, request.Filepath)switch request.Method {case "Rename":// 对于重命名,request.Target 会包含新的文件名targetPath := filepath.Join(h.baseDir, request.Target)// 确保目标路径不在 baseDir 之外if !strings.HasPrefix(targetPath, h.baseDir) {return errors.New("invalid target path")}return os.Rename(path, targetPath)case "Rmdir":// 删除目录return os.Remove(path)case "Mkdir":// 创建目录return os.Mkdir(path, os.ModePerm)case "Remove":return os.Remove(path)case "Setstat", "Link", "Symlink":fallthroughdefault:log.Printf("Filecmd request %v", request)return errors.New("operation not supported")}return nil
}func (h *CustomHandler) Filelist(request *sftp.Request) (sftp.ListerAt, error) {path := filepath.Join(h.baseDir, request.Filepath)switch request.Method {case "List":// 检索目录内容files, err := ioutil.ReadDir(path)if err != nil {return nil, err}// 将 os.FileInfo 列表转换为 sftp.ListerAtreturn listerAt(files), nilcase "Stat":info, err := os.Stat(path)if err != nil {return nil, err}return listerAt([]os.FileInfo{info}), nilcase "Readlink":target, err := os.Readlink(path)if err != nil {return nil, err}info, err := os.Lstat(target)if err != nil {return nil, err}return listerAt([]os.FileInfo{info}), nil}return nil, nil
}// listerAt 是一个辅助类型,用于实现 sftp.ListerAt 接口
type listerAt []os.FileInfofunc (l listerAt) ListAt(list []os.FileInfo, offset int64) (int, error) {if offset >= int64(len(l)) {return 0, io.EOF}n := copy(list, l[offset:])return n, nil
}type SFTPServer struct {HostKeyPath stringAuthUser    stringAuthPass    stringPort        string
}func NewSFTPServer(hostKeyPath, user, pass, port string) *SFTPServer {return &SFTPServer{HostKeyPath: hostKeyPath,AuthUser:    user,AuthPass:    pass,Port:        port,}
}func (server *SFTPServer) Start() {config := &ssh.ServerConfig{PasswordCallback: server.passwordCallback,}privateBytes, err := os.ReadFile(server.HostKeyPath)if err != nil {log.Fatalf("Failed to load host key: %v", err)}private, err := ssh.ParsePrivateKey(privateBytes)if err != nil {log.Fatalf("Failed to parse host key: %v", err)}config.AddHostKey(private)listener, err := net.Listen("tcp", "0.0.0.0:"+server.Port)if err != nil {log.Fatalf("Failed to listen on port %s: %v", server.Port, err)}log.Printf("Listening on port %s...", server.Port)for {conn, err := listener.Accept()if err != nil {log.Printf("Failed to accept incoming connection: %v", err)continue}go server.handleConn(conn, config)}
}func (server *SFTPServer) handleConn(nConn net.Conn, config *ssh.ServerConfig) {sshConn, chans, reqs, err := ssh.NewServerConn(nConn, config)if err != nil {log.Printf("Failed to handshake: %v", err)return}defer sshConn.Close()go ssh.DiscardRequests(reqs)for newChannel := range chans {if newChannel.ChannelType() == "session" {go server.handleChannel(newChannel)} else {newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")}}
}func (server *SFTPServer) handleChannel(newChannel ssh.NewChannel) {if newChannel.ChannelType() != "session" {newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")return}channel, requests, err := newChannel.Accept()if err != nil {log.Printf("Could not accept channel (%s)", err)return}defer channel.Close()// 只在需要时创建 SFTP 服务器实例var sftpServer *sftp.Serverfor req := range requests {switch req.Type {case "subsystem":if string(req.Payload[4:]) == "sftp" {req.Reply(true, nil)baseDir, err := server.getWorkDir()if err != nil {log.Printf("Failed to create SFTP work dir: %v", err)return}handler := &CustomHandler{baseDir: baseDir,}sftpServer := sftp.NewRequestServer(channel, sftp.Handlers{FileGet:  handler,FilePut:  handler,FileCmd:  handler,FileList: handler,})if err := sftpServer.Serve(); err == io.EOF {log.Printf("SFTP client disconnected")return} else if err != nil {log.Printf("SFTP server completed with error: %v", err)return}} else {req.Reply(false, nil)}default:req.Reply(false, nil)}}if sftpServer == nil {log.Printf("No SFTP subsystem started")return}
}func (server *SFTPServer) passwordCallback(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {if c.User() == server.AuthUser && string(pass) == server.AuthPass {return nil, nil}return nil, fmt.Errorf("password rejected for %q", c.User())
}func (server *SFTPServer) getWorkDir() (string, error) {// 获取当前工作目录workingDir, err := os.Getwd()if err != nil {log.Fatalf("Unable to get current working directory: %v", err)return "", err}// 构建操作目录的完整路径baseDir := filepath.Join(workingDir, "sftp_tmp")// 确保目录存在err = os.MkdirAll(baseDir, os.ModePerm)if err != nil {log.Fatalf("Unable to create tmp directory: %v", err)return "", err}return baseDir, nil
}func main() {sftpServer := NewSFTPServer("~/.ssh/id_rsa", "root", "123456", "9527")sftpServer.Start()
}

效果图:
在这里插入图片描述

在这个封装中,SFTPServer结构体包含SSH服务器的配置信息,如主机密钥路径、授权用户名、密码和监听端口。NewSFTPServer函数是构造函数,用于创建SFTPServer实例。Start方法启动SFTP服务器并监听指定端口。

注意:请确保替换 hostKeyPathusernamepasswordport 这些参数为您自己的设置。~/.ssh/id_rsa 是您的服务器私钥文件的路径,您需要将其替换为实际的文件路径。root123456 是您希望用户使用的登录凭证。9527 是您希望SFTP服务器监听的端口号。

在上述封装代码中,Start 方法会启动SFTP服务器并等待连接。对于每个新连接,都会在一个新的goroutine中调用 handleConn 方法,进行SSH握手并处理SFTP会话。handleConn 方法会处理新通道,并将每个新通道传递给 handleChannel 方法,后者配置并启动SFTP服务。

passwordCallback 方法是一个回调函数,用于在SSH握手过程中验证用户凭证。如果提供的用户名和密码与结构体中定义的匹配,则会允许连接。

最后,main 函数实例化了 SFTPServer 并启动了SFTP服务。您需要确保您的系统中有SSH私钥文件,并且您有权使用指定的端口。

在实际部署中,您应该使用更安全的方法存储用户凭据,例如使用加密的方式,或者通过集成现有的用户管理系统,而不是将用户名和密码硬编码在代码中。

此外,您可能还需要添加更多的功能,例如支持基于公钥的认证、限制用户的文件系统访问权限、记录日志到文件等。这些功能可以根据需要扩展SFTPServer结构体和相关方法。

在您的main函数中调用sftpServer.Start(),就可以启动SFTP服务器。记得在正式环境中处理好错误和日志记录,确保服务的稳定性和安全性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/34973.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

配置Nginx 在服务器重启后自动启动

记录一下,配置Nginx 在服务器重启后自动启动,可以通过配置 systemd 服务来实现。以下是具体步骤: 检查 Nginx 服务的状态: 先确认 Nginx 是否已经安装并且可以通过 systemctl 管理: sudo systemctl status nginx如果…

大厂薪资福利篇第五弹:小红书

欢迎来到绝命Coding! 今天继续更新大家最关心的 大厂薪资福利系列! 为什么计算机学子对大厂趋之若鹜呢?最直接的原因就是高薪资的吸引力。 • 但是薪资可不是简单的数字哦,里面还是有很多“学问”的。 • 很多同学对大厂薪资只有一…

《黑神话悟空》电脑配置要求

《黑神话:悟空》这款国内优秀的3A游戏大作,拥有顶级的特效与故事剧情,自公布以来便备受玩家期待,其精美的画面与流畅的战斗体验,对玩家的电脑配置提出一定要求。那么这款优秀的游戏需要什么样的电脑配置,才…

老板舍不得买库存管理软件❓一招解决

在当今快节奏的商业环境中,仓库管理是企业运作中不可或缺的一环。对于许多中小型企业而言,简易且高效的库存管理系统尤为重要。搭贝简易库存管理系统针对仓库的出入库进行有效管理,帮助企业实现库存的透明化和流程的自动化。 客户的痛点 1. …

基于SSM构建的校园失眠与压力管理系统的设计与实现【附源码】

毕业设计(论文) 题目:基于SSM构建的校园失眠与压力管理系统的设计与实现 二级学院: 专业(方向): 班 级: 学 生: 指导教师&a…

SNEC天合储能秀:全球首发多元场景一站式工商业储能融合解决方案

6月13日-15日,SNEC2024光伏与智慧能源展在上海隆重举行,来自全球95个国家和地区3000家国内外展商齐聚展会,5000行业专家共话产业发展。致力于成为全球光储智慧能源解决方案的领导者,天合光能(展位号:7.2H-E…

2024-06-24 问AI: 在大语言模型中,什么是ray?

文心一言 在大语言模型中,Ray是一个开源的统一框架,用于扩展AI和Python应用程序,特别是在机器学习领域。以下是关于Ray的详细解释: 核心功能: Ray为并行处理提供了计算层,使得用户无需成为分布式系统专家…

GitLab 不小心提交了master/develop版本如何回退

1. 找寻最近的版本,使用git reset --hard 回退到具体的提交版本号 2. git push origin master --force 这个会遇到gitlab默认拦截,处理版本 版本仓库页面,选择Setting——Repository,找到Protected branches 3. 再回到master分支…

Linux系统SPI子系统框架驱动调用实现详解

大家好,今天主要和大家分享一下,如何使用Linux系统中SPI子系统框架,也是分为主机驱动和设备驱动,裸机部分控制的是SPI控制器驱动,可以直接操控。 第一:Linux系统SPI主机驱动 SPI主机驱动就是SOC的SPI控制器驱动。Linux内核使用spi_master表示主机SPI驱动spi_master 是个结…

代码随想录算法训练营第四十九天| 300.最长递增子序列、 674. 最长连续递增序列、 718. 最长重复子数组

LeetCode 300.最长递增子序列 题目链接:https://leetcode.cn/problems/longest-increasing-subsequence/description/ 文章链接:https://programmercarl.com/0300.%E6%9C%80%E9%95%BF%E4%B8%8A%E5%8D%87%E5%AD%90%E5%BA%8F%E5%88%97.html 思路 * dp[i]…

二级web基础操作题练习

---------要求--------- 利用HTML和CSS实现如图所示页面: ---------代码示例--------- 分析:该页面包含一个标题、一个副标题、“姓名信息”的表格,并且有一段文字提示用户仔细填写,使用内联CSS来控制HTML页面的视觉外观&…

x264 码率控制 MBtree 原理:数学模型

x264 码率控制 MBtree 原理 关于x264 码率控制中 MBtree 算法的原理具体可以参考:x264 码率控制MBtree原理。关于macroblock_tree函数分析可以参考:x264 码率控制 MBtree 原理:macroblock_tree 函数。关于 macroblock_tree_propagate函数的分析可以参考:x264 码率控制 MBtr…

算法题--华为od机试考试(最大坐标值、寻找最富裕的小家庭、两个字符串间的最短路径问题)

目录 最大坐标值 题目描述 输入描述 输出描述 示例1 输入 输出 说明 解析 答案 寻找最富裕的小家庭 题目描述 输入描述 输出描述 示例1 输入 输出 说明 解析 答案 两个字符串间的最短路径问题 题目描述 ​编辑 输入描述 输出描述 示例1 输入 输出 …

【Linux】解决windows下文件到linux下文件格式^M的问题之tr命令、sed命令

方法一: sed -i s/^M/ /g 方法二 : tr -d "^M" 1. 删除 -d 2. 替换字符

【C++STL】Vector扩容机制

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

论文阅读--《FourierGNN:从纯图的角度重新思考多元时间序列预测》

Yi K, Zhang Q, Fan W, et al. FourierGNN: Rethinking multivariate time series forecasting from a pure graph perspective[J]. Advances in Neural Information Processing Systems, 2024, 36. 本次介绍的文章来自NeurIPS 2023&#xff0c;关于多变量时间序列的预测 摘要…

Java基础 - 练习(九)反射的常用方法(访问字段)

对任意的一个Object实例&#xff0c;只要我们获取了它的Class&#xff0c;就可以获取它的一切信息。 Class类提供了以下几个方法来获取字段&#xff1a; Field getField(name)&#xff1a;根据字段名获取某个public的field&#xff08;包括父类)Field.getDeclaredField(name)…

excel表格加密:电脑文件加密的5个方法介绍【新手篇】

为了防止数据泄露&#xff0c;编辑好表格文件后一般都会加上密码。敏感数据的泄露会导致严重的商业损失和声誉损害。Excel表格加密方法有很多&#xff0c;包括金舟文件夹加密大师、金舟ZIP解压缩、工作簿密码设置等方法。 下面分享5个excel表格加密方法&#xff0c;希望能够帮到…

canvas入门详细教程(W3C)

文章目录 一、线形1、画线形之前&#xff0c;最基本的方法需要知道&#xff1a;2、线形的样式设置&#xff1a;3、不同的线形路径给不同的样式设置-需要知道俩个方法&#xff1a;4、画线形三角5、画贝塞尔曲线6、画虚线 二、画矩形1、绘制空心矩形有三种方法2、绘制填充矩形有俩…

C++——布隆过滤器

目录 布隆过滤器的提出 布隆过滤器的概念 布隆过滤器的基本原理和特点 布隆过滤器的实现 布隆过滤器的插入 布隆过滤器的查找 布隆过滤器的删除 布隆过滤器的优点 布隆过滤器的缺陷 布隆过滤器使用场景 布隆过滤器的提出 在注册账号设置昵称的时候&#xff0c;为了保证…