package common import ( "encoding/hex" "fmt" "net" "net/url" "strconv" "strings" "github.com/shadow1ng/fscan/common/config" "github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/common/parsers" ) /* config_builder.go - 统一配置构建入口 从 FlagVars 直接构建 Config 和 State,消除中间层。 */ // BuildConfig 从 FlagVars 构建完整的 Config 和 State // 这是新的统一入口,替代原来的 Parse() + BuildConfigFromFlags() + updateGlobalVariables() func BuildConfig(fv *FlagVars, info *HostInfo) (*Config, *State, error) { // 1. 构建基础 Config(从 flag_config.go 的 BuildConfigFromFlags) cfg := BuildConfigFromFlags(fv) // 2. 创建 State state := NewState() // 3. 解析凭据 if err := parseCredentials(fv, cfg); err != nil { return nil, nil, fmt.Errorf("%s: %w", i18n.GetText("config_credentials_parse_failed"), err) } // 4. 解析目标(主机、端口、URL) if err := parseTargets(fv, info, cfg, state); err != nil { return nil, nil, fmt.Errorf("%s: %w", i18n.GetText("config_targets_parse_failed"), err) } // 5. 应用日志级别 applyLogLevelFromConfig(fv) return cfg, state, nil } // ============================================================================= // 凭据解析 // ============================================================================= func parseCredentials(fv *FlagVars, cfg *Config) error { // 解析用户名 usernames, err := parseUsernames(fv) if err != nil { return err } if len(usernames) > 0 { for serviceName := range cfg.Credentials.Userdict { cfg.Credentials.Userdict[serviceName] = usernames } } // 解析密码 passwords, err := parsePasswords(fv) if err != nil { return err } if len(passwords) > 0 { cfg.Credentials.Passwords = passwords } // 解析用户密码对 pairs, err := parseUserPassPairs(fv) if err != nil { return err } if len(pairs) > 0 { cfg.Credentials.UserPassPairs = pairs } // 解析哈希 hashValues, hashBytes, err := parseHashes(fv) if err != nil { return err } if len(hashValues) > 0 { cfg.Credentials.HashValues = hashValues cfg.Credentials.HashBytes = hashBytes } return nil } func parseUsernames(fv *FlagVars) ([]string, error) { var usernames []string // 命令行用户名 if fv.Username != "" { for _, u := range strings.Split(fv.Username, ",") { u = strings.TrimSpace(u) if u != "" { usernames = append(usernames, u) } } } // 从文件读取 if fv.UsersFile != "" { if lines, err := parsers.ReadLinesFromFile(fv.UsersFile); err == nil { usernames = append(usernames, lines...) } else { return nil, fmt.Errorf("%s", i18n.Tr("config_read_users_failed", fv.UsersFile, err)) } } // 额外用户名 if fv.AddUsers != "" { for _, u := range strings.Split(fv.AddUsers, ",") { u = strings.TrimSpace(u) if u != "" { usernames = append(usernames, u) } } } return removeDuplicate(usernames), nil } func parsePasswords(fv *FlagVars) ([]string, error) { var passwords []string // 命令行密码(支持逗号分隔多个值,保留空格作为密码的一部分) if fv.Password != "" { for _, p := range strings.Split(fv.Password, ",") { p = strings.TrimSpace(p) if p != "" { passwords = append(passwords, p) } } } // 从文件读取 if fv.PasswordsFile != "" { if lines, err := parsers.ReadLinesFromFile(fv.PasswordsFile); err == nil { passwords = append(passwords, lines...) } else { return nil, fmt.Errorf("%s", i18n.Tr("config_read_passwords_failed", fv.PasswordsFile, err)) } } // 额外密码 if fv.AddPasswords != "" { passwords = append(passwords, splitCredentialValues(fv.AddPasswords)...) } return removeDuplicate(passwords), nil } func splitCredentialValues(input string) []string { fields := strings.FieldsFunc(input, func(r rune) bool { return r == ',' || r == ' ' || r == '\t' || r == '\n' || r == '\r' }) values := make([]string, 0, len(fields)) for _, field := range fields { field = strings.TrimSpace(field) if field != "" { values = append(values, field) } } return values } func parseUserPassPairs(fv *FlagVars) ([]config.CredentialPair, error) { var pairs []config.CredentialPair // 如果命令行同时指定了单个用户名和单个密码(不是逗号分隔的多个) if fv.Username != "" && fv.Password != "" && !strings.Contains(fv.Username, ",") && !strings.Contains(fv.Password, ",") && fv.AddUsers == "" && fv.AddPasswords == "" && fv.UsersFile == "" && fv.PasswordsFile == "" && fv.UserPassFile == "" { pairs = append(pairs, config.CredentialPair{ Username: strings.TrimSpace(fv.Username), Password: fv.Password, }) return pairs, nil } // 从文件读取用户密码对 if fv.UserPassFile != "" { filePairs, err := parsers.ParseUserPassFile(fv.UserPassFile) if err != nil { return nil, err } pairs = append(pairs, filePairs...) } return pairs, nil } func parseHashes(fv *FlagVars) ([]string, [][]byte, error) { var hashValues []string var hashBytes [][]byte // 命令行哈希 if fv.HashValue != "" { hash := strings.TrimSpace(fv.HashValue) if len(hash) != 32 { return nil, nil, fmt.Errorf("invalid hash length: %s", hash) } hashByte, err := hex.DecodeString(hash) if err != nil { return nil, nil, err } hashValues = append(hashValues, hash) hashBytes = append(hashBytes, hashByte) } // 从文件读取 if fv.HashFile != "" { fileHashes, fileHashBytes, err := parsers.ParseHashFile(fv.HashFile) if err != nil { return nil, nil, err } hashValues = append(hashValues, fileHashes...) hashBytes = append(hashBytes, fileHashBytes...) } return hashValues, hashBytes, nil } // ============================================================================= // 目标解析 // ============================================================================= func parseTargets(fv *FlagVars, info *HostInfo, cfg *Config, state *State) error { // 检查是否为 host:port 格式 ports := fv.Ports if info.Host != "" && strings.Contains(info.Host, ":") { if _, portStr, err := net.SplitHostPort(info.Host); err == nil { if port, portErr := strconv.Atoi(portStr); portErr == nil && port >= 1 && port <= 65535 { // 有效的 host:port 格式 state.SetHostPorts([]string{info.Host}) info.Host = "" ports = "" // 清空端口,避免双重扫描 } } } // 解析 URL urls, err := parseURLs(fv) if err != nil { return err } if len(urls) > 0 { state.SetURLs(urls) if info.URL == "" && len(urls) == 1 { info.URL = urls[0] } } // 更新端口配置 if ports != "" { cfg.Target.Ports = ports } return nil } func parseURLs(fv *FlagVars) ([]string, error) { var urls []string // 命令行 URL if fv.TargetURL != "" { for _, u := range strings.Split(fv.TargetURL, ",") { u = strings.TrimSpace(u) if u != "" { urls = append(urls, normalizeURL(u)) } } } // 从文件读取 if fv.URLsFile != "" { if lines, err := parsers.ReadLinesFromFile(fv.URLsFile); err == nil { for _, line := range lines { urls = append(urls, normalizeURL(line)) } } else { return nil, fmt.Errorf("%s", i18n.Tr("config_read_urls_failed", fv.URLsFile, err)) } } return removeDuplicate(urls), nil } func normalizeURL(rawURL string) string { rawURL = strings.TrimSpace(rawURL) if rawURL == "" { return rawURL } lowerURL := strings.ToLower(rawURL) if !strings.HasPrefix(lowerURL, "http://") && !strings.HasPrefix(lowerURL, "https://") { return "http://" + normalizeSchemelessURLTarget(rawURL) } parsed, err := url.Parse(rawURL) if err != nil || parsed.Host == "" { return rawURL } normalizedHost := normalizeURLHost(parsed.Host) if normalizedHost == parsed.Host { return rawURL } parsed.Host = normalizedHost normalized := parsed.String() if schemeEnd := strings.Index(rawURL, "://"); schemeEnd >= 0 { return rawURL[:schemeEnd] + normalized[len(parsed.Scheme):] } return normalized } func normalizeSchemelessURLTarget(rawURL string) string { authority := rawURL suffix := "" if idx := strings.IndexAny(rawURL, "/?#"); idx >= 0 { authority = rawURL[:idx] suffix = rawURL[idx:] } return normalizeURLHost(authority) + suffix } func normalizeURLHost(host string) string { if strings.HasPrefix(host, "[") { return host } if ip := net.ParseIP(host); ip != nil && strings.Contains(host, ":") { return "[" + host + "]" } return host } // ============================================================================= // 日志级别应用 // ============================================================================= func applyLogLevelFromConfig(fv *FlagVars) { if fv.LogLevel == "" { return } // 调用已有的 applyLogLevel 函数 applyLogLevel() } // ============================================================================= // 辅助函数 // ============================================================================= func removeDuplicate(old []string) []string { if len(old) <= 1 { return old } temp := make(map[string]struct{}, len(old)) result := make([]string, 0, len(old)) for _, item := range old { if _, exists := temp[item]; !exists { temp[item] = struct{}{} result = append(result, item) } } return result } // ============================================================================= // 保留 BuildConfigFromFlags 的原有实现(从 flag_config.go 移入) // ============================================================================= // BuildConfigFromFlags 已在 flag_config.go 中定义,这里不重复