feat: 初始化FTP服务器项目 - 支持Web管理界面
此提交包含在:
+185
@@ -0,0 +1,185 @@
|
||||
package ftp
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"ftp-server/config"
|
||||
"ftp-server/database"
|
||||
|
||||
ftpserver "github.com/fclairamb/ftpserverlib"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// Server FTP服务器
|
||||
type Server struct {
|
||||
config *config.Config
|
||||
db *database.DB
|
||||
ftpServer *ftpserver.FtpServer
|
||||
onlineMu sync.RWMutex
|
||||
onlineUsers map[string]*database.OnlineUser
|
||||
}
|
||||
|
||||
// NewServer 创建FTP服务器
|
||||
func NewServer(cfg *config.Config, db *database.DB) *Server {
|
||||
return &Server{
|
||||
config: cfg,
|
||||
db: db,
|
||||
onlineUsers: make(map[string]*database.OnlineUser),
|
||||
}
|
||||
}
|
||||
|
||||
// Start 启动FTP服务器
|
||||
func (s *Server) Start() error {
|
||||
ftpCfg := s.config.Get().FTP
|
||||
|
||||
// 确保FTP根目录存在
|
||||
if err := os.MkdirAll(ftpCfg.RootDir, 0755); err != nil {
|
||||
return fmt.Errorf("创建FTP根目录失败: %w", err)
|
||||
}
|
||||
|
||||
server := ftpserver.NewFtpServer(s)
|
||||
s.ftpServer = server
|
||||
|
||||
go func() {
|
||||
if err := server.ListenAndServe(); err != nil {
|
||||
log.Printf("FTP服务器错误: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
log.Printf("FTP服务器已启动: %s:%d", ftpCfg.Host, ftpCfg.Port)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop 停止FTP服务器
|
||||
func (s *Server) Stop() {
|
||||
if s.ftpServer != nil {
|
||||
s.ftpServer.Stop()
|
||||
log.Println("FTP服务器已停止")
|
||||
}
|
||||
}
|
||||
|
||||
// GetOnlineUsers 获取在线用户列表
|
||||
func (s *Server) GetOnlineUsers() []database.OnlineUser {
|
||||
s.onlineMu.RLock()
|
||||
defer s.onlineMu.RUnlock()
|
||||
|
||||
result := make([]database.OnlineUser, 0, len(s.onlineUsers))
|
||||
for _, u := range s.onlineUsers {
|
||||
result = append(result, *u)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// --- 实现 ftpserverlib.MainDriver 接口 ---
|
||||
|
||||
// GetSettings 返回FTP服务器设置
|
||||
func (s *Server) GetSettings() (*ftpserver.Settings, error) {
|
||||
ftpCfg := s.config.Get().FTP
|
||||
return &ftpserver.Settings{
|
||||
ListenAddr: fmt.Sprintf("%s:%d", ftpCfg.Host, ftpCfg.Port),
|
||||
PassiveTransferPortRange: ftpserver.PortRange{
|
||||
Start: ftpCfg.PassivePortMin,
|
||||
End: ftpCfg.PassivePortMax,
|
||||
},
|
||||
ConnectionTimeout: int(time.Duration(ftpCfg.IdleTimeout) * time.Second),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ClientConnected 客户端连接
|
||||
func (s *Server) ClientConnected(cc ftpserver.ClientContext) (string, error) {
|
||||
return "220 Welcome to FTP Server\r\n", nil
|
||||
}
|
||||
|
||||
// ClientDisconnected 客户端断开
|
||||
func (s *Server) ClientDisconnected(cc ftpserver.ClientContext) {
|
||||
s.onlineMu.Lock()
|
||||
defer s.onlineMu.Unlock()
|
||||
|
||||
for id, u := range s.onlineUsers {
|
||||
if u.IP == cc.RemoteAddr().String() {
|
||||
delete(s.onlineUsers, id)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AuthUser 认证用户
|
||||
func (s *Server) AuthUser(cc ftpserver.ClientContext, username, password string) (ftpserver.ClientDriver, error) {
|
||||
ftpCfg := s.config.Get().FTP
|
||||
|
||||
// 匿名登录
|
||||
if username == "anonymous" {
|
||||
if !ftpCfg.EnableAnonymous {
|
||||
return nil, fmt.Errorf("匿名访问未启用")
|
||||
}
|
||||
if err := os.MkdirAll(ftpCfg.RootDir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("创建根目录失败")
|
||||
}
|
||||
osFs := afero.NewOsFs()
|
||||
boundedFs := afero.NewBasePathFs(osFs, ftpCfg.RootDir)
|
||||
return boundedFs, nil
|
||||
}
|
||||
|
||||
// 数据库用户认证
|
||||
user, err := s.db.GetUser(username)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("认证失败")
|
||||
}
|
||||
if user == nil || !user.Enabled {
|
||||
return nil, fmt.Errorf("用户不存在或已禁用")
|
||||
}
|
||||
if user.Password != password {
|
||||
s.db.AddLog(&database.FTPLog{
|
||||
Username: username,
|
||||
IP: cc.RemoteAddr().String(),
|
||||
Action: "login_failed",
|
||||
Status: "failed",
|
||||
})
|
||||
return nil, fmt.Errorf("密码错误")
|
||||
}
|
||||
|
||||
// 记录登录日志
|
||||
s.db.AddLog(&database.FTPLog{
|
||||
Username: username,
|
||||
IP: cc.RemoteAddr().String(),
|
||||
Action: "login",
|
||||
Status: "success",
|
||||
})
|
||||
|
||||
// 记录在线用户
|
||||
s.onlineMu.Lock()
|
||||
s.onlineUsers[username+"_"+cc.RemoteAddr().String()] = &database.OnlineUser{
|
||||
Username: username,
|
||||
IP: cc.RemoteAddr().String(),
|
||||
LoginTime: time.Now(),
|
||||
LastActivity: time.Now(),
|
||||
CurrentDir: user.HomeDir,
|
||||
}
|
||||
s.onlineMu.Unlock()
|
||||
|
||||
// 确保用户目录存在(自动创建)
|
||||
if err := os.MkdirAll(user.HomeDir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("创建用户目录失败: %v", err)
|
||||
}
|
||||
|
||||
// 返回 afero.Fs 作为 ClientDriver
|
||||
osFs := afero.NewOsFs()
|
||||
boundedFs := afero.NewBasePathFs(osFs, user.HomeDir)
|
||||
|
||||
// 根据权限设置只读
|
||||
if user.Permissions == "read" {
|
||||
return afero.NewReadOnlyFs(boundedFs), nil
|
||||
}
|
||||
|
||||
return boundedFs, nil
|
||||
}
|
||||
|
||||
// GetTLSConfig 获取TLS配置
|
||||
func (s *Server) GetTLSConfig() (*tls.Config, error) {
|
||||
return nil, fmt.Errorf("TLS未配置")
|
||||
}
|
||||
新增問題並參考
封鎖使用者