From 4db3ca8214b17c78d711da81f4b6fce0a226d7dd Mon Sep 17 00:00:00 2001 From: Jozef Reisinger Date: Thu, 11 Nov 2021 21:34:51 +0100 Subject: [PATCH] refactor --- Makefile | 8 +-- check/check.go | 56 +++++++++++++----- check/data.go | 34 ----------- check/error.go | 13 +++-- check/http.go | 2 +- check/result.go | 76 ------------------------- checker/abuseipdb.go | 14 ++--- checker/as.go | 10 ++-- checker/blocklist.go | 15 +++-- checker/checkers.go | 2 +- checker/cins.go | 11 ++-- checker/dns.go | 9 ++- checker/geo.go | 18 +++--- checker/ipsum.go | 10 ++-- checker/otx.go | 9 ++- checker/shodan.go | 13 ++--- checker/threatcrowd.go | 8 +-- checker/virustotal.go | 12 ++-- cmd/checkip.go | 38 ------------- cmd/cmd.go | 125 +++++++++++++++++++++++++++++++++++++++++ 20 files changed, 243 insertions(+), 240 deletions(-) delete mode 100644 check/data.go delete mode 100644 check/result.go delete mode 100755 cmd/checkip.go create mode 100755 cmd/cmd.go diff --git a/Makefile b/Makefile index 9d4e09d..8420615 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ -test: - go test -cover ./... - -install: test +install: go install run: install checkip 91.228.166.47 checkip 209.141.33.65 checkip 218.92.0.158 + checkip -j 91.228.166.47 | jq -r '.[] | select(.Type=="Sec" or .Type=="InfoSec") | "\(.IPaddrMalicious)\t\(.Name)"' | sort + checkip -j 209.141.33.65 | jq -r '.[] | select(.Type=="Sec" or .Type=="InfoSec") | "\(.IPaddrMalicious)\t\(.Name)"' | sort + checkip -j 218.92.0.158 | jq -r '.[] | select(.Type=="Sec" or .Type=="InfoSec") | "\(.IPaddrMalicious)\t\(.Name)"' | sort diff --git a/check/check.go b/check/check.go index 7057953..1aa1f11 100644 --- a/check/check.go +++ b/check/check.go @@ -4,31 +4,57 @@ package check import ( "net" - "sync" ) +type Type string + const ( TypeInfo Type = "Info" // provides some useful information about the IP address TypeSec Type = "Sec" // says whether the IP address is considered malicious TypeInfoSec Type = "InfoSec" ) -type Type string +type Check func(ipaddr net.IP) (Result, error) + +type Result struct { + Name string + Type Type + IPaddrMalicious bool + Info Info + // Error *ResultError + // Error error + // ErrorRedacted string +} + +type Info interface { + String() string + JsonString() (string, error) +} -type Check func(ipaddr net.IP) Result +type EmptyInfo struct { +} + +func (EmptyInfo) String() string { + return Na("") +} -// Run runs checkers concurrently checking the ipaddr. -func Run(checks []Check, ipaddr net.IP) Results { - var res []Result +func (EmptyInfo) JsonString() (string, error) { + return "{}", nil +} + +func Na(s string) string { + if s == "" { + return "n/a" + } + return s +} - var wg sync.WaitGroup - for _, chk := range checks { - wg.Add(1) - go func(c Check) { - defer wg.Done() - res = append(res, c(ipaddr)) - }(chk) +func NonEmpty(strings ...string) []string { + var ss []string + for _, s := range strings { + if s != "" { + ss = append(ss, s) + } } - wg.Wait() - return res + return ss } diff --git a/check/data.go b/check/data.go deleted file mode 100644 index 1de7fb2..0000000 --- a/check/data.go +++ /dev/null @@ -1,34 +0,0 @@ -package check - -type Data interface { - String() string - JsonString() (string, error) -} - -type EmptyData struct { -} - -func (EmptyData) String() string { - return Na("") -} - -func (EmptyData) JsonString() (string, error) { - return "{}", nil -} - -func Na(s string) string { - if s == "" { - return "n/a" - } - return s -} - -func NonEmpty(strings ...string) []string { - var ss []string - for _, s := range strings { - if s != "" { - ss = append(ss, s) - } - } - return ss -} diff --git a/check/error.go b/check/error.go index cabb79b..376fef3 100644 --- a/check/error.go +++ b/check/error.go @@ -2,16 +2,17 @@ package check import "regexp" -type ResultError struct { - err error +type Error struct { + err error + ErrString string `json:"error"` } -func NewResultError(err error) *ResultError { - return &ResultError{err: err} +func NewError(err error) *Error { + return &Error{err: err, ErrString: redactSecrets(err.Error())} } -func (e *ResultError) Error() string { - return redactSecrets(e.err.Error()) +func (e *Error) Error() string { + return e.ErrString } func redactSecrets(s string) string { diff --git a/check/http.go b/check/http.go index 6da51c5..aaaafb3 100644 --- a/check/http.go +++ b/check/http.go @@ -64,7 +64,7 @@ func (c HttpClient) GetJson(apiUrl string, headers map[string]string, queryParam } if response != nil { if err := json.Unmarshal(b, response); err != nil { - return err + return fmt.Errorf("unmarshalling JSON from %s: %v", apiUrl, err) } } return nil diff --git a/check/result.go b/check/result.go deleted file mode 100644 index 126241d..0000000 --- a/check/result.go +++ /dev/null @@ -1,76 +0,0 @@ -package check - -import ( - "encoding/json" - "fmt" - "log" - "os" - "sort" - - "github.com/logrusorgru/aurora" -) - -type Result struct { - Name string - Type Type - IPaddrMalicious bool - Data Data - Error *ResultError -} - -type Results []Result - -// PrintJSON prints all results in JSON. -func (rs Results) PrintJSON() { - enc := json.NewEncoder(os.Stdout) - if err := enc.Encode(rs); err != nil { - log.Fatal(err) - } -} - -// SortByName sorts results by the checker name. -func (rs Results) SortByName() { - sort.Slice(rs, func(i, j int) bool { - return rs[i].Name < rs[j].Name - }) -} - -// PrintInfo prints results from Info and InfoSec checkers. -func (rs Results) PrintInfo() { - for _, r := range rs { - if r.Error != nil { - log.Print(r.Error.Error()) - } - if r.Type == "Info" || r.Type == "InfoSec" { - fmt.Printf("%-15s %s\n", r.Name, r.Data.String()) - } - } -} - -// PrintProbabilityMalicious prints the probability the IP address is malicious. -func (rs Results) PrintProbabilityMalicious() { - var msg string - switch { - case rs.probabilityMalicious() <= 0.15: - msg = fmt.Sprint(aurora.Green("Malicious")) - case rs.probabilityMalicious() <= 0.50: - msg = fmt.Sprint(aurora.Yellow("Malicious")) - default: - msg = fmt.Sprint(aurora.Red("Malicious")) - } - - fmt.Printf("%s\t%.0f%%\n", msg, rs.probabilityMalicious()*100) -} - -func (rs Results) probabilityMalicious() float64 { - var malicious, totalSec float64 - for _, r := range rs { - if r.Type == "Sec" || r.Type == "InfoSec" { - totalSec++ - if r.IPaddrMalicious { - malicious++ - } - } - } - return malicious / totalSec -} diff --git a/checker/abuseipdb.go b/checker/abuseipdb.go index 764ed4b..c949732 100644 --- a/checker/abuseipdb.go +++ b/checker/abuseipdb.go @@ -12,6 +12,7 @@ import ( // Only return reports within the last x amount of days. Default is 30. const abuseIPDBMaxAgeInDays = "90" +// AbuseIPDB holds information from abuseipdb.com. type AbuseIPDB struct { IsWhitelisted bool `json:"isWhitelisted"` AbuseConfidenceScore int `json:"abuseConfidenceScore"` @@ -35,12 +36,10 @@ func (d AbuseIPDB) JsonString() (string, error) { return string(b), err } -// CheckAbuseIPDB fills in AbuseIPDB data for a given IP address. It gets the data from -// api.abuseipdb.com/api/v2/check (docs.abuseipdb.com/#check-endpoint). -func CheckAbuseIPDB(ipaddr net.IP) check.Result { +func CheckAbuseIPDB(ipaddr net.IP) (check.Result, error) { apiKey, err := check.GetConfigValue("ABUSEIPDB_API_KEY") if err != nil { - return check.Result{Error: check.NewResultError(err)} + return check.Result{}, check.NewError(err) } headers := map[string]string{ @@ -57,14 +56,15 @@ func CheckAbuseIPDB(ipaddr net.IP) check.Result { var data struct { AbuseIPDB AbuseIPDB `json:"data"` } + // docs.abuseipdb.com/#check-endpoint if err := check.DefaultHttpClient.GetJson("https://api.abuseipdb.com/api/v2/check", headers, queryParams, &data); err != nil { - return check.Result{Error: check.NewResultError(err)} + return check.Result{}, check.NewError(err) } return check.Result{ Name: "abuseipdb.com", Type: check.TypeInfoSec, - Data: data.AbuseIPDB, + Info: data.AbuseIPDB, IPaddrMalicious: data.AbuseIPDB.TotalReports > 0 && !data.AbuseIPDB.IsWhitelisted && data.AbuseIPDB.AbuseConfidenceScore > 25, - } + }, nil } diff --git a/checker/as.go b/checker/as.go index a0bd896..e872288 100644 --- a/checker/as.go +++ b/checker/as.go @@ -34,24 +34,24 @@ func (a AS) JsonString() (string, error) { // CheckAs fills in AS data for a given IP address. The data is taken from a TSV // file ip2asn-combined downloaded from iptoasn.com. The file is created or // updated as needed. -func CheckAs(ipaddr net.IP) check.Result { +func CheckAs(ipaddr net.IP) (check.Result, error) { file := "/var/tmp/ip2asn-combined.tsv" url := "https://iptoasn.com/data/ip2asn-combined.tsv.gz" if err := check.UpdateFile(file, url, "gz"); err != nil { - return check.Result{Error: check.NewResultError(err)} + return check.Result{}, check.NewError(err) } as, err := asSearch(ipaddr, file) if err != nil { - return check.Result{Error: check.NewResultError(fmt.Errorf("searching %s in %s: %v", ipaddr, file, err))} + return check.Result{}, check.NewError(fmt.Errorf("searching %s in %s: %v", ipaddr, file, err)) } return check.Result{ Name: "iptoasn.com", Type: check.TypeInfo, - Data: as, - } + Info: as, + }, nil } // search the ippadrr in tsvFile and if found fills in AS data. diff --git a/checker/blocklist.go b/checker/blocklist.go index ac8aa3e..aa01acc 100644 --- a/checker/blocklist.go +++ b/checker/blocklist.go @@ -9,14 +9,13 @@ import ( "github.com/jreisinger/checkip/check" ) -// CheckBlockList fills in BlockList data for a given IP address. It gets the data from -// http://api.blocklist.de -func CheckBlockList(ipddr net.IP) check.Result { +// CheckBlockList searches the ipaddr in http://api.blocklist.de. +func CheckBlockList(ipddr net.IP) (check.Result, error) { url := fmt.Sprintf("http://api.blocklist.de/api.php?ip=%s&start=1", ipddr) resp, err := check.DefaultHttpClient.Get(url, map[string]string{}, map[string]string{}) if err != nil { - return check.Result{Error: check.NewResultError(err)} + return check.Result{}, check.NewError(err) } number := regexp.MustCompile(`\d+`) @@ -24,17 +23,17 @@ func CheckBlockList(ipddr net.IP) check.Result { attacks, err := strconv.Atoi(string(numbers[0])) if err != nil { - return check.Result{Error: check.NewResultError(err)} + return check.Result{}, check.NewError(err) } reports, err := strconv.Atoi(string(numbers[1])) if err != nil { - return check.Result{Error: check.NewResultError(err)} + return check.Result{}, check.NewError(err) } return check.Result{ Name: "blocklist.de", Type: check.TypeSec, - Data: check.EmptyData{}, + Info: check.EmptyInfo{}, IPaddrMalicious: attacks > 0 && reports > 0, - } + }, nil } diff --git a/checker/checkers.go b/checker/checkers.go index 18615a8..3b16573 100644 --- a/checker/checkers.go +++ b/checker/checkers.go @@ -3,8 +3,8 @@ package checker import "github.com/jreisinger/checkip/check" var DefaultCheckers = []check.Check{ - CheckAs, CheckAbuseIPDB, + CheckAs, CheckBlockList, CheckCins, CheckDNS, diff --git a/checker/cins.go b/checker/cins.go index 7c02e94..149a226 100644 --- a/checker/cins.go +++ b/checker/cins.go @@ -17,26 +17,25 @@ type CINSArmy struct { CountIPs int } -// CheckCins fills in the CINSArmy data. -func CheckCins(ipaddr net.IP) check.Result { +func CheckCins(ipaddr net.IP) (check.Result, error) { file := "/var/tmp/cins.txt" url := "http://cinsscore.com/list/ci-badguys.txt" if err := check.UpdateFile(file, url, ""); err != nil { - return check.Result{Error: check.NewResultError(err)} + return check.Result{}, check.NewError(err) } cins, err := cinsSearch(ipaddr, file) if err != nil { - return check.Result{Error: check.NewResultError(fmt.Errorf("searching %s in %s: %v", ipaddr, file, err))} + return check.Result{}, check.NewError(fmt.Errorf("searching %s in %s: %v", ipaddr, file, err)) } return check.Result{ Name: "cinsscore.com", Type: check.TypeSec, - Data: check.EmptyData{}, + Info: check.EmptyInfo{}, IPaddrMalicious: cins.BadGuyIP, - } + }, nil } // search searches the ippadrr in filename fills in ET data. diff --git a/checker/dns.go b/checker/dns.go index 1d80ff0..6d9ed55 100644 --- a/checker/dns.go +++ b/checker/dns.go @@ -28,14 +28,17 @@ func (d DNS) JsonString() (string, error) { } // CheckDNS does a reverse lookup for a given IP address. -func CheckDNS(ipaddr net.IP) check.Result { +func CheckDNS(ipaddr net.IP) (check.Result, error) { // NOTE: We are ignoring error. It says: "nodename nor servname // provided, or not known" if there is no DNS name for the IP address. names, _ := net.LookupAddr(ipaddr.String()) + // if err != nil { + // return check.Result{}, check.NewError(err) + // } return check.Result{ Name: "net.LookupAddr", Type: check.TypeInfo, - Data: DNS{Names: names}, - } + Info: DNS{Names: names}, + }, nil } diff --git a/checker/geo.go b/checker/geo.go index 66330ee..512eb22 100644 --- a/checker/geo.go +++ b/checker/geo.go @@ -25,30 +25,30 @@ func (g Geo) JsonString() (string, error) { return string(b), err } -// CheckGeo fills in the geolocation data. The data is taken from -// GeoLite2-City.mmdb file that gets downloaded and regularly updated. -func CheckGeo(ip net.IP) check.Result { +// CheckGeo gets data from GeoLite2-City.mmdb that is downloaded and regularly +// updated. +func CheckGeo(ip net.IP) (check.Result, error) { licenseKey, err := check.GetConfigValue("MAXMIND_LICENSE_KEY") if err != nil { - return check.Result{Error: check.NewResultError(err)} + return check.Result{}, check.NewError(err) } file := "/var/tmp/GeoLite2-City.mmdb" url := "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=" + licenseKey + "&suffix=tar.gz" if err := check.UpdateFile(file, url, "tgz"); err != nil { - return check.Result{Error: check.NewResultError(err)} + return check.Result{}, check.NewError(err) } db, err := geoip2.Open(file) if err != nil { - return check.Result{Error: check.NewResultError(fmt.Errorf("can't load DB file: %v", err))} + return check.Result{}, check.NewError(fmt.Errorf("can't load DB file: %v", err)) } defer db.Close() record, err := db.City(ip) if err != nil { - return check.Result{Error: check.NewResultError(err)} + return check.Result{}, check.NewError(err) } geo := Geo{ @@ -60,6 +60,6 @@ func CheckGeo(ip net.IP) check.Result { return check.Result{ Name: "maxmind.com", Type: check.TypeInfo, - Data: geo, - } + Info: geo, + }, nil } diff --git a/checker/ipsum.go b/checker/ipsum.go index df93755..b518956 100644 --- a/checker/ipsum.go +++ b/checker/ipsum.go @@ -12,25 +12,25 @@ import ( ) // CheckIPSum checks how many blacklists the IP address is found on. -func CheckIPSum(ipaddr net.IP) check.Result { +func CheckIPSum(ipaddr net.IP) (check.Result, error) { file := "/var/tmp/ipsum.txt" url := "https://raw.githubusercontent.com/stamparm/ipsum/master/ipsum.txt" if err := check.UpdateFile(file, url, ""); err != nil { - return check.Result{Error: check.NewResultError(err)} + return check.Result{}, check.NewError(err) } blackLists, err := searchIPSumBlacklists(ipaddr, file) if err != nil { - return check.Result{Error: check.NewResultError(fmt.Errorf("searching %s in %s: %v", ipaddr, file, err))} + return check.Result{}, check.NewError(fmt.Errorf("searching %s in %s: %v", ipaddr, file, err)) } return check.Result{ Name: "github.com/stamparm/ipsum", Type: check.TypeSec, - Data: check.EmptyData{}, + Info: check.EmptyInfo{}, IPaddrMalicious: blackLists > 0, - } + }, nil } // searchIPSumBlacklists searches the ippadrr in tsvFile for number of blacklists diff --git a/checker/otx.go b/checker/otx.go index ca7bf92..b658375 100644 --- a/checker/otx.go +++ b/checker/otx.go @@ -14,19 +14,18 @@ type OTX struct { } `json:"pulse_info"` } -// CheckOTX gets data from https://otx.alienvault.com/api. -func CheckOTX(ipaddr net.IP) check.Result { +func CheckOTX(ipaddr net.IP) (check.Result, error) { apiurl := fmt.Sprintf("https://otx.alienvault.com/api/v1/indicators/IPv4/%s/", ipaddr.String()) var otx OTX if err := check.DefaultHttpClient.GetJson(apiurl, map[string]string{}, map[string]string{}, &otx); err != nil { - return check.Result{Error: check.NewResultError(err)} + return check.Result{}, check.NewError(err) } return check.Result{ Name: "otx.alienvault.com", Type: check.TypeSec, - Data: check.EmptyData{}, + Info: check.EmptyInfo{}, IPaddrMalicious: otx.PulseInfo.Count > 10, - } + }, nil } diff --git a/checker/shodan.go b/checker/shodan.go index 6496090..8abf29e 100644 --- a/checker/shodan.go +++ b/checker/shodan.go @@ -25,25 +25,24 @@ type ShodanData []struct { Transport string `json:"transport"` // tcp, udp } -// CheckShodan fills in Shodan data for a given IP address. Its get the data from -// https://api.shodan.io. -func CheckShodan(ipaddr net.IP) check.Result { +// CheckShodan gets data from https://api.shodan.io. +func CheckShodan(ipaddr net.IP) (check.Result, error) { apiKey, err := check.GetConfigValue("SHODAN_API_KEY") if err != nil { - return check.Result{Error: check.NewResultError(err)} + return check.Result{}, check.NewError(err) } var shodan Shodan apiURL := fmt.Sprintf("https://api.shodan.io/shodan/host/%s?key=%s", ipaddr, apiKey) if err := check.DefaultHttpClient.GetJson(apiURL, map[string]string{}, map[string]string{}, &shodan); err != nil { - return check.Result{Error: check.NewResultError(err)} + return check.Result{}, check.NewError(err) } return check.Result{ Name: "shodan.io", Type: check.TypeInfo, - Data: shodan, - } + Info: shodan, + }, nil } type byPort ShodanData diff --git a/checker/threatcrowd.go b/checker/threatcrowd.go index a348f34..b6db678 100644 --- a/checker/threatcrowd.go +++ b/checker/threatcrowd.go @@ -13,7 +13,7 @@ type ThreatCrowd struct { // CheckThreadCrowd retrieves information from // https://www.threatcrowd.org/searchApi/v2/ip/report. -func CheckThreadCrowd(ipaddr net.IP) check.Result { +func CheckThreadCrowd(ipaddr net.IP) (check.Result, error) { queryParams := map[string]string{ "ip": ipaddr.String(), } @@ -24,13 +24,13 @@ func CheckThreadCrowd(ipaddr net.IP) check.Result { // 1: voted harmless by most users var threadCrowd ThreatCrowd if err := check.DefaultHttpClient.GetJson("https://www.threatcrowd.org/searchApi/v2/ip/report", map[string]string{}, queryParams, &threadCrowd); err != nil { - return check.Result{Error: check.NewResultError(err)} + return check.Result{}, check.NewError(err) } return check.Result{ Name: "threatcrowd.org", Type: check.TypeSec, - Data: check.EmptyData{}, + Info: check.EmptyInfo{}, IPaddrMalicious: threadCrowd.Votes < 0, - } + }, nil } diff --git a/checker/virustotal.go b/checker/virustotal.go index b24d5c9..5f4533c 100644 --- a/checker/virustotal.go +++ b/checker/virustotal.go @@ -41,11 +41,11 @@ func (v VirusTotal) JsonString() (string, error) { return string(b), err } -// CheckVirusTotal fills in data about ippaddr from https://www.virustotal.com/api -func CheckVirusTotal(ipaddr net.IP) check.Result { +// CheckVirusTotal gets data about ippaddr from https://www.virustotal.com/api. +func CheckVirusTotal(ipaddr net.IP) (check.Result, error) { apiKey, err := check.GetConfigValue("VIRUSTOTAL_API_KEY") if err != nil { - return check.Result{Error: check.NewResultError(err)} + return check.Result{}, check.NewError(err) } // curl --header "x-apikey:$VIRUSTOTAL_API_KEY" https://www.virustotal.com/api/v3/ip_addresses/1.1.1.1 @@ -53,13 +53,13 @@ func CheckVirusTotal(ipaddr net.IP) check.Result { apiUrl := "https://www.virustotal.com/api/v3/ip_addresses/" + ipaddr.String() var virusTotal VirusTotal if err := check.DefaultHttpClient.GetJson(apiUrl, headers, map[string]string{}, &virusTotal); err != nil { - return check.Result{Error: check.NewResultError(err)} + return check.Result{}, check.NewError(err) } return check.Result{ Name: "virustotal.com", Type: check.TypeInfoSec, - Data: virusTotal, + Info: virusTotal, IPaddrMalicious: virusTotal.Data.Attributes.Reputation < 0, - } + }, nil } diff --git a/cmd/checkip.go b/cmd/checkip.go deleted file mode 100755 index 8a47fef..0000000 --- a/cmd/checkip.go +++ /dev/null @@ -1,38 +0,0 @@ -package cmd - -import ( - "flag" - "log" - "net" - "os" - - "github.com/jreisinger/checkip/check" - "github.com/jreisinger/checkip/checker" -) - -var j = flag.Bool("j", false, "output all results in JSON") - -func Exec() { - flag.Parse() - - log.SetFlags(0) - log.SetPrefix(os.Args[0] + ": ") - - if len(flag.Args()) != 1 { - log.Fatal("supply an IP address") - } - - ipaddr := net.ParseIP(flag.Args()[0]) - if ipaddr == nil { - log.Fatalf("wrong IP address: %s\n", flag.Args()[0]) - } - - results := check.Run(checker.DefaultCheckers, ipaddr) - results.SortByName() - if *j { - results.PrintJSON() - } else { - results.PrintInfo() - results.PrintProbabilityMalicious() - } -} diff --git a/cmd/cmd.go b/cmd/cmd.go new file mode 100755 index 0000000..60cf7a3 --- /dev/null +++ b/cmd/cmd.go @@ -0,0 +1,125 @@ +package cmd + +import ( + "encoding/json" + "flag" + "fmt" + "log" + "net" + "os" + "sort" + "sync" + + "github.com/jreisinger/checkip/check" + "github.com/jreisinger/checkip/checker" + "github.com/logrusorgru/aurora" +) + +func init() { + log.SetFlags(0) + log.SetPrefix(os.Args[0] + ": ") +} + +var j = flag.Bool("j", false, "output all results in JSON") + +func Exec() { + flag.Parse() + + if len(flag.Args()) != 1 { + log.Fatal("supply an IP address") + } + + ipaddr := net.ParseIP(flag.Args()[0]) + if ipaddr == nil { + log.Fatalf("wrong IP address: %s\n", flag.Args()[0]) + } + + results, errors := run(checker.DefaultCheckers, ipaddr) + for _, e := range errors { + log.Print(e) + } + results.SortByName() + if *j { + results.printJSON() + } else { + results.printInfo() + results.printProbabilityMalicious() + } +} + +func run(checks []check.Check, ipaddr net.IP) (results, []error) { + var results results + var errors []error + + var wg sync.WaitGroup + for _, chk := range checks { + wg.Add(1) + go func(c check.Check) { + defer wg.Done() + r, err := c(ipaddr) + if err != nil { + errors = append(errors, err) + // log.Printf("check failed: %v", err) + return + } + results = append(results, r) + }(chk) + } + wg.Wait() + return results, errors +} + +type results []check.Result + +func (rs results) printJSON() { + if len(rs) == 0 { + return + } + enc := json.NewEncoder(os.Stdout) + if err := enc.Encode(rs); err != nil { + log.Fatal(err) + } +} + +func (rs results) SortByName() { + sort.Slice(rs, func(i, j int) bool { + return rs[i].Name < rs[j].Name + }) +} + +// printInfo prints results from Info and InfoSec checkers. +func (rs results) printInfo() { + for _, r := range rs { + if r.Type == "Info" || r.Type == "InfoSec" { + fmt.Printf("%-15s %s\n", r.Name, r.Info.String()) + } + } +} + +// printProbabilityMalicious prints the probability the IP address is malicious. +func (rs results) printProbabilityMalicious() { + var msg string + switch { + case rs.probabilityMalicious() <= 0.15: + msg = fmt.Sprint(aurora.Green("Malicious")) + case rs.probabilityMalicious() <= 0.50: + msg = fmt.Sprint(aurora.Yellow("Malicious")) + default: + msg = fmt.Sprint(aurora.Red("Malicious")) + } + + fmt.Printf("%s\t%.0f%%\n", msg, rs.probabilityMalicious()*100) +} + +func (rs results) probabilityMalicious() float64 { + var malicious, totalSec float64 + for _, r := range rs { + if r.Type == "Sec" || r.Type == "InfoSec" { + totalSec++ + if r.IPaddrMalicious { + malicious++ + } + } + } + return malicious / totalSec +}