package parsers import ( "encoding/hex" "fmt" "regexp" "strings" "sync" "time" "github.com/shadow1ng/fscan/common/i18n" ) // CredentialParser 凭据解析器 type CredentialParser struct { fileReader *FileReader mu sync.RWMutex //nolint:unused // reserved for future thread safety hashRegex *regexp.Regexp options *CredentialParserOptions } // CredentialParserOptions 凭据解析器选项 type CredentialParserOptions struct { MaxUsernameLength int `json:"max_username_length"` MaxPasswordLength int `json:"max_password_length"` AllowEmptyPasswords bool `json:"allow_empty_passwords"` ValidateHashes bool `json:"validate_hashes"` DeduplicateUsers bool `json:"deduplicate_users"` DeduplicatePasswords bool `json:"deduplicate_passwords"` EnableStatistics bool `json:"enable_statistics"` } // DefaultCredentialParserOptions 默认凭据解析器选项 func DefaultCredentialParserOptions() *CredentialParserOptions { return &CredentialParserOptions{ MaxUsernameLength: DefaultMaxUsernameLength, MaxPasswordLength: DefaultMaxPasswordLength, AllowEmptyPasswords: DefaultAllowEmptyPasswords, ValidateHashes: DefaultValidateHashes, DeduplicateUsers: DefaultDeduplicateUsers, DeduplicatePasswords: DefaultDeduplicatePasswords, EnableStatistics: DefaultCredentialsEnableStatistics, } } // NewCredentialParser 创建凭据解析器 func NewCredentialParser(fileReader *FileReader, options *CredentialParserOptions) *CredentialParser { if options == nil { options = DefaultCredentialParserOptions() } // 编译哈希验证正则表达式 (MD5: 32位十六进制) hashRegex := CompiledHashRegex return &CredentialParser{ fileReader: fileReader, hashRegex: hashRegex, options: options, } } // CredentialInput 凭据输入参数 type CredentialInput struct { // 直接输入 Username string `json:"username"` Password string `json:"password"` AddUsers string `json:"add_users"` AddPasswords string `json:"add_passwords"` HashValue string `json:"hash_value"` SshKeyPath string `json:"ssh_key_path"` Domain string `json:"domain"` // 文件输入 UsersFile string `json:"users_file"` PasswordsFile string `json:"passwords_file"` HashFile string `json:"hash_file"` } // Parse 解析凭据配置 func (cp *CredentialParser) Parse(input *CredentialInput, options *ParserOptions) (*ParseResult, error) { if input == nil { return nil, NewParseError(ErrorTypeInputError, "凭据输入为空", "", 0, ErrEmptyInput) } startTime := time.Now() result := &ParseResult{ Config: &ParsedConfig{ Credentials: &CredentialConfig{ SshKeyPath: input.SshKeyPath, Domain: input.Domain, }, }, Success: true, } var errors []error var warnings []string // 解析用户名 usernames, userErrors, userWarnings := cp.parseUsernames(input) errors = append(errors, userErrors...) warnings = append(warnings, userWarnings...) // 解析密码 passwords, passErrors, passWarnings := cp.parsePasswords(input) errors = append(errors, passErrors...) warnings = append(warnings, passWarnings...) // 解析哈希值 hashValues, hashBytes, hashErrors, hashWarnings := cp.parseHashes(input) errors = append(errors, hashErrors...) warnings = append(warnings, hashWarnings...) // 更新配置 result.Config.Credentials.Usernames = usernames result.Config.Credentials.Passwords = passwords result.Config.Credentials.HashValues = hashValues result.Config.Credentials.HashBytes = hashBytes // 生成统计信息 if cp.options.EnableStatistics { result.Config.Credentials.Statistics = cp.generateStatistics(usernames, passwords, hashValues, hashBytes) } // 设置结果状态 result.Errors = errors result.Warnings = warnings result.ParseTime = time.Since(startTime) result.Success = len(errors) == 0 return result, nil } // parseUsernames 解析用户名 func (cp *CredentialParser) parseUsernames(input *CredentialInput) ([]string, []error, []string) { var usernames []string var errors []error var warnings []string // 解析命令行用户名 if input.Username != "" { users := strings.Split(input.Username, ",") for _, user := range users { if processedUser, valid, err := cp.validateUsername(strings.TrimSpace(user)); valid { usernames = append(usernames, processedUser) } else if err != nil { errors = append(errors, NewParseError(ErrorTypeUsernameError, err.Error(), "command line", 0, err)) } } } // 从文件读取用户名 if input.UsersFile != "" { fileResult, err := cp.fileReader.ReadFile(input.UsersFile) if err != nil { errors = append(errors, NewParseError(ErrorTypeFileError, "读取用户名文件失败", input.UsersFile, 0, err)) } else { for i, line := range fileResult.Lines { if processedUser, valid, err := cp.validateUsername(line); valid { usernames = append(usernames, processedUser) } else if err != nil { warnings = append(warnings, fmt.Sprintf("用户名文件第%d行无效: %s", i+1, err.Error())) } } } } // 处理额外用户名 if input.AddUsers != "" { extraUsers := strings.Split(input.AddUsers, ",") for _, user := range extraUsers { if processedUser, valid, err := cp.validateUsername(strings.TrimSpace(user)); valid { usernames = append(usernames, processedUser) } else if err != nil { warnings = append(warnings, fmt.Sprintf("额外用户名无效: %s", err.Error())) } } } // 去重 if cp.options.DeduplicateUsers { usernames = cp.removeDuplicateStrings(usernames) } return usernames, errors, warnings } // parsePasswords 解析密码 func (cp *CredentialParser) parsePasswords(input *CredentialInput) ([]string, []error, []string) { var passwords []string var errors []error var warnings []string // 解析命令行密码 if input.Password != "" { passes := strings.Split(input.Password, ",") for _, pass := range passes { if processedPass, valid, err := cp.validatePassword(pass); valid { passwords = append(passwords, processedPass) } else if err != nil { errors = append(errors, NewParseError(ErrorTypePasswordError, err.Error(), "command line", 0, err)) } } } // 从文件读取密码 if input.PasswordsFile != "" { fileResult, err := cp.fileReader.ReadFile(input.PasswordsFile) if err != nil { errors = append(errors, NewParseError(ErrorTypeFileError, "读取密码文件失败", input.PasswordsFile, 0, err)) } else { for i, line := range fileResult.Lines { if processedPass, valid, err := cp.validatePassword(line); valid { passwords = append(passwords, processedPass) } else if err != nil { warnings = append(warnings, fmt.Sprintf("密码文件第%d行无效: %s", i+1, err.Error())) } } } } // 处理额外密码 if input.AddPasswords != "" { extraPasses := strings.Split(input.AddPasswords, ",") for _, pass := range extraPasses { if processedPass, valid, err := cp.validatePassword(pass); valid { passwords = append(passwords, processedPass) } else if err != nil { warnings = append(warnings, fmt.Sprintf("额外密码无效: %s", err.Error())) } } } // 去重 if cp.options.DeduplicatePasswords { passwords = cp.removeDuplicateStrings(passwords) } return passwords, errors, warnings } // parseHashes 解析哈希值 func (cp *CredentialParser) parseHashes(input *CredentialInput) ([]string, [][]byte, []error, []string) { var hashValues []string var hashBytes [][]byte var errors []error var warnings []string // 解析单个哈希值 if input.HashValue != "" { if valid, err := cp.validateHash(input.HashValue); valid { hashValues = append(hashValues, input.HashValue) } else { errors = append(errors, NewParseError(ErrorTypeHashError, err.Error(), "command line", 0, err)) } } // 从文件读取哈希值 if input.HashFile != "" { fileResult, err := cp.fileReader.ReadFile(input.HashFile) if err != nil { errors = append(errors, NewParseError(ErrorTypeFileError, "读取哈希文件失败", input.HashFile, 0, err)) } else { for i, line := range fileResult.Lines { if valid, err := cp.validateHash(line); valid { hashValues = append(hashValues, line) } else { warnings = append(warnings, fmt.Sprintf("哈希文件第%d行无效: %s", i+1, err.Error())) } } } } // 转换哈希值为字节数组 for _, hash := range hashValues { if hashByte, err := hex.DecodeString(hash); err == nil { hashBytes = append(hashBytes, hashByte) } else { warnings = append(warnings, fmt.Sprintf("哈希值解码失败: %s", hash)) } } return hashValues, hashBytes, errors, warnings } // validateUsername 验证用户名 func (cp *CredentialParser) validateUsername(username string) (string, bool, error) { if len(username) == 0 { return "", false, nil // 允许空用户名,但不添加到列表 } if len(username) > cp.options.MaxUsernameLength { return "", false, fmt.Errorf(i18n.GetText("parser_username_too_long"), len(username), cp.options.MaxUsernameLength) } // 检查特殊字符 if strings.ContainsAny(username, InvalidUsernameChars) { return "", false, fmt.Errorf("%s", i18n.GetText("parser_username_invalid_chars")) } return username, true, nil } // validatePassword 验证密码 func (cp *CredentialParser) validatePassword(password string) (string, bool, error) { if len(password) == 0 && !cp.options.AllowEmptyPasswords { return "", false, fmt.Errorf("%s", i18n.GetText("parser_password_empty")) } if len(password) > cp.options.MaxPasswordLength { return "", false, fmt.Errorf(i18n.GetText("parser_password_too_long"), len(password), cp.options.MaxPasswordLength) } return password, true, nil } // validateHash 验证哈希值 func (cp *CredentialParser) validateHash(hash string) (bool, error) { if !cp.options.ValidateHashes { return true, nil } hash = strings.TrimSpace(hash) if len(hash) == 0 { return false, fmt.Errorf("%s", i18n.GetText("parser_hash_empty")) } if !cp.hashRegex.MatchString(hash) { return false, fmt.Errorf("%s", i18n.GetText("parser_hash_invalid_format")) } return true, nil } // removeDuplicateStrings 去重字符串切片 func (cp *CredentialParser) removeDuplicateStrings(slice []string) []string { seen := make(map[string]struct{}) var result []string for _, item := range slice { if _, exists := seen[item]; !exists { seen[item] = struct{}{} result = append(result, item) } } return result } // generateStatistics 生成统计信息 func (cp *CredentialParser) generateStatistics(usernames, passwords, hashValues []string, hashBytes [][]byte) *CredentialStats { return &CredentialStats{ TotalUsernames: len(usernames), TotalPasswords: len(passwords), TotalHashes: len(hashValues), UniqueUsernames: len(cp.removeDuplicateStrings(usernames)), UniquePasswords: len(cp.removeDuplicateStrings(passwords)), ValidHashes: len(hashBytes), InvalidHashes: len(hashValues) - len(hashBytes), } } // ============================================================================================= // 已删除的死代码(未使用):Validate 和 GetStatistics 方法 // =============================================================================================