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 }