fscan/plugins/services/rsync/connector.go
ZacharyZcR 4a3f281b6b refactor: 统一Plugins目录大小写为小写
- 将所有Plugins路径重命名为plugins
- 修复Git索引与实际文件系统大小写不一致问题
- 确保跨平台兼容性和路径一致性
2025-08-12 13:08:06 +08:00

377 lines
8.6 KiB
Go

package rsync
import (
"context"
"fmt"
"net"
"strconv"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// RsyncConnector Rsync连接器实现
type RsyncConnector struct {
host string
port int
}
// RsyncConnection Rsync连接结构
type RsyncConnection struct {
conn net.Conn
username string
password string
info string
modules []string
}
// NewRsyncConnector 创建Rsync连接器
func NewRsyncConnector() *RsyncConnector {
return &RsyncConnector{}
}
// Connect 建立Rsync连接
func (c *RsyncConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
// 解析端口
port, err := strconv.Atoi(info.Ports)
if err != nil {
return nil, fmt.Errorf("无效的端口号: %s", info.Ports)
}
c.host = info.Host
c.port = port
timeout := time.Duration(common.Timeout) * time.Second
address := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 结果通道
type connResult struct {
conn *RsyncConnection
err error
banner string
}
resultChan := make(chan connResult, 1)
// 在协程中尝试连接
go func() {
// 建立TCP连接
tcpConn, err := net.DialTimeout("tcp", address, timeout)
if err != nil {
select {
case <-ctx.Done():
case resultChan <- connResult{nil, err, ""}:
}
return
}
buffer := make([]byte, 1024)
// 读取服务器初始greeting
tcpConn.SetReadDeadline(time.Now().Add(timeout))
n, err := tcpConn.Read(buffer)
if err != nil {
tcpConn.Close()
select {
case <-ctx.Done():
case resultChan <- connResult{nil, err, ""}:
}
return
}
greeting := strings.TrimSpace(string(buffer[:n]))
if !strings.HasPrefix(greeting, "@RSYNCD:") {
tcpConn.Close()
select {
case <-ctx.Done():
case resultChan <- connResult{nil, fmt.Errorf("不是Rsync服务"), ""}:
}
return
}
// 获取服务器版本号
version := strings.TrimSpace(strings.TrimPrefix(greeting, "@RSYNCD:"))
// 回应相同的版本号
tcpConn.SetWriteDeadline(time.Now().Add(timeout))
_, err = tcpConn.Write([]byte(fmt.Sprintf("@RSYNCD: %s\n", version)))
if err != nil {
tcpConn.Close()
select {
case <-ctx.Done():
case resultChan <- connResult{nil, err, ""}:
}
return
}
// 获取模块列表
modules, err := c.getModuleList(tcpConn, timeout)
if err != nil {
tcpConn.Close()
select {
case <-ctx.Done():
case resultChan <- connResult{nil, err, ""}:
}
return
}
// 创建连接对象
rsyncConn := &RsyncConnection{
conn: tcpConn,
info: fmt.Sprintf("Rsync Service %s (Modules: %s)", version, strings.Join(modules, ",")),
modules: modules,
}
select {
case <-ctx.Done():
tcpConn.Close()
case resultChan <- connResult{rsyncConn, nil, rsyncConn.info}:
}
}()
// 等待连接结果
select {
case result := <-resultChan:
if result.err != nil {
return nil, result.err
}
return result.conn, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}
// getModuleList 获取Rsync模块列表
func (c *RsyncConnector) getModuleList(conn net.Conn, timeout time.Duration) ([]string, error) {
// 请求模块列表
conn.SetWriteDeadline(time.Now().Add(timeout))
_, err := conn.Write([]byte("#list\n"))
if err != nil {
return nil, err
}
buffer := make([]byte, 4096)
var moduleList strings.Builder
// 读取模块列表
for {
conn.SetReadDeadline(time.Now().Add(timeout))
n, err := conn.Read(buffer)
if err != nil {
break
}
chunk := string(buffer[:n])
moduleList.WriteString(chunk)
if strings.Contains(chunk, "@RSYNCD: EXIT") {
break
}
}
// 解析模块名
var modules []string
lines := strings.Split(moduleList.String(), "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "@RSYNCD") {
continue
}
// 提取模块名(第一个字段)
fields := strings.Fields(line)
if len(fields) > 0 {
modules = append(modules, fields[0])
}
}
return modules, nil
}
// Authenticate 进行Rsync认证
func (c *RsyncConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
rsyncConn, ok := conn.(*RsyncConnection)
if !ok {
return fmt.Errorf("无效的Rsync连接类型")
}
timeout := time.Duration(common.Timeout) * time.Second
// 结果通道
type authResult struct {
success bool
module string
err error
}
resultChan := make(chan authResult, 1)
// 在协程中尝试认证
go func() {
success, module, err := c.tryModuleAuthentication(ctx, rsyncConn, cred.Username, cred.Password, timeout)
select {
case <-ctx.Done():
case resultChan <- authResult{success, module, err}:
}
}()
// 等待认证结果
select {
case result := <-resultChan:
if result.err != nil {
return fmt.Errorf(i18n.GetText("rsync_auth_failed"), result.err)
}
if !result.success {
return fmt.Errorf(i18n.GetText("rsync_auth_failed"), "认证失败")
}
// 更新连接信息
rsyncConn.username = cred.Username
rsyncConn.password = cred.Password
return nil
case <-ctx.Done():
return ctx.Err()
}
}
// tryModuleAuthentication 尝试模块认证
func (c *RsyncConnector) tryModuleAuthentication(ctx context.Context, rsyncConn *RsyncConnection, username, password string, timeout time.Duration) (bool, string, error) {
// 为每个模块尝试认证
for _, moduleName := range rsyncConn.modules {
select {
case <-ctx.Done():
return false, "", ctx.Err()
default:
}
// 为每个模块创建新连接进行认证测试
success, err := c.testModuleAuth(ctx, moduleName, username, password, timeout)
if err != nil {
continue // 尝试下一个模块
}
if success {
return true, moduleName, nil
}
}
return false, "", fmt.Errorf("所有模块认证失败")
}
// testModuleAuth 测试单个模块的认证
func (c *RsyncConnector) testModuleAuth(ctx context.Context, moduleName, username, password string, timeout time.Duration) (bool, error) {
address := fmt.Sprintf("%s:%d", c.host, c.port)
// 建立新连接
authConn, err := net.DialTimeout("tcp", address, timeout)
if err != nil {
return false, err
}
defer authConn.Close()
buffer := make([]byte, 1024)
// 读取greeting
authConn.SetReadDeadline(time.Now().Add(timeout))
n, err := authConn.Read(buffer)
if err != nil {
return false, err
}
greeting := strings.TrimSpace(string(buffer[:n]))
version := strings.TrimSpace(strings.TrimPrefix(greeting, "@RSYNCD:"))
// 回应版本号
authConn.SetWriteDeadline(time.Now().Add(timeout))
_, err = authConn.Write([]byte(fmt.Sprintf("@RSYNCD: %s\n", version)))
if err != nil {
return false, err
}
// 选择模块
authConn.SetWriteDeadline(time.Now().Add(timeout))
_, err = authConn.Write([]byte(moduleName + "\n"))
if err != nil {
return false, err
}
// 等待认证挑战
authConn.SetReadDeadline(time.Now().Add(timeout))
n, err = authConn.Read(buffer)
if err != nil {
return false, err
}
authResponse := string(buffer[:n])
if strings.Contains(authResponse, "@RSYNCD: OK") {
// 模块不需要认证,匿名访问成功
return username == "" && password == "", nil
} else if strings.Contains(authResponse, "@RSYNCD: AUTHREQD") {
if username != "" && password != "" {
// 发送认证信息
authString := fmt.Sprintf("%s %s\n", username, password)
authConn.SetWriteDeadline(time.Now().Add(timeout))
_, err = authConn.Write([]byte(authString))
if err != nil {
return false, err
}
// 读取认证结果
authConn.SetReadDeadline(time.Now().Add(timeout))
n, err = authConn.Read(buffer)
if err != nil {
return false, err
}
// 检查认证结果
return !strings.Contains(string(buffer[:n]), "@ERROR"), nil
}
}
return false, nil
}
// Close 关闭Rsync连接
func (c *RsyncConnector) Close(conn interface{}) error {
if rsyncConn, ok := conn.(*RsyncConnection); ok {
if rsyncConn.conn != nil {
rsyncConn.conn.Close()
}
return nil
}
return fmt.Errorf("无效的Rsync连接类型")
}
// GetConnectionInfo 获取连接信息
func (conn *RsyncConnection) GetConnectionInfo() map[string]interface{} {
info := map[string]interface{}{
"protocol": "RSYNCD",
"service": "Rsync",
"info": conn.info,
"modules": conn.modules,
}
if conn.username != "" {
info["username"] = conn.username
info["authenticated"] = true
}
return info
}
// IsAlive 检查连接是否仍然有效
func (conn *RsyncConnection) IsAlive() bool {
if conn.conn == nil {
return false
}
// 简单的连接测试
conn.conn.SetReadDeadline(time.Now().Add(1 * time.Second))
_, err := conn.conn.Read(make([]byte, 1))
return err == nil
}
// GetServerInfo 获取服务器信息
func (conn *RsyncConnection) GetServerInfo() string {
return conn.info
}