diff --git a/common/i18n/locales/en.yaml b/common/i18n/locales/en.yaml index 3e0f7a9..1da43c5 100644 --- a/common/i18n/locales/en.yaml +++ b/common/i18n/locales/en.yaml @@ -213,6 +213,36 @@ parse_error_empty_input: other: "Input parameters are empty" parse_error_parser_not_init: other: "Parser not initialized" +parser_read_hosts_failed: + other: "Failed to read hosts file" +parser_parse_host_failed: + other: "Failed to parse host" +parser_parse_exclude_failed: + other: "Failed to parse exclude hosts" +parser_no_valid_hosts: + other: "No valid hosts found" +parser_cidr_failed: + other: "CIDR parse failed {{.Arg1}}" +parser_ip_range_failed: + other: "IP range parse failed {{.Arg1}}" +parser_invalid_ip_range_fmt: + other: "Invalid IP range format: {{.Arg1}}" +parser_invalid_start_ip: + other: "Invalid start IP: {{.Arg1}}" +parser_invalid_end_ip: + other: "Invalid end IP: {{.Arg1}}" +parser_invalid_ip_end_val: + other: "Invalid IP range end value: {{.Arg1}}" +parser_invalid_ip_range_val: + other: "Invalid IP range: {{.Arg1}}-{{.Arg2}}" +parser_invalid_ip_fmt: + other: "Invalid IP address format: {{.Arg1}}" +parser_ipv4_only: + other: "IPv4 addresses only" +parser_start_gt_end: + other: "Start IP greater than end IP" +network_rate_limited: + other: "Rate limited: {{.Arg1}}" target_local_mode: other: "Local scan mode" param_conflict_ao_icmp_both: @@ -817,3 +847,25 @@ web_shutting_down: other: "Web server shutting down..." web_mode_not_supported: other: "Web mode not supported in this build, rebuild with: go build -tags web" + +# ========================= Service Plugin Common Messages ========================= +service_no_credentials: + other: "No available test credentials" +service_not_identified: + other: "Cannot identify as {{.Arg1}} service" +service_port_restriction: + other: "{{.Arg1}} plugin only supports port {{.Arg2}}" +service_target_unreachable: + other: "Target unreachable: {{.Arg1}}" +service_unauthorized: + other: "Unauthorized access" +service_auth_failed: + other: "Authentication failed" +service_connection_failed: + other: "Connection failed: {{.Arg1}}" +service_no_weak_pass: + other: "No weak password found" +service_no_test_creds: + other: "No credentials to test" +service_conn_port_failed: + other: "Port connection failed: {{.Arg1}}" diff --git a/common/i18n/locales/zh.yaml b/common/i18n/locales/zh.yaml index f2b5c66..db530a4 100644 --- a/common/i18n/locales/zh.yaml +++ b/common/i18n/locales/zh.yaml @@ -213,6 +213,36 @@ parse_error_empty_input: other: "输入参数为空" parse_error_parser_not_init: other: "解析器未初始化" +parser_read_hosts_failed: + other: "读取主机文件失败" +parser_parse_host_failed: + other: "解析主机失败" +parser_parse_exclude_failed: + other: "解析排除主机失败" +parser_no_valid_hosts: + other: "没有找到有效的主机" +parser_cidr_failed: + other: "CIDR解析失败 {{.Arg1}}" +parser_ip_range_failed: + other: "IP范围解析失败 {{.Arg1}}" +parser_invalid_ip_range_fmt: + other: "无效的IP范围格式: {{.Arg1}}" +parser_invalid_start_ip: + other: "无效的起始IP地址: {{.Arg1}}" +parser_invalid_end_ip: + other: "无效的结束IP地址: {{.Arg1}}" +parser_invalid_ip_end_val: + other: "无效的IP范围结束值: {{.Arg1}}" +parser_invalid_ip_range_val: + other: "无效的IP范围: {{.Arg1}}-{{.Arg2}}" +parser_invalid_ip_fmt: + other: "无效的IP地址格式: {{.Arg1}}" +parser_ipv4_only: + other: "仅支持IPv4地址范围" +parser_start_gt_end: + other: "起始IP大于结束IP" +network_rate_limited: + other: "发包受限: {{.Arg1}}" target_local_mode: other: "本地扫描模式" param_conflict_ao_icmp_both: @@ -796,6 +826,8 @@ webscan_response_parse_failed: # Main 入口 param_error: other: "参数错误: {{.Arg1}}" +param_exclusive: + other: "参数 {{.Arg1}} 互斥,请只指定一个扫描目标\n -h: 网络主机扫描\n -u: Web URL扫描\n -local: 本地信息收集" error_generic: other: "错误: {{.Arg1}}" init_failed: @@ -814,3 +846,25 @@ web_shutting_down: other: "Web服务器正在关闭..." web_mode_not_supported: other: "当前版本不支持Web模式,请使用 -tags web 重新编译" + +# ========================= 服务插件通用消息 ========================= +service_no_credentials: + other: "没有可用的测试凭据" +service_not_identified: + other: "无法识别为{{.Arg1}}服务" +service_port_restriction: + other: "{{.Arg1}}插件仅支持{{.Arg2}}端口" +service_target_unreachable: + other: "目标不可达: {{.Arg1}}" +service_unauthorized: + other: "未授权访问" +service_auth_failed: + other: "认证失败" +service_connection_failed: + other: "连接失败: {{.Arg1}}" +service_no_weak_pass: + other: "未发现弱密码" +service_no_test_creds: + other: "无凭据可测试" +service_conn_port_failed: + other: "连接端口失败: {{.Arg1}}" diff --git a/common/initialize.go b/common/initialize.go index fbebf84..44b96a5 100644 --- a/common/initialize.go +++ b/common/initialize.go @@ -2,6 +2,8 @@ package common import ( "fmt" + + "github.com/shadow1ng/fscan/common/i18n" ) /* @@ -80,7 +82,7 @@ func ValidateExclusiveParams(info *HostInfo) error { } if paramCount > 1 { - return fmt.Errorf("参数 %s 互斥,请只指定一个扫描目标\n -h: 网络主机扫描\n -u: Web URL扫描\n -local: 本地信息收集", activeParam) + return fmt.Errorf(i18n.Tr("param_exclusive", activeParam)) } return nil diff --git a/common/network.go b/common/network.go index 1fa304f..0973828 100644 --- a/common/network.go +++ b/common/network.go @@ -17,6 +17,7 @@ import ( "time" "github.com/shadow1ng/fscan/common/proxy" + "github.com/shadow1ng/fscan/common/i18n" ) // ============================================================================= @@ -109,7 +110,7 @@ func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net. // 检查发包限制 - 在代理连接前进行控制 if canSend, reason := CanSendPacket(); !canSend { LogError(fmt.Sprintf("TCP连接 %s 受限: %s", address, reason)) - return nil, fmt.Errorf("发包受限: %s", reason) + return nil, fmt.Errorf(i18n.Tr("network_rate_limited", reason)) } // 获取全局拨号器(复用,避免重复创建) @@ -166,7 +167,7 @@ func SafeHTTPDo(client *http.Client, req *http.Request) (*http.Response, error) // 检查发包限制 if canSend, reason := CanSendPacket(); !canSend { LogError(fmt.Sprintf("HTTP请求 %s 受限: %s", req.URL.String(), reason)) - return nil, fmt.Errorf("发包受限: %s", reason) + return nil, fmt.Errorf(i18n.Tr("network_rate_limited", reason)) } // 执行HTTP请求 diff --git a/common/parsers/parsers.go b/common/parsers/parsers.go index d32d408..6a03f93 100644 --- a/common/parsers/parsers.go +++ b/common/parsers/parsers.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/shadow1ng/fscan/common/config" + "github.com/shadow1ng/fscan/common/i18n" ) /* @@ -37,7 +38,7 @@ func ParseIP(host string, filename string, nohosts ...string) ([]string, error) if filename != "" { fileHosts, err := ReadLinesFromFile(filename) if err != nil { - return nil, fmt.Errorf("读取主机文件失败: %w", err) + return nil, fmt.Errorf(i18n.GetText("parser_read_hosts_failed")+": %w", err) } for _, h := range fileHosts { parsed, err := parseHostString(h) @@ -52,7 +53,7 @@ func ParseIP(host string, filename string, nohosts ...string) ([]string, error) if host != "" { hostList, err := parseHostString(host) if err != nil { - return nil, fmt.Errorf("解析主机失败: %w", err) + return nil, fmt.Errorf(i18n.GetText("parser_parse_host_failed")+": %w", err) } hosts = append(hosts, hostList...) } @@ -61,7 +62,7 @@ func ParseIP(host string, filename string, nohosts ...string) ([]string, error) if len(nohosts) > 0 && nohosts[0] != "" { excludeList, err := parseHostString(nohosts[0]) if err != nil { - return nil, fmt.Errorf("解析排除主机失败: %w", err) + return nil, fmt.Errorf(i18n.GetText("parser_parse_exclude_failed")+": %w", err) } hosts = excludeFromList(hosts, excludeList) } @@ -71,7 +72,7 @@ func ParseIP(host string, filename string, nohosts ...string) ([]string, error) sort.Strings(hosts) if len(hosts) == 0 { - return nil, fmt.Errorf("没有找到有效的主机") + return nil, fmt.Errorf(i18n.GetText("parser_no_valid_hosts")) } return hosts, nil @@ -109,13 +110,13 @@ func parseHostString(host string) ([]string, error) { case strings.Contains(h, "/"): cidrHosts, err := parseIPCIDR(h, SimpleMaxHosts) if err != nil { - return nil, fmt.Errorf("CIDR解析失败 %s: %w", h, err) + return nil, fmt.Errorf(i18n.Tr("parser_cidr_failed", h)+": %w", err) } hosts = append(hosts, cidrHosts...) case strings.Contains(h, "-") && !strings.Contains(h, ":") && looksLikeIPRange(h): rangeHosts, err := parseIPRangeString(h, SimpleMaxHosts) if err != nil { - return nil, fmt.Errorf("IP范围解析失败 %s: %w", h, err) + return nil, fmt.Errorf(i18n.Tr("parser_ip_range_failed", h)+": %w", err) } hosts = append(hosts, rangeHosts...) default: @@ -333,7 +334,7 @@ func looksLikeIPRange(s string) bool { func parseIPRangeString(rangeStr string, maxTargets int) ([]string, error) { parts := strings.Split(rangeStr, "-") if len(parts) != 2 { - return nil, fmt.Errorf("无效的IP范围格式: %s", rangeStr) + return nil, fmt.Errorf(i18n.Tr("parser_invalid_ip_range_fmt", rangeStr)) } startIPStr := strings.TrimSpace(parts[0]) @@ -341,7 +342,7 @@ func parseIPRangeString(rangeStr string, maxTargets int) ([]string, error) { startIP := net.ParseIP(startIPStr) if startIP == nil { - return nil, fmt.Errorf("无效的起始IP地址: %s", startIPStr) + return nil, fmt.Errorf(i18n.Tr("parser_invalid_start_ip", startIPStr)) } // 处理简写格式 (如: 192.168.1.1-100) @@ -352,7 +353,7 @@ func parseIPRangeString(rangeStr string, maxTargets int) ([]string, error) { // 处理完整格式 (如: 192.168.1.1-192.168.1.100) endIP := net.ParseIP(endIPStr) if endIP == nil { - return nil, fmt.Errorf("无效的结束IP地址: %s", endIPStr) + return nil, fmt.Errorf(i18n.Tr("parser_invalid_end_ip", endIPStr)) } return parseIPFullRange(startIP, endIP, maxTargets) @@ -362,18 +363,18 @@ func parseIPRangeString(rangeStr string, maxTargets int) ([]string, error) { func parseIPShortRange(startIPStr, endSuffix string, maxTargets int) ([]string, error) { endNum, err := strconv.Atoi(endSuffix) if err != nil || endNum > 255 { - return nil, fmt.Errorf("无效的IP范围结束值: %s", endSuffix) + return nil, fmt.Errorf(i18n.Tr("parser_invalid_ip_end_val", endSuffix)) } ipParts := strings.Split(startIPStr, ".") if len(ipParts) != 4 { - return nil, fmt.Errorf("无效的IP地址格式: %s", startIPStr) + return nil, fmt.Errorf(i18n.Tr("parser_invalid_ip_fmt", startIPStr)) } prefixIP := strings.Join(ipParts[0:3], ".") startNum, err := strconv.Atoi(ipParts[3]) if err != nil || startNum > endNum { - return nil, fmt.Errorf("无效的IP范围: %s-%s", startIPStr, endSuffix) + return nil, fmt.Errorf(i18n.Tr("parser_invalid_ip_range_val", startIPStr, endSuffix)) } var allIP []string @@ -394,14 +395,14 @@ func parseIPFullRange(startIP, endIP net.IP, maxTargets int) ([]string, error) { start4 := startIP.To4() end4 := endIP.To4() if start4 == nil || end4 == nil { - return nil, fmt.Errorf("仅支持IPv4地址范围") + return nil, fmt.Errorf(i18n.GetText("parser_ipv4_only")) } startInt := (int(start4[0]) << 24) | (int(start4[1]) << 16) | (int(start4[2]) << 8) | int(start4[3]) endInt := (int(end4[0]) << 24) | (int(end4[1]) << 16) | (int(end4[2]) << 8) | int(end4[3]) if startInt > endInt { - return nil, fmt.Errorf("起始IP大于结束IP") + return nil, fmt.Errorf(i18n.GetText("parser_start_gt_end")) } var ips []string diff --git a/common/session.go b/common/session.go index 18e0328..5674f51 100644 --- a/common/session.go +++ b/common/session.go @@ -9,6 +9,7 @@ import ( "time" "github.com/shadow1ng/fscan/common/proxy" + "github.com/shadow1ng/fscan/common/i18n" ) // ScanSession 封装单次扫描的全部上下文 @@ -38,7 +39,7 @@ func (s *ScanSession) DialTCP(ctx context.Context, network, address string, time // 检查发包限制 if ok, err := CanSendPacketWith(s.Config, s.State); !ok { LogError(fmt.Sprintf("TCP连接 %s 受限: %s", address, err.Error())) - return nil, fmt.Errorf("发包受限: %s", err.Error()) + return nil, fmt.Errorf(i18n.Tr("network_rate_limited", err.Error())) } // 获取 dialer diff --git a/core/portfinger/probe_parser.go b/core/portfinger/probe_parser.go index 555b97c..584cd04 100644 --- a/core/portfinger/probe_parser.go +++ b/core/portfinger/probe_parser.go @@ -29,22 +29,20 @@ func (p *Probe) getDirectiveSyntax(data string) (directive Directive) { return directive } -// 解析探测器信息 -func (p *Probe) parseProbeInfo(probeStr string) { +// parseProbeInfo 解析探测器信息,返回错误替代 panic +func (p *Probe) parseProbeInfo(probeStr string) error { // 提取协议和其他信息 proto := probeStr[:4] other := probeStr[4:] // 验证协议类型 if proto != "TCP " && proto != "UDP " { - errMsg := "探测器协议必须是 TCP 或 UDP" - panic(errMsg) + return fmt.Errorf("探测器协议必须是 TCP 或 UDP") } // 验证其他信息不为空 if len(other) == 0 { - errMsg := "nmap-service-probes - 探测器名称无效" - panic(errMsg) + return fmt.Errorf("nmap-service-probes - 探测器名称无效") } // 解析指令 @@ -55,6 +53,7 @@ func (p *Probe) parseProbeInfo(probeStr string) { p.Data = strings.Split(directive.DirectiveStr, directive.Delimiter)[0] p.Protocol = strings.ToLower(strings.TrimSpace(proto)) + return nil } // 从字符串解析探测器信息 @@ -69,7 +68,9 @@ func (p *Probe) fromString(data string) error { } probeStr := lines[0] - p.parseProbeInfo(probeStr) + if err := p.parseProbeInfo(probeStr); err != nil { + return err + } // 解析匹配规则和其他配置 var matchs []Match @@ -154,8 +155,8 @@ func (p *Probe) parseFallback(data string) { p.Fallback = data[len("fallback")+1:] } -// 从内容解析探测器规则 -func (v *VScan) parseProbesFromContent(content string) { +// parseProbesFromContent 从内容解析探测器规则,返回错误替代 panic +func (v *VScan) parseProbesFromContent(content string) error { var probes []Probe var lines []string @@ -171,8 +172,7 @@ func (v *VScan) parseProbesFromContent(content string) { // 验证文件内容 if len(lines) == 0 { - errMsg := "读取nmap-service-probes文件失败: 内容为空" - panic(errMsg) + return fmt.Errorf("读取nmap-service-probes文件失败: 内容为空") } // 检查Exclude指令 @@ -182,16 +182,14 @@ func (v *VScan) parseProbesFromContent(content string) { excludeCount++ } if excludeCount > 1 { - errMsg := "nmap-service-probes文件中只允许有一个Exclude指令" - panic(errMsg) + return fmt.Errorf("nmap-service-probes文件中只允许有一个Exclude指令") } } // 验证第一行格式 firstLine := lines[0] if !strings.HasPrefix(firstLine, "Exclude ") && !strings.HasPrefix(firstLine, "Probe ") { - errMsg := "解析错误: 首行必须以\"Probe \"或\"Exclude \"开头" - panic(errMsg) + return fmt.Errorf("解析错误: 首行必须以\"Probe \"或\"Exclude \"开头") } // 处理Exclude指令 @@ -214,6 +212,7 @@ func (v *VScan) parseProbesFromContent(content string) { } v.AllProbes = probes + return nil } // 将探测器转换为名称映射 diff --git a/core/portfinger/scanner_core.go b/core/portfinger/scanner_core.go index a71596c..e768425 100644 --- a/core/portfinger/scanner_core.go +++ b/core/portfinger/scanner_core.go @@ -19,13 +19,16 @@ var ( vscanOnce sync.Once ) -// Init 初始化VScan对象 -func (vs *VScan) Init() { - vs.parseProbesFromContent(ProbeString) +// Init 初始化VScan对象,返回错误替代panic +func (vs *VScan) Init() error { + if err := vs.parseProbesFromContent(ProbeString); err != nil { + return err + } vs.parseProbesToMapKName() vs.SetusedProbes() - vs.compileFallbacks() // 编译 fallback 数组 - vs.preDecodeProbeData() // 预解码探针数据 + vs.compileFallbacks() + vs.preDecodeProbeData() + return nil } // preDecodeProbeData 预解码所有探针的 Data 字段,避免运行时重复解码 @@ -100,10 +103,14 @@ func (vs *VScan) compileFallbacks() { } // InitializeGlobalVScan 初始化全局VScan实例(线程安全,只执行一次) -func InitializeGlobalVScan() { +func InitializeGlobalVScan() error { + var initErr error vscanOnce.Do(func() { globalVScan = VScan{} - globalVScan.Init() + if err := globalVScan.Init(); err != nil { + initErr = err + return + } // 获取并检查 NULL 探测器 if nullProbe, ok := globalVScan.ProbesMapKName["NULL"]; ok { @@ -115,6 +122,7 @@ func InitializeGlobalVScan() { globalCommon = &genericProbe } }) + return initErr } // GetGlobalVScan 获取全局VScan实例 @@ -136,5 +144,5 @@ func GetCommonProbe() *Probe { } func init() { - InitializeGlobalVScan() + _ = InitializeGlobalVScan() } diff --git a/core/scanner.go b/core/scanner.go index 68ba27e..807dd24 100644 --- a/core/scanner.go +++ b/core/scanner.go @@ -88,7 +88,7 @@ func RunScan(ctx context.Context, info common.HostInfo, session *common.ScanSess // 初始化HTTP客户端(静默,无需日志) if err := lib.Inithttp(config); err != nil { common.LogError(i18n.Tr("http_client_init_failed", err)) - os.Exit(1) + return } // 选择策略 diff --git a/plugins/services/activemq.go b/plugins/services/activemq.go index d9b5cbf..8115a1d 100644 --- a/plugins/services/activemq.go +++ b/plugins/services/activemq.go @@ -218,7 +218,7 @@ func (p *ActiveMQPlugin) identifyService(ctx context.Context, info *common.HostI return &ScanResult{ Success: false, Service: "activemq", - Error: fmt.Errorf("无法发送STOMP请求: %w", writeErr), + Error: fmt.Errorf("STOMP request send failed: %w", writeErr), } } @@ -229,7 +229,7 @@ func (p *ActiveMQPlugin) identifyService(ctx context.Context, info *common.HostI return &ScanResult{ Success: false, Service: "activemq", - Error: fmt.Errorf("无法读取响应: %w", err), + Error: fmt.Errorf("Failed to read response: %w", err), } } if n == 0 { @@ -267,7 +267,7 @@ func (p *ActiveMQPlugin) identifyService(ctx context.Context, info *common.HostI return &ScanResult{ Success: false, Service: "activemq", - Error: fmt.Errorf("无法识别为ActiveMQ STOMP服务"), + Error: fmt.Errorf(i18n.Tr("service_not_identified", "ActiveMQ STOMP")), } } diff --git a/plugins/services/cassandra.go b/plugins/services/cassandra.go index 1ad9fe7..75aaff3 100644 --- a/plugins/services/cassandra.go +++ b/plugins/services/cassandra.go @@ -43,7 +43,7 @@ func (p *CassandraPlugin) Scan(ctx context.Context, info *common.HostInfo, sessi return &ScanResult{ Success: false, Service: "cassandra", - Error: fmt.Errorf("没有可用的测试凭据"), + Error: fmt.Errorf(i18n.GetText("service_no_credentials")), } } diff --git a/plugins/services/credential_tester.go b/plugins/services/credential_tester.go index da4f2ae..5a8e4d5 100644 --- a/plugins/services/credential_tester.go +++ b/plugins/services/credential_tester.go @@ -10,6 +10,7 @@ import ( "time" "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/plugins" ) @@ -147,7 +148,7 @@ func TestCredentialsConcurrently( return &ScanResult{ Success: false, Service: serviceName, - Error: fmt.Errorf("无凭据可测试"), + Error: fmt.Errorf(i18n.GetText("service_no_test_creds")), } } @@ -159,7 +160,7 @@ func TestCredentialsConcurrently( return &ScanResult{ Success: false, Service: serviceName, - Error: fmt.Errorf("目标不可达: %w", err), + Error: fmt.Errorf(i18n.Tr("service_target_unreachable", "%w"), err), } } _ = preConn.Close() @@ -222,7 +223,7 @@ func TestCredentialsConcurrently( Type: plugins.ResultTypeCredential, // 标记这是凭据测试结果 Success: false, Service: serviceName, - Error: fmt.Errorf("未发现弱密码"), + Error: fmt.Errorf(i18n.GetText("service_no_weak_pass")), } } @@ -255,14 +256,14 @@ func workerTestCredentials( } // 带重试的凭据测试 - result := testCredentialWithRetry(ctx, cred, authFn, serviceName, testConfig) + result, errType := testCredentialWithRetry(ctx, cred, authFn, serviceName, testConfig) if result != nil && result.Success { resultChan <- result return } // 跟踪连续网络错误 - if result != nil && result.Error != nil { + if errType == ErrorTypeNetwork { consecutiveNetErrors++ } else { consecutiveNetErrors = 0 @@ -277,12 +278,12 @@ func testCredentialWithRetry( authFn AuthFunc, serviceName string, testConfig ConcurrentTestConfig, -) *ScanResult { +) (*ScanResult, ErrorType) { for attempt := 0; attempt < testConfig.MaxRetries; attempt++ { // 检查是否应该停止 select { case <-ctx.Done(): - return nil + return nil, ErrorTypeUnknown default: } @@ -298,14 +299,14 @@ func testCredentialWithRetry( Service: serviceName, Username: cred.Username, Password: cred.Password, - } + }, ErrorTypeUnknown } // 根据错误类型决定是否重试 switch result.ErrorType { case ErrorTypeAuth: // 认证错误(密码错误),不重试 - return nil + return nil, result.ErrorType case ErrorTypeNetwork, ErrorTypeUnknown: // 网络错误或未知错误,可以重试(可能是服务端限流等临时问题) if attempt < testConfig.MaxRetries-1 { @@ -313,13 +314,13 @@ func testCredentialWithRetry( select { case <-ctx.Done(): timer.Stop() - return nil + return nil, result.ErrorType case <-timer.C: } } } } - return nil + return nil, ErrorTypeNetwork } // ============================================================================= diff --git a/plugins/services/elasticsearch.go b/plugins/services/elasticsearch.go index 9fd3464..d5c0b48 100644 --- a/plugins/services/elasticsearch.go +++ b/plugins/services/elasticsearch.go @@ -51,7 +51,7 @@ func (p *ElasticsearchPlugin) Scan(ctx context.Context, info *common.HostInfo, s return &ScanResult{ Success: false, Service: "elasticsearch", - Error: fmt.Errorf("没有可用的测试凭据"), + Error: fmt.Errorf(i18n.GetText("service_no_credentials")), } } @@ -71,7 +71,7 @@ func (p *ElasticsearchPlugin) Scan(ctx context.Context, info *common.HostInfo, s return &ScanResult{ Success: false, Service: "elasticsearch", - Error: fmt.Errorf("未发现弱密码"), + Error: fmt.Errorf(i18n.GetText("service_no_weak_pass")), } } @@ -137,7 +137,7 @@ func (p *ElasticsearchPlugin) identifyService(ctx context.Context, info *common. return &ScanResult{ Success: false, Service: "elasticsearch", - Error: fmt.Errorf("无法识别为Elasticsearch服务"), + Error: fmt.Errorf(i18n.Tr("service_not_identified", "Elasticsearch")), } } diff --git a/plugins/services/findnet.go b/plugins/services/findnet.go index 5bf61a1..57fd3f8 100644 --- a/plugins/services/findnet.go +++ b/plugins/services/findnet.go @@ -15,6 +15,7 @@ import ( "unicode" "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/plugins" ) @@ -45,7 +46,7 @@ func (p *FindNetPlugin) Scan(ctx context.Context, info *common.HostInfo, session return &ScanResult{ Success: false, Service: "findnet", - Error: fmt.Errorf("FindNet插件仅支持RPC端口135"), + Error: fmt.Errorf(i18n.Tr("service_port_restriction", "FindNet", "135")), } } @@ -54,7 +55,7 @@ func (p *FindNetPlugin) Scan(ctx context.Context, info *common.HostInfo, session return &ScanResult{ Success: false, Service: "findnet", - Error: fmt.Errorf("连接RPC端口失败: %w", err), + Error: fmt.Errorf(i18n.Tr("service_conn_port_failed", "%w"), err), } } defer func() { _ = conn.Close() }() diff --git a/plugins/services/ftp.go b/plugins/services/ftp.go index a3513d6..595cb74 100644 --- a/plugins/services/ftp.go +++ b/plugins/services/ftp.go @@ -43,7 +43,7 @@ func (p *FTPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co return &ScanResult{ Success: false, Service: "ftp", - Error: fmt.Errorf("没有可用的测试凭据"), + Error: fmt.Errorf(i18n.GetText("service_no_credentials")), } } diff --git a/plugins/services/kafka.go b/plugins/services/kafka.go index 71d0339..a959f49 100644 --- a/plugins/services/kafka.go +++ b/plugins/services/kafka.go @@ -38,7 +38,7 @@ func (p *KafkaPlugin) Scan(ctx context.Context, info *common.HostInfo, session * return &ScanResult{ Success: false, Service: "kafka", - Error: fmt.Errorf("没有可用的测试凭据"), + Error: fmt.Errorf(i18n.GetText("service_no_credentials")), } } @@ -195,7 +195,7 @@ func (p *KafkaPlugin) identifyService(ctx context.Context, info *common.HostInfo return &ScanResult{ Success: false, Service: "kafka", - Error: fmt.Errorf("无法识别为Kafka服务"), + Error: fmt.Errorf(i18n.Tr("service_not_identified", "Kafka")), } } state.IncrementTCPSuccessPacketCount() diff --git a/plugins/services/ldap.go b/plugins/services/ldap.go index 0f2b94e..af3975a 100644 --- a/plugins/services/ldap.go +++ b/plugins/services/ldap.go @@ -44,7 +44,7 @@ func (p *LDAPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *c return &ScanResult{ Success: false, Service: "ldap", - Error: fmt.Errorf("没有可用的测试凭据"), + Error: fmt.Errorf(i18n.GetText("service_no_credentials")), } } diff --git a/plugins/services/memcached.go b/plugins/services/memcached.go index be6800b..5aea51b 100644 --- a/plugins/services/memcached.go +++ b/plugins/services/memcached.go @@ -59,7 +59,7 @@ func (p *MemcachedPlugin) testUnauthorizedAccess(ctx context.Context, info *comm Type: plugins.ResultTypeVuln, Success: true, Service: "memcached", - Banner: "未授权访问", + Banner: i18n.GetText("service_unauthorized"), } } @@ -140,7 +140,7 @@ func (p *MemcachedPlugin) identifyService(ctx context.Context, info *common.Host return &ScanResult{ Success: false, Service: "memcached", - Error: fmt.Errorf("无法识别为Memcached服务"), + Error: fmt.Errorf(i18n.Tr("service_not_identified", "Memcached")), } } diff --git a/plugins/services/mongodb.go b/plugins/services/mongodb.go index 78d1cd5..3f0057c 100644 --- a/plugins/services/mongodb.go +++ b/plugins/services/mongodb.go @@ -63,7 +63,7 @@ func (p *MongoDBPlugin) Scan(ctx context.Context, info *common.HostInfo, session return &ScanResult{ Success: false, Service: "mongodb", - Error: fmt.Errorf("没有可用的测试凭据"), + Error: fmt.Errorf(i18n.GetText("service_no_credentials")), } } @@ -237,14 +237,14 @@ func (p *MongoDBPlugin) mongodbUnauth(ctx context.Context, info *common.HostInfo return false, nil } - return false, fmt.Errorf("无法识别为MongoDB服务") + return false, fmt.Errorf(i18n.Tr("service_not_identified", "MongoDB")) } // checkMongoAuth 检查MongoDB认证状态 func (p *MongoDBPlugin) checkMongoAuth(ctx context.Context, address string, packet []byte, session *common.ScanSession) (string, error) { conn, err := session.DialTCP(ctx, "tcp", address, session.Config.Timeout) if err != nil { - return "", fmt.Errorf("连接失败: %w", err) + return "", fmt.Errorf(i18n.Tr("service_connection_failed", "%w"), err) } defer func() { _ = conn.Close() }() diff --git a/plugins/services/mssql.go b/plugins/services/mssql.go index a921b79..37a365b 100644 --- a/plugins/services/mssql.go +++ b/plugins/services/mssql.go @@ -39,7 +39,7 @@ func (p *MSSQLPlugin) Scan(ctx context.Context, info *common.HostInfo, session * return &ScanResult{ Success: false, Service: "mssql", - Error: fmt.Errorf("没有可用的测试凭据"), + Error: fmt.Errorf(i18n.GetText("service_no_credentials")), } } @@ -188,7 +188,7 @@ func (p *MSSQLPlugin) identifyService(ctx context.Context, info *common.HostInfo return &ScanResult{ Success: false, Service: "mssql", - Error: fmt.Errorf("无法识别为MSSQL服务"), + Error: fmt.Errorf(i18n.Tr("service_not_identified", "MSSQL")), } } diff --git a/plugins/services/mysql.go b/plugins/services/mysql.go index 933fe5e..4dae063 100644 --- a/plugins/services/mysql.go +++ b/plugins/services/mysql.go @@ -48,7 +48,7 @@ func (p *MySQLPlugin) Scan(ctx context.Context, info *common.HostInfo, session * return &ScanResult{ Success: false, Service: "mysql", - Error: fmt.Errorf("没有可用的测试凭据"), + Error: fmt.Errorf(i18n.GetText("service_no_credentials")), } } @@ -165,7 +165,7 @@ func (p *MySQLPlugin) identifyService(ctx context.Context, info *common.HostInfo return &ScanResult{ Success: false, Service: "mysql", - Error: fmt.Errorf("无法识别为MySQL服务"), + Error: fmt.Errorf(i18n.Tr("service_not_identified", "MySQL")), } } diff --git a/plugins/services/neo4j.go b/plugins/services/neo4j.go index e67a6cc..2d11815 100644 --- a/plugins/services/neo4j.go +++ b/plugins/services/neo4j.go @@ -45,7 +45,7 @@ func (p *Neo4jPlugin) Scan(ctx context.Context, info *common.HostInfo, session * return &ScanResult{ Success: false, Service: "neo4j", - Error: fmt.Errorf("没有可用的测试凭据"), + Error: fmt.Errorf(i18n.GetText("service_no_credentials")), } } @@ -112,7 +112,7 @@ func (p *Neo4jPlugin) doNeo4jAuth(ctx context.Context, info *common.HostInfo, cr return &AuthResult{ Success: false, ErrorType: ErrorTypeAuth, - Error: fmt.Errorf("认证失败,状态码: %d", resp.StatusCode), + Error: fmt.Errorf(i18n.GetText("service_auth_failed")+": %d", resp.StatusCode), } } @@ -170,7 +170,7 @@ func (p *Neo4jPlugin) testUnauthorizedAccess(ctx context.Context, info *common.H Type: plugins.ResultTypeVuln, Success: true, Service: "neo4j", - Banner: "未授权访问", + Banner: i18n.GetText("service_unauthorized"), } } @@ -220,7 +220,7 @@ func (p *Neo4jPlugin) identifyService(ctx context.Context, info *common.HostInfo return &ScanResult{ Success: false, Service: "neo4j", - Error: fmt.Errorf("无法识别为Neo4j服务"), + Error: fmt.Errorf(i18n.Tr("service_not_identified", "Neo4j")), } } diff --git a/plugins/services/oracle.go b/plugins/services/oracle.go index 2a6223f..194338e 100644 --- a/plugins/services/oracle.go +++ b/plugins/services/oracle.go @@ -44,7 +44,7 @@ func (p *OraclePlugin) Scan(ctx context.Context, info *common.HostInfo, session return &ScanResult{ Success: false, Service: "oracle", - Error: fmt.Errorf("没有可用的测试凭据"), + Error: fmt.Errorf(i18n.GetText("service_no_credentials")), } } diff --git a/plugins/services/postgresql.go b/plugins/services/postgresql.go index 9d0c6f5..7f0b365 100644 --- a/plugins/services/postgresql.go +++ b/plugins/services/postgresql.go @@ -45,7 +45,7 @@ func (p *PostgreSQLPlugin) Scan(ctx context.Context, info *common.HostInfo, sess return &ScanResult{ Success: false, Service: "postgresql", - Error: fmt.Errorf("没有可用的测试凭据"), + Error: fmt.Errorf(i18n.GetText("service_no_credentials")), } } @@ -243,7 +243,7 @@ func (p *PostgreSQLPlugin) identifyService(ctx context.Context, info *common.Hos return &ScanResult{ Success: false, Service: "postgresql", - Error: fmt.Errorf("无法识别为PostgreSQL服务"), + Error: fmt.Errorf(i18n.Tr("service_not_identified", "PostgreSQL")), } } } else { diff --git a/plugins/services/rabbitmq.go b/plugins/services/rabbitmq.go index 419b917..6a98a98 100644 --- a/plugins/services/rabbitmq.go +++ b/plugins/services/rabbitmq.go @@ -46,7 +46,7 @@ func (p *RabbitMQPlugin) Scan(ctx context.Context, info *common.HostInfo, sessio return &ScanResult{ Success: false, Service: "rabbitmq", - Error: fmt.Errorf("没有可用的测试凭据"), + Error: fmt.Errorf(i18n.GetText("service_no_credentials")), } } @@ -121,7 +121,7 @@ func (p *RabbitMQPlugin) doRabbitMQAuth(ctx context.Context, info *common.HostIn return &AuthResult{ Success: false, ErrorType: ErrorTypeAuth, - Error: fmt.Errorf("认证失败,状态码: %d", resp.StatusCode), + Error: fmt.Errorf(i18n.GetText("service_auth_failed")+": %d", resp.StatusCode), } } @@ -184,7 +184,7 @@ func (p *RabbitMQPlugin) testUnauthorizedAccess(ctx context.Context, info *commo Type: plugins.ResultTypeVuln, Success: true, Service: "rabbitmq", - Banner: "未授权访问", + Banner: i18n.GetText("service_unauthorized"), } } } @@ -307,7 +307,7 @@ func (p *RabbitMQPlugin) testManagementInterface(ctx context.Context, info *comm return &ScanResult{ Success: false, Service: "rabbitmq", - Error: fmt.Errorf("无法识别为RabbitMQ服务"), + Error: fmt.Errorf(i18n.Tr("service_not_identified", "RabbitMQ")), } } diff --git a/plugins/services/rdp.go b/plugins/services/rdp.go index ee4145f..ba61f98 100644 --- a/plugins/services/rdp.go +++ b/plugins/services/rdp.go @@ -153,7 +153,7 @@ func (p *RDPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co return &ScanResult{ Success: false, Service: "rdp", - Error: fmt.Errorf("RDP认证失败"), + Error: fmt.Errorf(i18n.GetText("service_auth_failed")), } } diff --git a/plugins/services/redis.go b/plugins/services/redis.go index 4058edd..7ae5836 100644 --- a/plugins/services/redis.go +++ b/plugins/services/redis.go @@ -135,7 +135,7 @@ func (p *RedisPlugin) doRedisAuth(ctx context.Context, info *common.HostInfo, cr return &AuthResult{ Success: false, ErrorType: errType, - Error: fmt.Errorf("redis认证失败: %s", strings.TrimSpace(responseStr)), + Error: fmt.Errorf(i18n.GetText("service_auth_failed")+": %s", strings.TrimSpace(responseStr)), } } } diff --git a/plugins/services/rsync.go b/plugins/services/rsync.go index 9256af1..a51525b 100644 --- a/plugins/services/rsync.go +++ b/plugins/services/rsync.go @@ -58,7 +58,7 @@ func (p *RsyncPlugin) Scan(ctx context.Context, info *common.HostInfo, session * return &ScanResult{ Success: false, Service: "rsync", - Error: fmt.Errorf("没有可用的测试凭据"), + Error: fmt.Errorf(i18n.GetText("service_no_credentials")), } } @@ -374,7 +374,7 @@ func (p *RsyncPlugin) identifyService(ctx context.Context, info *common.HostInfo return &ScanResult{ Success: false, Service: "rsync", - Error: fmt.Errorf("无法识别为Rsync服务"), + Error: fmt.Errorf(i18n.Tr("service_not_identified", "Rsync")), } } diff --git a/plugins/services/smb_protocol.go b/plugins/services/smb_protocol.go index d4aac21..c6d3993 100644 --- a/plugins/services/smb_protocol.go +++ b/plugins/services/smb_protocol.go @@ -16,6 +16,7 @@ import ( "github.com/hirochachacha/go-smb2" "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" "github.com/stacktitan/smb/smb" ) @@ -206,7 +207,7 @@ func probeTarget(ctx context.Context, host string, port int, timeout time.Durati conn, err := session.DialTCP(ctx, "tcp", target, timeout) if err != nil { - return nil, fmt.Errorf("连接失败: %w", err) + return nil, fmt.Errorf(i18n.Tr("service_connection_failed", "%w"), err) } defer func() { _ = conn.Close() }() @@ -291,7 +292,7 @@ func probeSMBv1(conn net.Conn, target string, timeout time.Duration) (*SMBTarget func probeSMBv2(ctx context.Context, target string, timeout time.Duration, session *common.ScanSession) (*SMBTarget, error) { conn2, err := session.DialTCP(ctx, "tcp", target, timeout) if err != nil { - return nil, fmt.Errorf("SMBv2连接失败: %w", err) + return nil, fmt.Errorf(i18n.Tr("service_connection_failed", "%w"), err) } defer func() { _ = conn2.Close() }() @@ -436,7 +437,7 @@ func (a *SMB1Authenticator) Authenticate(ctx context.Context, host string, port resultChan <- &AuthResult{ Success: false, ErrorType: ErrorTypeAuth, - Error: fmt.Errorf("认证失败:用户名或密码错误"), + Error: fmt.Errorf(i18n.GetText("service_auth_failed")), } } }() @@ -507,7 +508,7 @@ func (a *SMB2Authenticator) Authenticate(ctx context.Context, host string, port return &AuthResult{ Success: false, ErrorType: classifySMBError(err), - Error: fmt.Errorf("SMB2认证失败: %w", err), + Error: fmt.Errorf(i18n.Tr("service_connection_failed", "%w"), err), }, nil } @@ -662,7 +663,7 @@ func classifySMBError(err error) ErrorType { "smb: wrong password", "smb: login failed", "smb: unauthorized", - "smb2认证失败", + i18n.GetText("service_auth_failed"), "ntlm authentication failed", "ntlm auth failed", // NT Status codes diff --git a/plugins/services/smtp.go b/plugins/services/smtp.go index 4d77b12..5bf538c 100644 --- a/plugins/services/smtp.go +++ b/plugins/services/smtp.go @@ -45,7 +45,7 @@ func (p *SMTPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *c return &ScanResult{ Success: false, Service: "smtp", - Error: fmt.Errorf("没有可用的测试凭据"), + Error: fmt.Errorf(i18n.GetText("service_no_credentials")), } } diff --git a/plugins/services/ssh.go b/plugins/services/ssh.go index d50b9a0..af166d4 100644 --- a/plugins/services/ssh.go +++ b/plugins/services/ssh.go @@ -248,7 +248,7 @@ func (p *SSHPlugin) identifyService(ctx context.Context, info *common.HostInfo, return &ScanResult{ Success: false, Service: "ssh", - Error: fmt.Errorf("无法识别为SSH服务"), + Error: fmt.Errorf(i18n.Tr("service_not_identified", "SSH")), } } diff --git a/plugins/services/telnet.go b/plugins/services/telnet.go index b131296..a13d5a3 100644 --- a/plugins/services/telnet.go +++ b/plugins/services/telnet.go @@ -75,7 +75,7 @@ func (p *TelnetPlugin) Scan(ctx context.Context, info *common.HostInfo, session return &ScanResult{ Success: false, Service: "telnet", - Error: fmt.Errorf("没有可用的测试凭据"), + Error: fmt.Errorf(i18n.GetText("service_no_credentials")), } } @@ -145,7 +145,7 @@ func (p *TelnetPlugin) doTelnetAuth(ctx context.Context, info *common.HostInfo, resultChan <- &AuthResult{ Success: false, ErrorType: ErrorTypeAuth, - Error: fmt.Errorf("认证失败"), + Error: fmt.Errorf(i18n.GetText("service_auth_failed")), } } }() diff --git a/plugins/services/vnc.go b/plugins/services/vnc.go index 2914632..eb60de3 100644 --- a/plugins/services/vnc.go +++ b/plugins/services/vnc.go @@ -182,7 +182,7 @@ func (p *VNCPlugin) testUnauthAccess(ctx context.Context, info *common.HostInfo, Type: plugins.ResultTypeVuln, Success: true, Service: "vnc", - Banner: "未授权访问", + Banner: i18n.GetText("service_unauthorized"), } }