mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00
377 lines
8.6 KiB
Go
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
|
|
} |