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,一经查实,立即删除!

相关文章

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

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

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

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

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

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

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

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

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

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

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 是个结…

二级web基础操作题练习

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

算法题--华为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;关于多变量时间序列的预测 摘要…

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;为了保证…

PUBG绝地求生·阿童木透视自瞄免费辅助 v6.24

在享受电子游戏的精彩世界时&#xff0c;家庭用户的数据安全和系统稳定性是不容忽视的重要方面。为了确保在使用游戏辅助工具时既能获得愉悦的游戏体验&#xff0c;又能保障个人数据和系统的安全&#xff0c;这里有一些建议和操作指南需要大家注意。 对于家庭用户而言&#x…

Java HashMap 简介

HashMap 简介 HashMap 主要用来存放键值对&#xff0c;它基于哈希表的 Map 接口实现&#xff0c;是常用的 Java 集合之一&#xff0c;是线程不安全的。 HashMap&#xff1b;可以存储 null 的 key 和 value &#xff0c;但 null 作为 key 只能有一个&#xff0c;null 作为值可以…

MAC Address

文章目录 1. 前言2. MAC Address2.1 MAC 地址格式2.2 Locally Administered MAC Address2.3 MAC 单播 和 多播 3. 参考资料 1. 前言 限于作者能力水平&#xff0c;本文可能存在谬误&#xff0c;因此而给读者带来的损失&#xff0c;作者不做任何承诺。 2. MAC Address 2.1 MA…

3d渲染软件有哪些(1),渲染100邀请码1a12

3D渲染是把三维模型转成2D图像的过程&#xff0c;领域不同常用的软件也不一样&#xff0c;今天我们就简单介绍几个。 在介绍前我们先推荐一个设计人员常用到的工具&#xff0c;就是网渲平台渲染100&#xff0c;通过它设计师可以把本地渲染放到云端进行&#xff0c;价格也不贵&a…

永洪bi里topN的设置/用法

要实现的效果&#xff1a;实现通过输入参数&#xff0c;进行图表top的排序筛选 图示&#xff1a; 筛选前&#xff1a; 输入3&#xff0c;看top3的值&#xff1a; 输入-3&#xff0c;看倒数3个的值&#xff1a; 设置步骤&#xff1a; 1️⃣&#xff1a;添加一个“文本参数组件…