{"record":{"id":"80feab9855209a17","repo":"shadow1ng/fscan","slug":"s-80feab","errorCode":null,"errorMessage":"%s","messagePattern":"%s","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugins/services/ftp.go","lineNumber":47,"sourceCode":"\tconfig := session.Config\n\tstate := session.State\n\tif config.DisableBrute {\n\t\treturn p.identifyService(info, session)\n\t}\n\n\ttarget := info.Target()\n\n\t// 优先检测匿名访问\n\tif result := p.testAnonymousAccess(ctx, info, session); result != nil && result.Success {\n\t\treturn result\n\t}\n\n\tcredentials := GenerateCredentials(\"ftp\", config)\n\tif len(credentials) == 0 {\n\t\treturn &ScanResult{\n\t\t\tSuccess: false,\n\t\t\tService: \"ftp\",\n\t\t\tError:   fmt.Errorf(\"%s\", i18n.GetText(\"service_no_credentials\")),\n\t\t}\n\t}\n\n\t// 使用公共框架进行并发凭据测试\n\tauthFn := p.createAuthFunc(info, config, state)\n\ttestConfig := DefaultConcurrentTestConfigWithTarget(config, info)\n\n\tresult := TestCredentialsConcurrently(ctx, credentials, authFn, \"ftp\", testConfig)\n\n\tif result.Success {\n\t\t// 成功后重新连接获取文件列表\n\t\tfileList := p.getFileListAfterAuth(info, result.Username, result.Password, config, state)\n\t\tvar output strings.Builder\n\t\tfmt.Fprintf(&output, \"FTP %s %s:%s\", target, result.Username, result.Password)\n\t\tif len(fileList) > 0 {\n\t\t\tfor _, file := range fileList {\n\t\t\t\tfmt.Fprintf(&output, \"\\n   [->] %s\", file)\n\t\t\t}","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/shadow1ng/fscan/blob/95cc12e753bf43de7004e5aef42a9ffba3934303/plugins/services/ftp.go#L29-L65","documentation":"The FTP plugin's Scan returns a ScanResult whose Error field carries the i18n message for 'no credentials available' when GenerateCredentials(\"ftp\", config) yields an empty list. Scan aborts before any connection is attempted because there is nothing to test. This is a configuration/input problem, not a network failure.","triggerScenarios":"Scan (public) on the FTP plugin: credentials := GenerateCredentials(\"ftp\", config) returns len 0 — e.g. empty or missing user/pass lists in the config with no defaults enabled.","commonSituations":"Config supplied with no username or password wordlists; typo'd config keys so the credential generator finds nothing; user intentionally restricted to custom credentials but passed an empty list.","solutions":["Provide at least one username and password in the FTP scan config","Enable built-in default credential lists if you intended dictionary brute force","Validate the config before invoking Scan and fail fast with a clear message","Check GenerateCredentials(\"ftp\", config) unit behavior for your config shape"],"exampleFix":"// before\ncredentials := GenerateCredentials(\"ftp\", config) // may be empty\n// after\ncredentials := GenerateCredentials(\"ftp\", config)\nif len(credentials) == 0 {\n    return nil, fmt.Errorf(\"ftp scan requires at least one username/password pair in config\")\n}","handlingStrategy":"validation","validationCode":"cfg := loadFTPConfig(config)\nif len(cfg.Usernames) == 0 || len(cfg.Passwords) == 0 {\n    return fmt.Errorf(\"ftp config must contain at least one username and password\")\n}","typeGuard":"func hasCredentials(c map[string][]string) bool {\n    return len(c[\"usernames\"]) > 0 && len(c[\"passwords\"]) > 0\n}","tryCatchPattern":"res := plugin.Scan(info, config, state)\nif res != nil && res.Error != nil && strings.Contains(res.Error.Error(), i18n.GetText(\"service_no_credentials\")) {\n    log.Println(\"ftp: add usernames/passwords to the scan config and rerun\")\n    return\n}","preventionTips":["Validate credential lists are non-empty before invoking Scan","Enable built-in default lists when custom lists are omitted","Check config key spelling so GenerateCredentials finds the lists","Fail fast with a config lint step before starting scans"],"tags":["ftp","credentials","config","empty"],"backgroundTag":"missing-credentials","analyzedSha":"95cc12e753bf43de7004e5aef42a9ffba3934303","analyzedAt":"2026-09-06T17:07:30.094Z","contentChangedAt":"2026-09-06T17:07:30.094Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}