golang实现ftp功能简单又实用

配置文件部分:

Go 复制代码
FtpServer:
  ListenAddr: ":2121"
  ListenDir: "/data/ftpwww" # ftp目录
  PassivePortRange: # end必须大于start,否则会panic, 非被动模式不用配置
    Start: 3121
    End: 3124
  ListenUsers: # ftp账号
    - Username: ftpadmin
      Password: "123456..."

    - Username: ftpadmin
      Password: "yueduo-654321..."

加载配置文件

Go 复制代码
package config

import (
	"fmt"
	"gopkg.in/yaml.v3"
	"os"
)

var (
	Conf Config
)

type Config struct {
...
	FtpServer  FtpServer  `yaml:"FtpServer"`
...
}

func InitConfigFromYaml(file string) error {
	if file == "" {
		file = "config.yaml"
	}
	f, err := os.Open(file)
	if err != nil {
		return fmt.Errorf("配置文件打开错误.%w", err)
	}
	dec := yaml.NewDecoder(f)
	err = dec.Decode(&Conf)
	if err != nil {
		return err
	}
	return nil
}

main.go引用

Go 复制代码
//初始化配置文件
	err := config.InitConfigFromYaml(f)
	if err != nil {
		logn.Errorf("配置文件初始化错误,file:%s 错误信息:%v", f, err)
		return
	}

// 加载ftp
	go ftp.FtpDirver()

ftp部分perm

Go 复制代码
package ftp

import (
	"fmt"
	"os"
)

// 加载文件权限只读
type ReadOnlyPerm struct{}

func (p *ReadOnlyPerm) GetOwner(path string) (string, error) {
	return "ftp", nil
}

func (p *ReadOnlyPerm) GetGroup(path string) (string, error) {
	return "ftp", nil
}

func (p *ReadOnlyPerm) GetMode(path string) (os.FileMode, error) {
	// 0444: 只读(r--r--r--)
	return 0444, nil
}

func (p *ReadOnlyPerm) ChOwner(path, owner string) error {
	return fmt.Errorf("permission denied")
}

func (p *ReadOnlyPerm) ChGroup(path, group string) error {
	return fmt.Errorf("permission denied")
}

func (p *ReadOnlyPerm) ChMode(path string, mode os.FileMode) error {
	return fmt.Errorf("permission denied")
}

dirver.go

Go 复制代码
package ftp

import (
	"fmt"
	filedriver "github.com/goftp/file-driver"
	"github.com/goftp/server"
	"io"
	"log"
	"net"
	"os"
	"safe-agent/config"
)

type MultiUserAuth struct {
	users map[string]config.FTPUser
}

type PerUserDriverFactory struct {
	users map[string]config.FTPUser
}

type PerUserFileDriver struct {
	*filedriver.FileDriver
	users map[string]config.FTPUser
}

func (d *PerUserFileDriver) Init(conn *server.Conn) {
	// 🔥 每个用户设置自己的根目录
	d.FileDriver.RootPath = config.Conf.FtpServer.ListenDir
}

func (d *PerUserFileDriver) Delete(path string) error {
	return fmt.Errorf("550 permission denied")
}

func (d *PerUserFileDriver) DeleteFile(path string) error {
	return fmt.Errorf("550 permission denied")
}

func (d *PerUserFileDriver) Rename(from, to string) error {
	return fmt.Errorf("550 permission denied")
}

func (d *PerUserFileDriver) PutFile(destPath string, data io.Reader, appendData bool) (int64, error) {
	return 0, fmt.Errorf("550 permission denied")
}

func (d *PerUserFileDriver) Chmod(path string, mode os.FileMode) error {
	return fmt.Errorf("550 permission denied")
}

func (d *PerUserFileDriver) Remove(path string) error {
	return fmt.Errorf("550 permission denied")
}

func (d *PerUserFileDriver) RemoveDir(path string) error {
	return fmt.Errorf("550 permission denied")
}

func (d *PerUserFileDriver) MakeDir(path string) error {
	return fmt.Errorf("550 permission denied")
}

func (f *PerUserDriverFactory) NewDriver() (server.Driver, error) {
	return &PerUserFileDriver{
		FileDriver: &filedriver.FileDriver{
			Perm: &ReadOnlyPerm{},
		},
		users: f.users,
	}, nil
}

func (a *MultiUserAuth) CheckPasswd(user, pass string) (bool, error) {
	u, ok := a.users[user]
	if !ok {
		return false, nil
	}
	return u.Password == pass, nil
}

func FtpDirver() {
	userMap := make(map[string]config.FTPUser)

	for _, u := range config.Conf.FtpServer.ListenUsers {
		userMap[u.Username] = u
	}

	host, portStr, _ := net.SplitHostPort(config.Conf.FtpServer.ListenAddr)
	port := 0
	if portStr != "" {
		fmt.Sscanf(portStr, "%d", &port)
	}
	passiveRange := config.Conf.FtpServer.PassivePortRange

	passivePorts := fmt.Sprintf("%d-%d", passiveRange.Start, passiveRange.End)

	opts := &server.ServerOpts{
		Factory: &PerUserDriverFactory{
			users: userMap,
		},
		PassivePorts: passivePorts,
		Auth:         &MultiUserAuth{users: userMap},
		Port:         port,
		Hostname:     host,
	}

	//log.Printf("FTP server listening on %s", config.Conf.FtpServer.ListenAddr)
	fmt.Printf(
		"FTP listen=%s passive_ports=%s",
		config.Conf.FtpServer.ListenAddr,
		passivePorts,
	)
	s := server.NewServer(opts)
	log.Fatal(s.ListenAndServe())
}
相关推荐
我命由我123451 小时前
SQL 检测脚本
java·数据库·sql·功能测试·单元测试·压力测试·可用性测试
Hpc_Fun1 小时前
GPU 工作站品牌怎么比?五把可核验尺子、加权评分脚本与场景重排
java·信创·gpu工作站·品牌对比·加权评分
const_qiu1 小时前
第四章 03:4.3 Tools —— 让 Agent 会用工具
java·人工智能
Rain的Java大神之路2 小时前
百万并发直播弹幕系统设计全解:看懂了扇出,你就看懂了高并发的八成
java·后端·面试
Hilaku2 小时前
为什么我在团队里禁止使用 axios?
前端·javascript·程序员
兮动人2 小时前
Chrome 152 升级后本地请求全挂了?是 LNA 策略在搞事
前端·chrome·lna·local network
JarvanMo2 小时前
让 3D 在 Flutter 中焕发生机:Flutter GPU 与 flutter_scene 实战经验
前端
\光辉岁月/2 小时前
2.异常类
java·开发语言·异常类
十年Java程序媛2 小时前
JVM 基础与 GC 垃圾回收详解|可达性分析、分代模型、垃圾收集器、STW
java·开发语言·jvm·spring boot·后端·面试
2301_32241428042 小时前
基于SSM框架的在线考试管理系统的设计与实现14750-计算机毕设 原创(免费领源码+部署教程+开发环境)
java·redis·mysql·tomcat·maven·mybatis·ssm