Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 770403419d | |||
| 86ceeba6d4 | |||
| e8e8cede33 | |||
| f5a5f07997 | |||
| 8dbd83f233 | |||
| 8246253738 | |||
| 65c4ea6a15 | |||
| ef5a8149e1 | |||
| 21bf091f0e |
Binary file not shown.
|
After Width: | Height: | Size: 188 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 116 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
@@ -16,7 +16,7 @@ PLATFORMS=linux darwin windows
|
|||||||
ARCHS=amd64 arm64
|
ARCHS=amd64 arm64
|
||||||
|
|
||||||
# Version info from git tag or default
|
# Version info from git tag or default
|
||||||
VERSION=$(shell git describe --tags 2>/dev/null || echo "v1.0.1")
|
VERSION=$(shell git describe --tags 2>/dev/null || echo "v1.2.0")
|
||||||
|
|
||||||
.PHONY: all clean build build-all
|
.PHONY: all clean build build-all
|
||||||
|
|
||||||
|
|||||||
@@ -77,14 +77,14 @@ To configure the database location:
|
|||||||
Dehasher can be used simply for example to query for credentials matching a given email domain.
|
Dehasher can be used simply for example to query for credentials matching a given email domain.
|
||||||
``` go
|
``` go
|
||||||
# Provide credentials for emails matching @target.com
|
# Provide credentials for emails matching @target.com
|
||||||
dehasher -k ddq<redacted> -a ar1ste1a@domain.tld -E @target.com
|
dehasher api -D @target.com -C
|
||||||
```
|
```
|
||||||
|
|
||||||
### Simple Credentials Query
|
### Simple Credentials Query
|
||||||
Dehasher can also be used to return only credentials for a given query.
|
Dehasher can also be used to return only credentials for a given query.
|
||||||
``` go
|
``` go
|
||||||
# Provide credentials for emails matching @target.com
|
# Provide credentials for emails matching @target.com
|
||||||
dehasher -E @target.com -C
|
dehasher api -E @target.com -C
|
||||||
```
|
```
|
||||||
|
|
||||||
### Multiple Match Query
|
### Multiple Match Query
|
||||||
@@ -92,7 +92,7 @@ Dehasher is capable of handling multiple queries for the same field.
|
|||||||
This is useful for when you want to search for multiple domains, or multiple usernames.
|
This is useful for when you want to search for multiple domains, or multiple usernames.
|
||||||
``` go
|
``` go
|
||||||
# Provide credentials for emails matching @target.com and @target2.com
|
# Provide credentials for emails matching @target.com and @target2.com
|
||||||
dehasher -E @target.com,@target2.com -C
|
dehasher api -E @target.com,@target2.com -C
|
||||||
```
|
```
|
||||||
|
|
||||||
### Wildcard Query
|
### Wildcard Query
|
||||||
@@ -100,9 +100,10 @@ Dehasher is capable of handling wildcard queries.
|
|||||||
A wildcard query cannot begin with a wildcard.
|
A wildcard query cannot begin with a wildcard.
|
||||||
This is a limitation of the Dehashed API.
|
This is a limitation of the Dehashed API.
|
||||||
An asterisk can be used to denote multiple characters, and a question mark can be used to denote a single character.
|
An asterisk can be used to denote multiple characters, and a question mark can be used to denote a single character.
|
||||||
|

|
||||||
``` go
|
``` go
|
||||||
# Provide credentials for emails matching @target.com and @target2.com
|
# Provide credentials for emails matching @target.com and @target2.com
|
||||||
dehasher -E @target?.com -C -W
|
dehasher api -E @target?.com -C -W
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -112,7 +113,7 @@ Simply denote regex queries with the `-R` flag.
|
|||||||
Place all regex queries in quotes with the corresponding query flag in single quotes.
|
Place all regex queries in quotes with the corresponding query flag in single quotes.
|
||||||
``` go
|
``` go
|
||||||
# Return matches for emails matching this given regex query
|
# Return matches for emails matching this given regex query
|
||||||
dehasher -R -e '[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)?@target.com'
|
dehasher api -R -e '[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)?@target.com'
|
||||||
```
|
```
|
||||||
|
|
||||||
### Output Text (default JSON)
|
### Output Text (default JSON)
|
||||||
@@ -122,7 +123,7 @@ To change the output format, use the `-f` flag.
|
|||||||
Dehasher currently supports JSON, YAML, XML, and TEXT output formats.
|
Dehasher currently supports JSON, YAML, XML, and TEXT output formats.
|
||||||
``` go
|
``` go
|
||||||
# Return matches for usernames exactly matching "admin" and write to text file 'admins_file.txt'
|
# Return matches for usernames exactly matching "admin" and write to text file 'admins_file.txt'
|
||||||
dehasher -U admin -o admins_file -f txt
|
dehasher api -U admin -o admins_file -f txt
|
||||||
```
|
```
|
||||||
|
|
||||||
<hr></hr>
|
<hr></hr>
|
||||||
@@ -132,6 +133,9 @@ Dehasher supports WHOIS lookups, history searches, reverse WHOIS searches, IP lo
|
|||||||
The WhoIs Lookups require a separate API Credit from the Dehashed API.
|
The WhoIs Lookups require a separate API Credit from the Dehashed API.
|
||||||
|
|
||||||
### Domain Lookup
|
### Domain Lookup
|
||||||
|
Dehasher can perform a domain lookup for a given domain.
|
||||||
|
This provides a tree view of the domain's WHOIS information.
|
||||||
|

|
||||||
```bash
|
```bash
|
||||||
# Perform a WHOIS lookup for example.com
|
# Perform a WHOIS lookup for example.com
|
||||||
dehasher whois -d example.com
|
dehasher whois -d example.com
|
||||||
@@ -140,40 +144,57 @@ dehasher whois -d example.com
|
|||||||
### History Lookup
|
### History Lookup
|
||||||
History Lookups require 25 credits.
|
History Lookups require 25 credits.
|
||||||
This is a Dehashed API limitation.
|
This is a Dehashed API limitation.
|
||||||
|
The history lookup is immediately written to file and not displayed in the terminal or stored in the database.
|
||||||
```bash
|
```bash
|
||||||
# Perform a WHOIS history search for example.com
|
# Perform a WHOIS history search for example.com
|
||||||
dehasher whois -d example.com -H
|
dehasher whois -d example.com -H
|
||||||
```
|
```
|
||||||
|
|
||||||
### Reverse WHOIS Lookup
|
### Reverse WHOIS Lookup
|
||||||
|
Dehasher can perform a reverse WHOIS lookup for given criteria.
|
||||||
|
This provides a list of all domains that match the given query.
|
||||||
|
The reverse WHOIS lookup is immediately written to file and not displayed in the terminal or stored in the database.
|
||||||
```bash
|
```bash
|
||||||
# Perform a reverse WHOIS lookup for example.com
|
# Perform a reverse WHOIS lookup for example.com
|
||||||
dehasher whois -I example.com
|
dehasher whois -I example.com
|
||||||
```
|
```
|
||||||
|
|
||||||
### IP Lookup
|
### IP Lookup
|
||||||
|
Dehasher can perform a reverse IP lookup for a given IP address.
|
||||||
|
This provides a list of all domains that match the given query.
|
||||||
|

|
||||||
```bash
|
```bash
|
||||||
# Perform a reverse IP lookup for 8.8.8.8
|
# Perform a reverse IP lookup for 8.8.8.8
|
||||||
dehasher whois -i 8.8.8.8
|
dehasher whois -i 8.8.8.8
|
||||||
```
|
```
|
||||||
|
|
||||||
### MX Lookup
|
### MX Lookup
|
||||||
|
Dehasher can perform an MX lookup for a given MX hostname.
|
||||||
|
This provides a list of all domains that match the given query.
|
||||||
|

|
||||||
```bash
|
```bash
|
||||||
# Perform a reverse MX lookup for google.com
|
# Perform a reverse MX lookup for google.com
|
||||||
dehasher whois -m google.com
|
dehasher whois -m google.com
|
||||||
```
|
```
|
||||||
### NS Lookup
|
### NS Lookup
|
||||||
|
Dehasher can perform an NS lookup for a given NS hostname.
|
||||||
|
This provides a list of all domains that match the given query.
|
||||||
|
The picture below also includes the --debug global flag.
|
||||||
|

|
||||||
```bash
|
```bash
|
||||||
# Perform a reverse NS lookup for google.com
|
# Perform a reverse NS lookup for google.com
|
||||||
dehasher whois -n google.com
|
dehasher whois -n google.com
|
||||||
```
|
```
|
||||||
### Subdomain Scan
|
### Subdomain Scan
|
||||||
|
Dehasher can perform a subdomain scan for a given domain.
|
||||||
|
This provides a list of all subdomains that match the given query.
|
||||||
|

|
||||||
```bash
|
```bash
|
||||||
# Perform a WHOIS subdomain scan for google.com
|
# Perform a WHOIS subdomain scan for google.com
|
||||||
dehasher whois -d google.com -s
|
dehasher whois -d google.com -s
|
||||||
```
|
```
|
||||||
|
|
||||||
<hr></hr>
|
---
|
||||||
|
|
||||||
## 📊 Database Querying
|
## 📊 Database Querying
|
||||||
Dehasher stores query results in a local database.
|
Dehasher stores query results in a local database.
|
||||||
@@ -217,11 +238,17 @@ dehasher query -a
|
|||||||
|
|
||||||
The current tables available for query are:
|
The current tables available for query are:
|
||||||
- results
|
- results
|
||||||
|
- Results from a dehashed query
|
||||||
- creds
|
- creds
|
||||||
|
- Credentials parsed from dehashed results
|
||||||
- whois
|
- whois
|
||||||
|
- Results from a whois record lookup
|
||||||
- subdomains
|
- subdomains
|
||||||
- history
|
- Subdomains discovered in a whois subdomain scan
|
||||||
- runs
|
- runs
|
||||||
|
- Previous query runs to the dehashed API
|
||||||
|
- lookup
|
||||||
|
- Results of any Whois NS, MX, or IP lookup
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+5
-4
@@ -2,7 +2,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"dehasher/internal/badger"
|
"dehasher/internal/badger"
|
||||||
"dehasher/internal/query"
|
"dehasher/internal/dehashed"
|
||||||
"dehasher/internal/sqlite"
|
"dehasher/internal/sqlite"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -12,7 +12,7 @@ func init() {
|
|||||||
// Add query command to root command
|
// Add query command to root command
|
||||||
rootCmd.AddCommand(apiCmd)
|
rootCmd.AddCommand(apiCmd)
|
||||||
|
|
||||||
// Add flags specific to query command
|
// Add flags specific to api command
|
||||||
apiCmd.Flags().IntVarP(&maxRecords, "max-records", "m", 30000, "Maximum amount of records to return")
|
apiCmd.Flags().IntVarP(&maxRecords, "max-records", "m", 30000, "Maximum amount of records to return")
|
||||||
apiCmd.Flags().IntVarP(&maxRequests, "max-requests", "r", -1, "Maximum number of requests to make")
|
apiCmd.Flags().IntVarP(&maxRequests, "max-requests", "r", -1, "Maximum number of requests to make")
|
||||||
apiCmd.Flags().IntVarP(&startingPage, "starting-page", "s", 1, "Starting page for requests")
|
apiCmd.Flags().IntVarP(&startingPage, "starting-page", "s", 1, "Starting page for requests")
|
||||||
@@ -36,7 +36,7 @@ func init() {
|
|||||||
apiCmd.Flags().StringVarP(&hashQuery, "hash", "Q", "", "Hashed password query")
|
apiCmd.Flags().StringVarP(&hashQuery, "hash", "Q", "", "Hashed password query")
|
||||||
apiCmd.Flags().StringVarP(&nameQuery, "name", "N", "", "Name query")
|
apiCmd.Flags().StringVarP(&nameQuery, "name", "N", "", "Name query")
|
||||||
|
|
||||||
// Add mutually exclusive flags to exact match and regex match
|
// Add mutually exclusive flags to wildcard match and regex match
|
||||||
apiCmd.MarkFlagsMutuallyExclusive("regex-match", "wildcard-match")
|
apiCmd.MarkFlagsMutuallyExclusive("regex-match", "wildcard-match")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,10 +103,11 @@ var (
|
|||||||
wildcardMatch,
|
wildcardMatch,
|
||||||
printBalance,
|
printBalance,
|
||||||
credsOnly,
|
credsOnly,
|
||||||
|
debugGlobal,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create new Dehasher
|
// Create new Dehasher
|
||||||
dehasher := query.NewDehasher(queryOptions)
|
dehasher := dehashed.NewDehasher(queryOptions)
|
||||||
dehasher.SetClientCredentials(
|
dehasher.SetClientCredentials(
|
||||||
key,
|
key,
|
||||||
)
|
)
|
||||||
|
|||||||
+22
-15
@@ -12,31 +12,38 @@ import (
|
|||||||
|
|
||||||
// Map of available tables and their columns
|
// Map of available tables and their columns
|
||||||
var availableTables = map[string][]string{
|
var availableTables = map[string][]string{
|
||||||
"results": {
|
|
||||||
"id", "created_at", "updated_at", "deleted_at", "dehashed_id", "email", "ip_address", "username",
|
|
||||||
"password", "hashed_password", "hash_type", "name", "vin", "license_plate", "url", "social",
|
|
||||||
"cryptocurrency_address", "address", "phone", "company", "database_name",
|
|
||||||
},
|
|
||||||
"creds": {
|
"creds": {
|
||||||
"id", "created_at", "updated_at", "deleted_at", "email", "username", "password",
|
"id", "created_at", "updated_at", "deleted_at", "email", "username", "password",
|
||||||
},
|
},
|
||||||
"whois": {
|
//"history": {
|
||||||
"id", "created_at", "updated_at", "deleted_at", "contact_email", "created_date",
|
// "id", "created_at", "updated_at", "deleted_at", "domain_name", "domain_type",
|
||||||
"domain_name", "domain_name_ext", "expires_date", "status", "whois_server",
|
// "registrar_name", "whois_server", "created_date_iso8601", "updated_date_iso8601", "expires_date_iso8601",
|
||||||
},
|
//},
|
||||||
"subdomains": {
|
"lookup": {
|
||||||
"id", "created_at", "updated_at", "deleted_at", "domain", "first_seen", "last_seen",
|
"id", "created_at", "updated_at", "deleted_at", "search_term", "type", "first_seen", "last_visit",
|
||||||
},
|
"name",
|
||||||
"history": {
|
|
||||||
"id", "created_at", "updated_at", "deleted_at", "domain_name", "domain_type",
|
|
||||||
"registrar_name", "whois_server", "created_date_iso8601", "updated_date_iso8601", "expires_date_iso8601",
|
|
||||||
},
|
},
|
||||||
|
// Query Options
|
||||||
"runs": {
|
"runs": {
|
||||||
"id", "created_at", "updated_at", "deleted_at", "max_records", "max_requests", "starting_page",
|
"id", "created_at", "updated_at", "deleted_at", "max_records", "max_requests", "starting_page",
|
||||||
"output_format", "output_file", "regex_match", "wildcard_match", "username_query", "email_query",
|
"output_format", "output_file", "regex_match", "wildcard_match", "username_query", "email_query",
|
||||||
"ip_query", "pass_query", "hash_query", "name_query", "domain_query", "vin_query", "license_plate_query",
|
"ip_query", "pass_query", "hash_query", "name_query", "domain_query", "vin_query", "license_plate_query",
|
||||||
"address_query", "phone_query", "social_query", "crypto_address_query", "print_balance", "creds_only",
|
"address_query", "phone_query", "social_query", "crypto_address_query", "print_balance", "creds_only",
|
||||||
},
|
},
|
||||||
|
"results": {
|
||||||
|
"id", "created_at", "updated_at", "deleted_at", "dehashed_id", "email", "ip_address", "username",
|
||||||
|
"password", "hashed_password", "hash_type", "name", "vin", "license_plate", "url", "social",
|
||||||
|
"cryptocurrency_address", "address", "phone", "company", "database_name",
|
||||||
|
},
|
||||||
|
"subdomains": {
|
||||||
|
"id", "created_at", "updated_at", "deleted_at", "domain", "first_seen", "last_seen",
|
||||||
|
},
|
||||||
|
"whois": {
|
||||||
|
"id", "created_at", "updated_at", "deleted_at", "audit", "contact_email", "created_date", "created_date_normalized",
|
||||||
|
"domain_name", "domain_name_ext", "estimated_domain_age", "expires_date", "expires_date_normalized", "footer", "header",
|
||||||
|
"name_servers", "parse_code", "raw_text", "registrant", "registrar_iana_id", "registrar_name", "registry_data",
|
||||||
|
"status", "stripped_text", "updated_date", "updated_date_normalized",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to list available tables and their columns
|
// Function to list available tables and their columns
|
||||||
|
|||||||
+3
-5
@@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// Global Flags
|
// Global Flags
|
||||||
|
debugGlobal bool
|
||||||
|
|
||||||
// rootCmd is the base command for the CLI.
|
// rootCmd is the base command for the CLI.
|
||||||
rootCmd = &cobra.Command{
|
rootCmd = &cobra.Command{
|
||||||
@@ -58,14 +59,11 @@ func Execute() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
//// Attempt to retrieve the useLocalDB
|
|
||||||
//useLocalDatabase := badger.GetUseLocalDB()
|
|
||||||
|
|
||||||
// Hide the default help command
|
// Hide the default help command
|
||||||
rootCmd.CompletionOptions.HiddenDefaultCmd = true
|
rootCmd.CompletionOptions.HiddenDefaultCmd = true
|
||||||
|
|
||||||
//// Add global flags
|
// Add global flags
|
||||||
//rootCmd.PersistentFlags().BoolVar(&useLocalDatabase, "local-db", useLocalDatabase, "Use local database in current directory instead of default path")
|
rootCmd.PersistentFlags().BoolVar(&debugGlobal, "debug", false, "Show debugGlobal information")
|
||||||
|
|
||||||
// Add subcommands
|
// Add subcommands
|
||||||
rootCmd.AddCommand(setKeyCmd)
|
rootCmd.AddCommand(setKeyCmd)
|
||||||
|
|||||||
+431
-76
@@ -1,14 +1,38 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"dehasher/internal/debug"
|
||||||
|
"dehasher/internal/export"
|
||||||
|
"dehasher/internal/files"
|
||||||
|
"dehasher/internal/pretty"
|
||||||
"dehasher/internal/sqlite"
|
"dehasher/internal/sqlite"
|
||||||
"dehasher/internal/whois"
|
"dehasher/internal/whois"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Add whois subcommand to root command
|
||||||
|
rootCmd.AddCommand(whoisCmd)
|
||||||
|
|
||||||
|
// Add flags specific to whois command
|
||||||
|
whoisCmd.Flags().StringVarP(&whoisDomain, "domain", "d", "", "Domain for WHOIS lookup, history search, and subdomain scan")
|
||||||
|
whoisCmd.Flags().StringVarP(&whoisIPAddress, "ip", "i", "", "IP address for reverse IP lookup")
|
||||||
|
whoisCmd.Flags().StringVarP(&whoisMXAddress, "mx", "m", "", "MX hostname for reverse MX lookup")
|
||||||
|
whoisCmd.Flags().StringVarP(&whoisNSAddress, "ns", "n", "", "NS hostname for reverse NS lookup")
|
||||||
|
whoisCmd.Flags().StringVarP(&whoisInclude, "include", "I", "", "Up to 4 Terms to include in reverse WHOIS search (comma-separated)")
|
||||||
|
whoisCmd.Flags().StringVarP(&whoisExclude, "exclude", "E", "", "Up to 4 Terms to exclude in reverse WHOIS search (comma-separated)")
|
||||||
|
whoisCmd.Flags().StringVarP(&whoisReverseType, "type", "t", "registrant", "Type of reverse WHOIS search ([default] current or historic)")
|
||||||
|
whoisCmd.Flags().StringVarP(&whoisOutputFormat, "format", "f", "text", "Output format (text, json)")
|
||||||
|
whoisCmd.Flags().StringVarP(&whoisOutputFile, "output", "o", "whois", "File to output results to including extension")
|
||||||
|
whoisCmd.Flags().BoolVarP(&whoisShowCredits, "credits", "c", false, "Show remaining WHOIS credits")
|
||||||
|
whoisCmd.Flags().BoolVarP(&whoisHistory, "history", "H", false, "Perform WHOIS history search [25 Credits]")
|
||||||
|
whoisCmd.Flags().BoolVarP(&whoisSubdomainScan, "subdomains", "s", false, "Perform WHOIS subdomain scan")
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// WHOIS command flags
|
// WHOIS command flags
|
||||||
whoisDomain string
|
whoisDomain string
|
||||||
@@ -19,6 +43,7 @@ var (
|
|||||||
whoisExclude string
|
whoisExclude string
|
||||||
whoisReverseType string
|
whoisReverseType string
|
||||||
whoisOutputFormat string
|
whoisOutputFormat string
|
||||||
|
whoisOutputFile string
|
||||||
whoisShowCredits bool
|
whoisShowCredits bool
|
||||||
whoisHistory bool
|
whoisHistory bool
|
||||||
whoisSubdomainScan bool
|
whoisSubdomainScan bool
|
||||||
@@ -37,20 +62,50 @@ var (
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show credits if requested
|
if debugGlobal {
|
||||||
if whoisShowCredits {
|
debug.PrintInfo("debug mode enabled")
|
||||||
fmt.Println("[*] Getting WHOIS credits...")
|
zap.L().Info("whois_debug",
|
||||||
credits, err := whois.GetWHOISCredits(key)
|
zap.String("message", "debug mode enabled"),
|
||||||
if err != nil {
|
|
||||||
zap.L().Error("get_whois_credits",
|
|
||||||
zap.String("message", "failed to get whois credits"),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
)
|
||||||
fmt.Printf("Error getting WHOIS credits: %v\n", err)
|
}
|
||||||
|
|
||||||
|
if whoisOutputFile == "" {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("output file not specified, using default")
|
||||||
|
}
|
||||||
|
whoisOutputFile = "whois_" + time.Now().Format("05_04_05")
|
||||||
|
}
|
||||||
|
|
||||||
|
if whoisOutputFormat == "" {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("output format not specified, using default")
|
||||||
|
}
|
||||||
|
whoisOutputFormat = "json"
|
||||||
|
}
|
||||||
|
|
||||||
|
fType := files.GetFileType(whoisOutputFormat)
|
||||||
|
if fType == files.UNKNOWN {
|
||||||
|
fmt.Println("[!] Error: Invalid output format. Must be 'json', 'xml', 'yaml', or 'txt'.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("WHOIS Credits: %d\n", credits.WhoisCredits)
|
if debugGlobal {
|
||||||
return
|
debug.PrintInfo("using output format: " + whoisOutputFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
w := whois.NewWhoIs(key, debugGlobal)
|
||||||
|
|
||||||
|
// Show credits if requested
|
||||||
|
if whoisShowCredits {
|
||||||
|
fmt.Println("[*] Getting WHOIS balance...")
|
||||||
|
balance, err := w.Balance()
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("get_whois_credits",
|
||||||
|
zap.String("message", "failed to get whois balance"),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
fmt.Printf("Error getting WHOIS balance: %v\n", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("WHOIS Credits: %d\n", balance)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if domain is provided for history and subdomain scan
|
// Check if domain is provided for history and subdomain scan
|
||||||
@@ -59,14 +114,30 @@ var (
|
|||||||
fmt.Println("Domain is required for history and subdomain scan.")
|
fmt.Println("Domain is required for history and subdomain scan.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if whoisShowCredits {
|
||||||
|
balance, err := w.Balance()
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("get_whois_credits",
|
||||||
|
zap.String("message", "failed to get whois balance"),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
fmt.Printf("Error getting WHOIS balance: %v\n", err)
|
||||||
|
}
|
||||||
|
fmt.Println("WHOIS Credits: ", balance)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine which operation to perform based on flags
|
// Determine which operation to perform based on flags
|
||||||
if whoisDomain != "" {
|
if whoisDomain != "" {
|
||||||
fmt.Println("[*] Performing WHOIS lookup...")
|
fmt.Println("[*] Performing WHOIS lookup...")
|
||||||
|
|
||||||
// Domain lookup
|
// Domain lookup
|
||||||
result, err := whois.WhoisSearch(whoisDomain, key)
|
result, err := w.WhoisSearch(whoisDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("failed to perform whois search")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
zap.L().Error("whois_search",
|
zap.L().Error("whois_search",
|
||||||
zap.String("message", "failed to perform whois search"),
|
zap.String("message", "failed to perform whois search"),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
@@ -75,12 +146,32 @@ var (
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if whoisShowCredits {
|
||||||
|
balance, err := w.Balance()
|
||||||
|
if err != nil {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("failed to get whois balance")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
|
zap.L().Error("get_whois_credits",
|
||||||
|
zap.String("message", "failed to get whois balance"),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
fmt.Printf("Error getting WHOIS balance: %v\n", err)
|
||||||
|
}
|
||||||
|
fmt.Println("WHOIS Credits: ", balance)
|
||||||
|
}
|
||||||
|
|
||||||
// Fix the output format to use proper formatting
|
// Fix the output format to use proper formatting
|
||||||
fmt.Printf("WHOIS Lookup Result:\n%+v\n", result.Data.WhoisRecord)
|
fmt.Println("WHOIS Lookup Result:")
|
||||||
|
|
||||||
// Store the record
|
// Store the record
|
||||||
err = sqlite.StoreWhoisRecord(result.Data.WhoisRecord)
|
err = sqlite.StoreWhoisRecord(result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("failed to store whois record")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
zap.L().Error("store_whois_record",
|
zap.L().Error("store_whois_record",
|
||||||
zap.String("message", "failed to store whois record"),
|
zap.String("message", "failed to store whois record"),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
@@ -89,75 +180,187 @@ var (
|
|||||||
// Continue execution even if storage fails
|
// Continue execution even if storage fails
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pretty Print WhoIs Record
|
||||||
|
pretty.WhoIsTree(whoisDomain, result)
|
||||||
|
|
||||||
|
// Write WhoIs Record to file
|
||||||
|
if len(result.DomainName) != 0 {
|
||||||
|
fmt.Printf("[*] Writing WHOIS record to file: %s%s\n", whoisOutputFile, fType.Extension())
|
||||||
|
err = export.WriteWhoIsRecordToFile(result, whoisOutputFile, fType)
|
||||||
|
} else {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("no whois record to write to file")
|
||||||
|
}
|
||||||
|
zap.L().Info("write_whois_record",
|
||||||
|
zap.String("message", "no whois record to write to file"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if whoisHistory {
|
if whoisHistory {
|
||||||
fmt.Println("[*] Performing WHOIS history search...")
|
fmt.Println("[*] Performing WHOIS history search...")
|
||||||
// Perform history search
|
// Perform history search
|
||||||
history, err := whois.WhoisHistory(whoisDomain, key)
|
historyRecords, err := w.WhoisHistory(whoisDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("failed to perform whois history lookup")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
zap.L().Error("whois_history",
|
zap.L().Error("whois_history",
|
||||||
zap.String("message", "failed to perform whois history lookup"),
|
zap.String("message", "failed to perform whois history lookup"),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
fmt.Printf("Error performing WHOIS history lookup: %v\n", err)
|
fmt.Printf("[!] Error performing WHOIS history lookup: %v\n", err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("\nWHOIS History:")
|
if whoisShowCredits {
|
||||||
fmt.Println(history)
|
balance, err := w.Balance()
|
||||||
|
if err != nil {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("failed to get whois balance")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
|
zap.L().Error("get_whois_credits",
|
||||||
|
zap.String("message", "failed to get whois balance"),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
fmt.Printf("Error getting WHOIS balance: %v\n", err)
|
||||||
|
}
|
||||||
|
fmt.Println("WHOIS Credits: ", balance)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = sqlite.StoreHistoryRecord(history.Data.Records)
|
// Write history records to file if any
|
||||||
|
if len(historyRecords) > 0 {
|
||||||
|
fmt.Println("[*] Records Found: %d\n", len(historyRecords))
|
||||||
|
fmt.Println("[*] WHOIS History being written to file: %s%s\n", whoisOutputFile, fType.Extension())
|
||||||
|
writeErr := export.WriteWhoIsHistoryToFile(historyRecords, whoisOutputFile, fType)
|
||||||
|
if writeErr != nil {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("failed to write whois history to file")
|
||||||
|
debug.PrintError(writeErr)
|
||||||
|
}
|
||||||
|
zap.L().Error("write_whois_history",
|
||||||
|
zap.String("message", "failed to write whois history to file"),
|
||||||
|
zap.Error(writeErr),
|
||||||
|
)
|
||||||
|
fmt.Printf("[!] Error writing WHOIS history to file: %v\n", writeErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sqlite.StoreHistoryRecord(historyRecords)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("failed to store history record")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
zap.L().Error("store_history_record",
|
zap.L().Error("store_history_record",
|
||||||
zap.String("message", "failed to store history record"),
|
zap.String("message", "failed to store history record"),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
fmt.Printf("Error storing WHOIS history record: %v\n", err)
|
fmt.Printf("Error storing WHOIS history record: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("no whois history records to write to file")
|
||||||
|
}
|
||||||
|
zap.L().Info("write_whois_history",
|
||||||
|
zap.String("message", "no whois history records to write to file"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform subdomain scan
|
// Perform subdomain scan
|
||||||
if whoisSubdomainScan {
|
if whoisSubdomainScan {
|
||||||
fmt.Println("[*] Performing WHOIS subdomain scan...")
|
fmt.Println("[*] Performing WHOIS subdomain scan...")
|
||||||
subdomains, err := whois.WhoisSubdomainScan(whoisDomain, key)
|
subdomains, err := w.WhoisSubdomainScan(whoisDomain)
|
||||||
|
|
||||||
|
if whoisShowCredits {
|
||||||
|
balance, err := w.Balance()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("failed to get whois balance")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
|
zap.L().Error("get_whois_credits",
|
||||||
|
zap.String("message", "failed to get whois balance"),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
fmt.Printf("Error getting WHOIS balance: %v\n", err)
|
||||||
|
}
|
||||||
|
fmt.Println("WHOIS Credits: ", balance)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("failed to perform subdomain scan")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
zap.L().Error("whois_subdomain_scan",
|
zap.L().Error("whois_subdomain_scan",
|
||||||
zap.String("message", "failed to perform subdomain scan"),
|
zap.String("message", "failed to perform subdomain scan"),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
fmt.Printf("Error performing subdomain scan: %v\n", err)
|
fmt.Printf("Error performing subdomain scan: %v\n", err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("\nSubdomain Scan:")
|
fmt.Println("Subdomain Scan:")
|
||||||
fmt.Println(subdomains)
|
err = sqlite.StoreSubdomainRecords(subdomains)
|
||||||
}
|
|
||||||
|
|
||||||
err = sqlite.StoreSubdomainRecord(subdomains.Data.Result.Records)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("failed to store subdomain record")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
zap.L().Error("store_subdomain_record",
|
zap.L().Error("store_subdomain_record",
|
||||||
zap.String("message", "failed to store subdomain record"),
|
zap.String("message", "failed to store subdomain record"),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
fmt.Printf("Error storing WHOIS subdomain record: %v\n", err)
|
fmt.Printf("Error storing WHOIS subdomain record: %v\n", err)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Get credits
|
// Write the subdomains to file if any
|
||||||
credits, err := whois.GetWHOISCredits(key)
|
if len(subdomains) > 0 {
|
||||||
|
fmt.Printf("[*] Writing subdomains to file: %s%s\n", whoisOutputFile, fType.Extension())
|
||||||
|
err = export.WriteSubdomainsToFile(subdomains, whoisOutputFile, fType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Error("get_whois_credits",
|
zap.L().Error("write_whois_subdomain",
|
||||||
zap.String("message", "failed to get whois credits"),
|
zap.String("message", "failed to write whois subdomain to file"),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
fmt.Printf("Error getting WHOIS credits: %v\n", err)
|
fmt.Printf("Error writing WHOIS subdomain to file: %v\n", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
fmt.Printf("\nWHOIS Credits Remaining: %d\n", credits.WhoisCredits)
|
|
||||||
|
var (
|
||||||
|
headers = []string{"Domain", "First Seen", "Last Seen"}
|
||||||
|
rows [][]string
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create the rows
|
||||||
|
for _, r := range subdomains {
|
||||||
|
rows = append(rows, []string{r.Domain, time.Unix(r.FirstSeen, 0).String(), time.Unix(r.LastSeen, 0).String()})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the subdomains
|
||||||
|
pretty.Table(headers, rows)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
fmt.Println("[!] No subdomains found")
|
||||||
|
zap.L().Info("write_whois_subdomain",
|
||||||
|
zap.String("message", "no whois subdomain records to write to file"),
|
||||||
|
zap.String("domain", whoisDomain),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if whoisIPAddress != "" {
|
if whoisIPAddress != "" {
|
||||||
fmt.Println("[*] Performing reverse IP lookup...")
|
fmt.Println("[*] Performing reverse IP lookup...")
|
||||||
// IP lookup
|
// IP lookup
|
||||||
result, err := whois.WhoisIP(whoisIPAddress, key)
|
result, err := w.WhoisIP(whoisIPAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("failed to perform ip lookup")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
zap.L().Error("whois_ip",
|
zap.L().Error("whois_ip",
|
||||||
zap.String("message", "failed to perform ip lookup"),
|
zap.String("message", "failed to perform ip lookup"),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
@@ -165,28 +368,73 @@ var (
|
|||||||
fmt.Printf("Error performing IP lookup: %v\n", err)
|
fmt.Printf("Error performing IP lookup: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("IP Lookup Result:")
|
|
||||||
fmt.Println(string(result))
|
|
||||||
|
|
||||||
// Get credits
|
// Get credits
|
||||||
credits, err := whois.GetWHOISCredits(key)
|
if whoisShowCredits {
|
||||||
|
balance, err := w.Balance()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("failed to get whois balance")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
zap.L().Error("get_whois_credits",
|
zap.L().Error("get_whois_credits",
|
||||||
zap.String("message", "failed to get whois credits"),
|
zap.String("message", "failed to get whois balance"),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
fmt.Printf("Error getting WHOIS credits: %v\n", err)
|
fmt.Printf("Error getting WHOIS balance: %v\n", err)
|
||||||
|
}
|
||||||
|
fmt.Println("WHOIS Credits: ", balance)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result) == 0 {
|
||||||
|
fmt.Println("[!] No results found")
|
||||||
|
zap.L().Info("whois_ip",
|
||||||
|
zap.String("message", "no results found"),
|
||||||
|
zap.String("ip", whoisIPAddress),
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("\nWHOIS Credits Remaining: %d\n", credits.WhoisCredits)
|
|
||||||
|
// Write the results to file
|
||||||
|
fmt.Printf("[*] Writing IP lookup results to file: %s%s\n", whoisOutputFile, fType.Extension())
|
||||||
|
err = export.WriteIPLookupToFile(result, whoisOutputFile, fType)
|
||||||
|
if err != nil {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("failed to write ip lookup to file")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
|
zap.L().Error("write_ip_lookup",
|
||||||
|
zap.String("message", "failed to write ip lookup to file"),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
fmt.Printf("Error writing IP lookup to file: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pretty Print the JSON
|
||||||
|
var (
|
||||||
|
headers = []string{"Name", "Search Term", "First Seen", "Last Visit", "Type"}
|
||||||
|
rows [][]string
|
||||||
|
)
|
||||||
|
fmt.Println("IP Lookup Result:")
|
||||||
|
|
||||||
|
for _, r := range result {
|
||||||
|
rows = append(rows, []string{r.Name, r.SearchTerm, time.Unix(r.FirstSeen, 0).String(), time.Unix(r.LastVisit, 0).String(), r.Type})
|
||||||
|
}
|
||||||
|
|
||||||
|
pretty.Table(headers, rows)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if whoisMXAddress != "" {
|
if whoisMXAddress != "" {
|
||||||
fmt.Println("[*] Performing reverse MX lookup...")
|
fmt.Println("[*] Performing reverse MX lookup...")
|
||||||
// MX lookup
|
// MX lookup
|
||||||
result, err := whois.WhoisMX(whoisMXAddress, key)
|
result, err := w.WhoisMX(whoisMXAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("failed to perform mx lookup")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
zap.L().Error("whois_mx",
|
zap.L().Error("whois_mx",
|
||||||
zap.String("message", "failed to perform mx lookup"),
|
zap.String("message", "failed to perform mx lookup"),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
@@ -195,29 +443,72 @@ var (
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo unmarshal mx lookup
|
|
||||||
fmt.Println("MX Lookup Result:")
|
|
||||||
fmt.Println(result)
|
|
||||||
|
|
||||||
// Get credits
|
// Get credits
|
||||||
credits, err := whois.GetWHOISCredits(key)
|
if whoisShowCredits {
|
||||||
|
balance, err := w.Balance()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("failed to get whois balance")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
zap.L().Error("get_whois_credits",
|
zap.L().Error("get_whois_credits",
|
||||||
zap.String("message", "failed to get whois credits"),
|
zap.String("message", "failed to get whois balance"),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
fmt.Printf("Error getting WHOIS credits: %v\n", err)
|
fmt.Printf("Error getting WHOIS balance: %v\n", err)
|
||||||
|
}
|
||||||
|
fmt.Println("WHOIS Credits: ", balance)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result) == 0 {
|
||||||
|
fmt.Println("[!] No results found")
|
||||||
|
zap.L().Info("whois_mx",
|
||||||
|
zap.String("message", "no results found"),
|
||||||
|
zap.String("mx", whoisMXAddress),
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("\nWHOIS Credits Remaining: %d\n", credits.WhoisCredits)
|
|
||||||
|
// Write the results to file
|
||||||
|
fmt.Printf("[*] Writing MX lookup results to file: %s%s\n", whoisOutputFile, fType.Extension())
|
||||||
|
err = export.WriteIPLookupToFile(result, whoisOutputFile, fType)
|
||||||
|
if err != nil {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("failed to write mx lookup to file")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
|
zap.L().Error("write_mx_lookup",
|
||||||
|
zap.String("message", "failed to write mx lookup to file"),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
fmt.Printf("Error writing MX lookup to file: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pretty Print the JSON
|
||||||
|
var (
|
||||||
|
headers = []string{"Name", "Search Term", "First Seen", "Last Visit", "Type"}
|
||||||
|
rows [][]string
|
||||||
|
)
|
||||||
|
fmt.Println("MX Lookup Result:")
|
||||||
|
|
||||||
|
for _, r := range result {
|
||||||
|
rows = append(rows, []string{r.Name, r.SearchTerm, time.Unix(r.FirstSeen, 0).String(), time.Unix(r.LastVisit, 0).String(), r.Type})
|
||||||
|
}
|
||||||
|
|
||||||
|
pretty.Table(headers, rows)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if whoisNSAddress != "" {
|
if whoisNSAddress != "" {
|
||||||
fmt.Println("[*] Performing reverse NS lookup...")
|
fmt.Println("[*] Performing reverse NS lookup...")
|
||||||
// NS lookup
|
// NS lookup
|
||||||
result, err := whois.WhoisNS(whoisNSAddress, key)
|
result, err := w.WhoisNS(whoisNSAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("failed to perform ns lookup")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
zap.L().Error("whois_ns",
|
zap.L().Error("whois_ns",
|
||||||
zap.String("message", "failed to perform ns lookup"),
|
zap.String("message", "failed to perform ns lookup"),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
@@ -225,20 +516,61 @@ var (
|
|||||||
fmt.Printf("Error performing NS lookup: %v\n", err)
|
fmt.Printf("Error performing NS lookup: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("NS Lookup Result:")
|
|
||||||
fmt.Println(result)
|
|
||||||
|
|
||||||
// Get credits
|
// Get credits
|
||||||
credits, err := whois.GetWHOISCredits(key)
|
if whoisShowCredits {
|
||||||
|
balance, err := w.Balance()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("failed to get whois balance")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
zap.L().Error("get_whois_credits",
|
zap.L().Error("get_whois_credits",
|
||||||
zap.String("message", "failed to get whois credits"),
|
zap.String("message", "failed to get whois balance"),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
fmt.Printf("Error getting WHOIS credits: %v\n", err)
|
fmt.Printf("Error getting WHOIS balance: %v\n", err)
|
||||||
|
}
|
||||||
|
fmt.Println("WHOIS Credits: ", balance)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result) == 0 {
|
||||||
|
fmt.Println("[!] No results found")
|
||||||
|
zap.L().Info("whois_ns",
|
||||||
|
zap.String("message", "no results found"),
|
||||||
|
zap.String("ns", whoisNSAddress),
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("\nWHOIS Credits Remaining: %d\n", credits.WhoisCredits)
|
|
||||||
|
// Write the results to file
|
||||||
|
fmt.Printf("[*] Writing NS lookup results to file: %s%s\n", whoisOutputFile, fType.Extension())
|
||||||
|
err = export.WriteIPLookupToFile(result, whoisOutputFile, fType)
|
||||||
|
if err != nil {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("failed to write ns lookup to file")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
|
zap.L().Error("write_ns_lookup",
|
||||||
|
zap.String("message", "failed to write ns lookup to file"),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
fmt.Printf("Error writing NS lookup to file: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pretty Print the JSON
|
||||||
|
var (
|
||||||
|
headers = []string{"Name", "Search Term", "First Seen", "Last Visit", "Type"}
|
||||||
|
rows [][]string
|
||||||
|
)
|
||||||
|
fmt.Println("NS Lookup Result:")
|
||||||
|
|
||||||
|
for _, r := range result {
|
||||||
|
rows = append(rows, []string{r.Name, r.SearchTerm, time.Unix(r.FirstSeen, 0).String(), time.Unix(r.LastVisit, 0).String(), r.Type})
|
||||||
|
}
|
||||||
|
|
||||||
|
pretty.Table(headers, rows)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,25 +579,66 @@ var (
|
|||||||
includeTerms := []string{}
|
includeTerms := []string{}
|
||||||
if whoisInclude != "" {
|
if whoisInclude != "" {
|
||||||
includeTerms = strings.Split(whoisInclude, ",")
|
includeTerms = strings.Split(whoisInclude, ",")
|
||||||
|
if len(includeTerms) > 4 {
|
||||||
|
fmt.Println("[!] Error: Maximum of 4 include terms allowed.")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
excludeTerms := []string{}
|
excludeTerms := []string{}
|
||||||
if whoisExclude != "" {
|
if whoisExclude != "" {
|
||||||
excludeTerms = strings.Split(whoisExclude, ",")
|
excludeTerms = strings.Split(whoisExclude, ",")
|
||||||
|
if len(excludeTerms) > 4 {
|
||||||
|
fmt.Println("[!] Error: Maximum of 4 exclude terms allowed.")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if whoisReverseType == "" {
|
if whoisReverseType == "" {
|
||||||
whoisReverseType = "registrant"
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("reverse type not specified, using default")
|
||||||
|
}
|
||||||
|
whoisReverseType = "current"
|
||||||
|
} else {
|
||||||
|
toLower := strings.ToLower(whoisReverseType)
|
||||||
|
if toLower != "current" && toLower != "historic" {
|
||||||
|
fmt.Println("[!] Error: Invalid reverse type. Must be 'current' or 'historic'.")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("[*] Performing reverse WHOIS lookup...")
|
fmt.Println("[*] Performing reverse WHOIS lookup...")
|
||||||
result, err := whois.ReverseWHOIS(includeTerms, excludeTerms, whoisReverseType, key)
|
result, err := w.ReverseWHOIS(includeTerms, excludeTerms, whoisReverseType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("failed to perform reverse whois")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
|
zap.L().Error("reverse_whois",
|
||||||
|
zap.String("message", "failed to perform reverse whois"),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
fmt.Printf("Error performing reverse WHOIS: %v\n", err)
|
fmt.Printf("Error performing reverse WHOIS: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("Reverse WHOIS Result:")
|
fmt.Println("Reverse WHOIS Result:")
|
||||||
fmt.Println(result)
|
fmt.Println(result)
|
||||||
|
|
||||||
|
if whoisShowCredits {
|
||||||
|
balance, err := w.Balance()
|
||||||
|
if err != nil {
|
||||||
|
if debugGlobal {
|
||||||
|
debug.PrintInfo("failed to get whois balance")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
|
zap.L().Error("get_whois_credits",
|
||||||
|
zap.String("message", "failed to get whois balance"),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
fmt.Printf("Error getting WHOIS balance: %v\n", err)
|
||||||
|
}
|
||||||
|
fmt.Println("WHOIS Credits: ", balance)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,21 +647,3 @@ var (
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Add whois command to root command
|
|
||||||
rootCmd.AddCommand(whoisCmd)
|
|
||||||
|
|
||||||
// Add flags specific to whois command
|
|
||||||
whoisCmd.Flags().StringVarP(&whoisDomain, "domain", "d", "", "Domain for WHOIS lookup, history search, and subdomain scan")
|
|
||||||
whoisCmd.Flags().StringVarP(&whoisIPAddress, "ip", "i", "", "IP address for reverse IP lookup")
|
|
||||||
whoisCmd.Flags().StringVarP(&whoisMXAddress, "mx", "m", "", "MX address for reverse MX lookup")
|
|
||||||
whoisCmd.Flags().StringVarP(&whoisNSAddress, "ns", "n", "", "NS address for reverse NS lookup")
|
|
||||||
whoisCmd.Flags().StringVarP(&whoisInclude, "include", "I", "", "Terms to include in reverse WHOIS search (comma-separated)")
|
|
||||||
whoisCmd.Flags().StringVarP(&whoisExclude, "exclude", "E", "", "Terms to exclude in reverse WHOIS search (comma-separated)")
|
|
||||||
whoisCmd.Flags().StringVarP(&whoisReverseType, "type", "t", "registrant", "Type of reverse WHOIS search (registrant, email, organization, address, phone)")
|
|
||||||
whoisCmd.Flags().StringVarP(&whoisOutputFormat, "format", "f", "text", "Output format (text, json)")
|
|
||||||
whoisCmd.Flags().BoolVarP(&whoisShowCredits, "credits", "c", false, "Show remaining WHOIS credits")
|
|
||||||
whoisCmd.Flags().BoolVarP(&whoisHistory, "history", "H", false, "Perform WHOIS history search [25 Credits]")
|
|
||||||
whoisCmd.Flags().BoolVarP(&whoisSubdomainScan, "subdomains", "s", false, "Perform WHOIS subdomain scan")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package debug
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/fatih/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PrintError(err error) {
|
||||||
|
errLine := fmt.Sprintf("[DEBUG-ERROR] %s", err)
|
||||||
|
fmt.Println(color.RedString(errLine))
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrintJson(json string) {
|
||||||
|
jsonLine := fmt.Sprintf("[DEBUG-JSON] %s", json)
|
||||||
|
fmt.Print(color.GreenString(jsonLine))
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrintInfo(info string) {
|
||||||
|
infoLine := fmt.Sprintf("[DEBUG-INFO] %s", info)
|
||||||
|
fmt.Println(color.BlueString(infoLine))
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
package query
|
package dehashed
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"dehasher/internal/debug"
|
||||||
"dehasher/internal/sqlite"
|
"dehasher/internal/sqlite"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -38,6 +39,7 @@ func (dp DehashedParameter) GetArgumentString(arg string) string {
|
|||||||
|
|
||||||
type DehashedSearchRequest struct {
|
type DehashedSearchRequest struct {
|
||||||
ForcePlaintext bool `json:"-"`
|
ForcePlaintext bool `json:"-"`
|
||||||
|
Debug bool `json:"-"`
|
||||||
Page int `json:"page"`
|
Page int `json:"page"`
|
||||||
Query string `json:"query"`
|
Query string `json:"query"`
|
||||||
Size int `json:"size"`
|
Size int `json:"size"`
|
||||||
@@ -46,42 +48,60 @@ type DehashedSearchRequest struct {
|
|||||||
DeDupe bool `json:"de_dupe"`
|
DeDupe bool `json:"de_dupe"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDehashedSearchRequest(page, size int, wildcard, regex, forcePlaintext bool) *DehashedSearchRequest {
|
func NewDehashedSearchRequest(page, size int, wildcard, regex, forcePlaintext, debug bool) *DehashedSearchRequest {
|
||||||
return &DehashedSearchRequest{Page: page, Query: "", Size: size, Wildcard: wildcard, Regex: regex, DeDupe: true, ForcePlaintext: forcePlaintext}
|
return &DehashedSearchRequest{Page: page, Query: "", Size: size, Wildcard: wildcard, Regex: regex, DeDupe: true, ForcePlaintext: forcePlaintext, Debug: debug}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dsr *DehashedSearchRequest) buildQuery(query string) {
|
||||||
|
if dsr.Debug {
|
||||||
|
debug.PrintInfo(fmt.Sprintf("building query: %s", query))
|
||||||
|
}
|
||||||
|
// Ensure query is properly formatted
|
||||||
|
query = strings.TrimSpace(query)
|
||||||
|
|
||||||
|
// For regex queries, we need to ensure the regex pattern is properly escaped
|
||||||
|
// and not enquoted, as that would break the regex pattern
|
||||||
|
if dsr.Regex && !strings.HasPrefix(query, "\"") && !strings.HasSuffix(query, "\"") {
|
||||||
|
// Don't add extra quotes for regex patterns
|
||||||
|
} else if strings.Contains(query, " ") && !strings.HasPrefix(query, "\"") {
|
||||||
|
query = fmt.Sprintf("\"%s\"", query)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dsr *DehashedSearchRequest) buildQuery(query string, param DehashedParameter) {
|
|
||||||
if len(dsr.Query) > 0 {
|
if len(dsr.Query) > 0 {
|
||||||
dsr.Query = fmt.Sprintf("%s&%s", strings.TrimSpace(dsr.Query), strings.TrimSpace(query))
|
dsr.Query = fmt.Sprintf("%s&%s", strings.TrimSpace(dsr.Query), query)
|
||||||
} else {
|
} else {
|
||||||
dsr.Query = query
|
dsr.Query = query
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dsr.Debug {
|
||||||
|
debug.PrintInfo(fmt.Sprintf("query built: %s", dsr.Query))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dsr *DehashedSearchRequest) AddUsernameQuery(query string) {
|
func (dsr *DehashedSearchRequest) AddUsernameQuery(query string) {
|
||||||
query = enquoteSpaced(query)
|
query = enquoteSpaced(query)
|
||||||
dsr.buildQuery(Username.GetArgumentString(query), Username)
|
dsr.buildQuery(Username.GetArgumentString(query))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dsr *DehashedSearchRequest) AddEmailQuery(query string) {
|
func (dsr *DehashedSearchRequest) AddEmailQuery(query string) {
|
||||||
query = enquoteSpaced(query)
|
query = enquoteSpaced(query)
|
||||||
dsr.buildQuery(Email.GetArgumentString(query), Email)
|
dsr.buildQuery(Email.GetArgumentString(query))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dsr *DehashedSearchRequest) AddIpAddressQuery(query string) {
|
func (dsr *DehashedSearchRequest) AddIpAddressQuery(query string) {
|
||||||
query = enquoteSpaced(query)
|
query = enquoteSpaced(query)
|
||||||
dsr.buildQuery(IpAddress.GetArgumentString(query), IpAddress)
|
dsr.buildQuery(IpAddress.GetArgumentString(query))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dsr *DehashedSearchRequest) AddDomainQuery(query string) {
|
func (dsr *DehashedSearchRequest) AddDomainQuery(query string) {
|
||||||
query = enquoteSpaced(query)
|
query = enquoteSpaced(query)
|
||||||
dsr.buildQuery(Domain.GetArgumentString(query), Domain)
|
dsr.buildQuery(Domain.GetArgumentString(query))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dsr *DehashedSearchRequest) AddPasswordQuery(query string) {
|
func (dsr *DehashedSearchRequest) AddPasswordQuery(query string) {
|
||||||
if dsr.ForcePlaintext {
|
if dsr.ForcePlaintext {
|
||||||
query = enquoteSpaced(query)
|
query = enquoteSpaced(query)
|
||||||
dsr.buildQuery(Password.GetArgumentString(query), Password)
|
dsr.buildQuery(Password.GetArgumentString(query))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hash := sha256.Sum256([]byte(query))
|
hash := sha256.Sum256([]byte(query))
|
||||||
@@ -91,89 +111,126 @@ func (dsr *DehashedSearchRequest) AddPasswordQuery(query string) {
|
|||||||
|
|
||||||
func (dsr *DehashedSearchRequest) AddVinQuery(query string) {
|
func (dsr *DehashedSearchRequest) AddVinQuery(query string) {
|
||||||
query = enquoteSpaced(query)
|
query = enquoteSpaced(query)
|
||||||
dsr.buildQuery(Vin.GetArgumentString(query), Vin)
|
dsr.buildQuery(Vin.GetArgumentString(query))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dsr *DehashedSearchRequest) AddLicensePlateQuery(query string) {
|
func (dsr *DehashedSearchRequest) AddLicensePlateQuery(query string) {
|
||||||
query = enquoteSpaced(query)
|
query = enquoteSpaced(query)
|
||||||
dsr.buildQuery(LicensePlate.GetArgumentString(query), LicensePlate)
|
dsr.buildQuery(LicensePlate.GetArgumentString(query))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dsr *DehashedSearchRequest) AddAddressQuery(query string) {
|
func (dsr *DehashedSearchRequest) AddAddressQuery(query string) {
|
||||||
query = enquoteSpaced(query)
|
query = enquoteSpaced(query)
|
||||||
dsr.buildQuery(Address.GetArgumentString(query), Address)
|
dsr.buildQuery(Address.GetArgumentString(query))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dsr *DehashedSearchRequest) AddPhoneQuery(query string) {
|
func (dsr *DehashedSearchRequest) AddPhoneQuery(query string) {
|
||||||
query = enquoteSpaced(query)
|
query = enquoteSpaced(query)
|
||||||
dsr.buildQuery(Phone.GetArgumentString(query), Phone)
|
dsr.buildQuery(Phone.GetArgumentString(query))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dsr *DehashedSearchRequest) AddSocialQuery(query string) {
|
func (dsr *DehashedSearchRequest) AddSocialQuery(query string) {
|
||||||
query = enquoteSpaced(query)
|
query = enquoteSpaced(query)
|
||||||
dsr.buildQuery(Social.GetArgumentString(query), Social)
|
dsr.buildQuery(Social.GetArgumentString(query))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dsr *DehashedSearchRequest) AddCryptoAddressQuery(query string) {
|
func (dsr *DehashedSearchRequest) AddCryptoAddressQuery(query string) {
|
||||||
query = enquoteSpaced(query)
|
query = enquoteSpaced(query)
|
||||||
dsr.buildQuery(CryptoAddress.GetArgumentString(query), CryptoAddress)
|
dsr.buildQuery(CryptoAddress.GetArgumentString(query))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dsr *DehashedSearchRequest) AddHashedPasswordQuery(query string) {
|
func (dsr *DehashedSearchRequest) AddHashedPasswordQuery(query string) {
|
||||||
query = strings.TrimSpace(query)
|
query = strings.TrimSpace(query)
|
||||||
dsr.buildQuery(HashedPassword.GetArgumentString(query), HashedPassword)
|
dsr.buildQuery(HashedPassword.GetArgumentString(query))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dsr *DehashedSearchRequest) AddNameQuery(query string) {
|
func (dsr *DehashedSearchRequest) AddNameQuery(query string) {
|
||||||
query = enquoteSpaced(query)
|
query = enquoteSpaced(query)
|
||||||
dsr.buildQuery(Name.GetArgumentString(query), Name)
|
dsr.buildQuery(Name.GetArgumentString(query))
|
||||||
}
|
}
|
||||||
|
|
||||||
type DehashedClientV2 struct {
|
type DehashedClientV2 struct {
|
||||||
apiKey string
|
apiKey string
|
||||||
results []sqlite.Result
|
results []sqlite.Result
|
||||||
|
debug bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDehashedClientV2(apiKey string) *DehashedClientV2 {
|
func NewDehashedClientV2(apiKey string, debug bool) *DehashedClientV2 {
|
||||||
return &DehashedClientV2{apiKey: apiKey}
|
return &DehashedClientV2{apiKey: apiKey, debug: debug}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dcv2 *DehashedClientV2) Search(searchRequest DehashedSearchRequest) (int, error) {
|
func (dcv2 *DehashedClientV2) Search(searchRequest DehashedSearchRequest) (int, int, error) {
|
||||||
|
if dcv2.debug {
|
||||||
|
debug.PrintInfo("preparing search request")
|
||||||
|
zap.L().Info("v2_search_debug",
|
||||||
|
zap.String("message", "preparing search request"),
|
||||||
|
)
|
||||||
|
}
|
||||||
reqBody, _ := json.Marshal(searchRequest)
|
reqBody, _ := json.Marshal(searchRequest)
|
||||||
|
|
||||||
|
if dcv2.debug {
|
||||||
|
j := string(reqBody)
|
||||||
|
jReq := fmt.Sprintf("Request Body: %s\n", j)
|
||||||
|
debug.PrintJson(jReq)
|
||||||
|
zap.L().Info("v2_search_debug",
|
||||||
|
zap.String("message", jReq),
|
||||||
|
zap.String("body", j),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", "https://api.dehashed.com/v2/search", bytes.NewReader(reqBody))
|
req, err := http.NewRequest("POST", "https://api.dehashed.com/v2/search", bytes.NewReader(reqBody))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dcv2.debug {
|
||||||
|
debug.PrintInfo("setting headers")
|
||||||
|
zap.L().Info("v2_search_debug",
|
||||||
|
zap.String("message", "setting headers"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("Dehashed-Api-Key", dcv2.apiKey)
|
req.Header.Set("Dehashed-Api-Key", dcv2.apiKey)
|
||||||
|
|
||||||
|
if dcv2.debug {
|
||||||
|
headers := req.Header.Clone()
|
||||||
|
h := fmt.Sprintf("Headers: %v\n", headers)
|
||||||
|
debug.PrintJson(h)
|
||||||
|
zap.L().Info("v2_search_debug",
|
||||||
|
zap.String("message", h),
|
||||||
|
zap.String("headers", fmt.Sprintf("%v", headers)),
|
||||||
|
)
|
||||||
|
|
||||||
|
debug.PrintInfo("performing request")
|
||||||
|
zap.L().Info("v2_search_debug",
|
||||||
|
zap.String("message", "performing request"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
res, err := http.DefaultClient.Do(req)
|
res, err := http.DefaultClient.Do(req)
|
||||||
if res != nil {
|
if res != nil {
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if dcv2.debug {
|
||||||
|
debug.PrintInfo("failed to perform request")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
zap.L().Error("v2_search",
|
zap.L().Error("v2_search",
|
||||||
zap.String("message", "failed to perform request"),
|
zap.String("message", "failed to perform request"),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
return -1, err
|
return -1, -1, err
|
||||||
}
|
}
|
||||||
if res == nil {
|
if res == nil {
|
||||||
|
if dcv2.debug {
|
||||||
|
debug.PrintInfo("response was nil")
|
||||||
|
}
|
||||||
zap.L().Error("v2_search",
|
zap.L().Error("v2_search",
|
||||||
zap.String("message", "response was nil"),
|
zap.String("message", "response was nil"),
|
||||||
)
|
)
|
||||||
return -1, errors.New("response was nil")
|
return -1, -1, errors.New("response was nil")
|
||||||
}
|
|
||||||
|
|
||||||
// Check for HTTP status code errors
|
|
||||||
if res.StatusCode != 200 {
|
|
||||||
dhErr := GetDehashedError(res.StatusCode)
|
|
||||||
fmt.Printf("[%d] API Error message: %s\n", res.StatusCode, dhErr.Error())
|
|
||||||
zap.L().Error("v2_search",
|
|
||||||
zap.String("message", "received error status code"),
|
|
||||||
zap.Int("status_code", res.StatusCode),
|
|
||||||
zap.String("error", dhErr.Error()),
|
|
||||||
)
|
|
||||||
return -1, &dhErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := io.ReadAll(res.Body)
|
b, err := io.ReadAll(res.Body)
|
||||||
@@ -182,21 +239,50 @@ func (dcv2 *DehashedClientV2) Search(searchRequest DehashedSearchRequest) (int,
|
|||||||
zap.String("message", "failed to read response body"),
|
zap.String("message", "failed to read response body"),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
return -1, err
|
return -1, -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for HTTP status code errors
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
if dcv2.debug {
|
||||||
|
debug.PrintInfo("received error status code")
|
||||||
|
debug.PrintJson(fmt.Sprintf("Status Code: %d\n", res.StatusCode))
|
||||||
|
debug.PrintJson(fmt.Sprintf("Body: %s\n", string(b[:])))
|
||||||
|
}
|
||||||
|
|
||||||
|
dhErr := GetDehashedError(res.StatusCode)
|
||||||
|
zap.L().Error("v2_search",
|
||||||
|
zap.String("message", "received error status code"),
|
||||||
|
zap.Int("status_code", res.StatusCode),
|
||||||
|
zap.String("error", dhErr.Error()),
|
||||||
|
zap.String("body_error", string(b)),
|
||||||
|
)
|
||||||
|
return -1, -1, &dhErr
|
||||||
}
|
}
|
||||||
|
|
||||||
var responseResults sqlite.DehashedResponse
|
var responseResults sqlite.DehashedResponse
|
||||||
err = json.Unmarshal(b, &responseResults)
|
err = json.Unmarshal(b, &responseResults)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if dcv2.debug {
|
||||||
|
debug.PrintInfo("failed to unmarshal response body")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
zap.L().Error("v2_search",
|
zap.L().Error("v2_search",
|
||||||
zap.String("message", "failed to unmarshal response body"),
|
zap.String("message", "failed to unmarshal response body"),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
return -1, err
|
return -1, -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if dcv2.debug {
|
||||||
|
debug.PrintInfo("appending results")
|
||||||
|
debug.PrintJson(fmt.Sprintf("Total Results: %d\n", responseResults.TotalResults))
|
||||||
|
debug.PrintJson(fmt.Sprintf("Balance: %d\n", responseResults.Balance))
|
||||||
|
debug.PrintJson(fmt.Sprintf("Entries: %d\n", len(responseResults.Entries)))
|
||||||
}
|
}
|
||||||
|
|
||||||
dcv2.results = append(dcv2.results, responseResults.Entries...)
|
dcv2.results = append(dcv2.results, responseResults.Entries...)
|
||||||
return responseResults.TotalResults, nil
|
return responseResults.TotalResults, responseResults.Balance, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dcv2 *DehashedClientV2) GetResults() sqlite.DehashedResults {
|
func (dcv2 *DehashedClientV2) GetResults() sqlite.DehashedResults {
|
||||||
@@ -0,0 +1,322 @@
|
|||||||
|
package dehashed
|
||||||
|
|
||||||
|
import (
|
||||||
|
"dehasher/internal/debug"
|
||||||
|
"dehasher/internal/export"
|
||||||
|
"dehasher/internal/sqlite"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dehasher is a struct for querying the Dehashed API
|
||||||
|
type Dehasher struct {
|
||||||
|
options sqlite.QueryOptions
|
||||||
|
nextPage int
|
||||||
|
debug bool
|
||||||
|
balance int
|
||||||
|
request *DehashedSearchRequest
|
||||||
|
client *DehashedClientV2
|
||||||
|
queryPlan []struct{ Page, Size int }
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDehasher creates a new Dehasher
|
||||||
|
func NewDehasher(options *sqlite.QueryOptions) *Dehasher {
|
||||||
|
dh := &Dehasher{
|
||||||
|
options: *options,
|
||||||
|
nextPage: options.StartingPage + 1,
|
||||||
|
debug: options.Debug,
|
||||||
|
balance: 0,
|
||||||
|
queryPlan: make([]struct{ Page, Size int }, 0),
|
||||||
|
}
|
||||||
|
dh.setQueries()
|
||||||
|
dh.request = NewDehashedSearchRequest(
|
||||||
|
dh.queryPlan[0].Page,
|
||||||
|
dh.queryPlan[0].Size,
|
||||||
|
dh.options.WildcardMatch,
|
||||||
|
dh.options.RegexMatch,
|
||||||
|
false,
|
||||||
|
options.Debug,
|
||||||
|
)
|
||||||
|
|
||||||
|
dh.buildRequest()
|
||||||
|
return dh
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetClientCredentials sets the client credentials for the dehasher
|
||||||
|
func (dh *Dehasher) SetClientCredentials(key string) {
|
||||||
|
dh.client = NewDehashedClientV2(key, dh.debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dh *Dehasher) getNextPage() int {
|
||||||
|
if dh.debug {
|
||||||
|
debug.PrintInfo(fmt.Sprintf("getting next page: %d", dh.nextPage))
|
||||||
|
}
|
||||||
|
nextPage := dh.nextPage
|
||||||
|
dh.nextPage += 1
|
||||||
|
return nextPage
|
||||||
|
}
|
||||||
|
|
||||||
|
// generatePagination creates a list of (page, size) tuples such that page * size <= 10000
|
||||||
|
func generatePagination(maxRecords int) []struct{ Page, Size int } {
|
||||||
|
const maxPageProduct = 9500
|
||||||
|
var queries []struct{ Page, Size int }
|
||||||
|
|
||||||
|
remaining := maxRecords
|
||||||
|
page := 1
|
||||||
|
|
||||||
|
for remaining > 0 {
|
||||||
|
size := (maxPageProduct - 1) / page // guarantees page * size < 10000
|
||||||
|
if size > remaining {
|
||||||
|
size = remaining
|
||||||
|
}
|
||||||
|
queries = append(queries, struct{ Page, Size int }{page, size})
|
||||||
|
remaining -= size
|
||||||
|
page++
|
||||||
|
}
|
||||||
|
|
||||||
|
return queries
|
||||||
|
}
|
||||||
|
|
||||||
|
// setQueries sets the number of queries to make based on the number of records and requests
|
||||||
|
func (dh *Dehasher) setQueries() {
|
||||||
|
if dh.options.MaxRecords <= 0 {
|
||||||
|
dh.options.MaxRecords = 10000
|
||||||
|
}
|
||||||
|
|
||||||
|
dh.queryPlan = generatePagination(dh.options.MaxRecords)
|
||||||
|
|
||||||
|
fmt.Printf("Making %d requests to retrieve %d records\n", len(dh.queryPlan), dh.options.MaxRecords)
|
||||||
|
|
||||||
|
if dh.debug {
|
||||||
|
for i, q := range dh.queryPlan {
|
||||||
|
debug.PrintInfo(fmt.Sprintf("query %d: page=%d, size=%d", i+1, q.Page, q.Size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts the querying process
|
||||||
|
func (dh *Dehasher) Start() {
|
||||||
|
fmt.Printf("[*] Querying Dehashed API...\n")
|
||||||
|
|
||||||
|
// Make initial request to get total count
|
||||||
|
fmt.Printf(" [*] Performing initial request to determine total records...\n")
|
||||||
|
totalRecords, balance, err := dh.client.Search(*dh.request)
|
||||||
|
if err != nil {
|
||||||
|
handleSearchError(dh, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dh.balance = balance
|
||||||
|
recordsRetrieved := len(dh.client.results)
|
||||||
|
|
||||||
|
fmt.Printf(" [+] Retrieved %d records\n", recordsRetrieved)
|
||||||
|
fmt.Printf(" [*] Total available records: %d\n", totalRecords)
|
||||||
|
|
||||||
|
if dh.options.PrintBalance {
|
||||||
|
fmt.Printf(" [*] Balance: %d\n", balance)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've already got all records or reached our limit, we're done
|
||||||
|
if recordsRetrieved >= totalRecords || recordsRetrieved >= dh.options.MaxRecords {
|
||||||
|
fmt.Printf(" [*] All requested records retrieved\n")
|
||||||
|
dh.parseResults()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate remaining records to fetch
|
||||||
|
remainingRecords := totalRecords - recordsRetrieved
|
||||||
|
if dh.options.MaxRecords > 0 && dh.options.MaxRecords < totalRecords {
|
||||||
|
remainingRecords = dh.options.MaxRecords - recordsRetrieved
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we need user confirmation for large datasets
|
||||||
|
if remainingRecords > 30000 {
|
||||||
|
tokensRequired := (remainingRecords + 9999) / 10000 // Ceiling division
|
||||||
|
fmt.Printf("\n[!] Large dataset detected: %d additional records\n", remainingRecords)
|
||||||
|
fmt.Printf("[!] This will require approximately %d API tokens\n", tokensRequired)
|
||||||
|
fmt.Printf("[!] Your current balance: %d\n", balance)
|
||||||
|
|
||||||
|
if balance < tokensRequired {
|
||||||
|
fmt.Printf("[!] WARNING: Your balance (%d) is less than required tokens (%d)\n", balance, tokensRequired)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("[?] Do you want to continue? (y/n): ")
|
||||||
|
var response string
|
||||||
|
fmt.Scanln(&response)
|
||||||
|
|
||||||
|
if response != "y" && response != "Y" {
|
||||||
|
fmt.Println("[*] Operation cancelled by user")
|
||||||
|
dh.parseResults()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make additional requests
|
||||||
|
for i, q := range dh.queryPlan {
|
||||||
|
if i == 0 {
|
||||||
|
// We already made the first request before this loop
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dh.request.Page = q.Page
|
||||||
|
dh.request.Size = q.Size
|
||||||
|
|
||||||
|
fmt.Printf(" [*] Performing Request %d of %d (page=%d, size=%d)...\n", i+1, len(dh.queryPlan), q.Page, q.Size)
|
||||||
|
|
||||||
|
_, balance, err := dh.client.Search(*dh.request)
|
||||||
|
if err != nil {
|
||||||
|
handleSearchError(dh, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
dh.balance = balance
|
||||||
|
recordsRetrieved += len(dh.client.results)
|
||||||
|
|
||||||
|
fmt.Printf(" [+] Retrieved %d total records so far\n", recordsRetrieved)
|
||||||
|
|
||||||
|
if dh.options.PrintBalance {
|
||||||
|
fmt.Printf(" [*] Balance: %d\n", balance)
|
||||||
|
}
|
||||||
|
|
||||||
|
if recordsRetrieved >= totalRecords || recordsRetrieved >= dh.options.MaxRecords {
|
||||||
|
fmt.Printf(" [*] All requested records retrieved\n")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dh.parseResults()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to handle search errors
|
||||||
|
func handleSearchError(dh *Dehasher, err error) {
|
||||||
|
if dh.debug {
|
||||||
|
debug.PrintInfo("error performing request")
|
||||||
|
debug.PrintError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's a DehashError
|
||||||
|
if dhErr, ok := err.(*DehashError); ok {
|
||||||
|
fmt.Printf(" [!] Dehashed API Error: %s (Code: %d)\n", dhErr.Message, dhErr.Code)
|
||||||
|
zap.L().Error("dehashed_api_error",
|
||||||
|
zap.String("message", dhErr.Message),
|
||||||
|
zap.Int("code", dhErr.Code),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" [!] Error performing request: %v\n", err)
|
||||||
|
zap.L().Error("request_error",
|
||||||
|
zap.String("message", "failed to perform request"),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dh.client.results) > 0 {
|
||||||
|
fmt.Printf(" [!] Partial results retrieved. Storing Results...\n")
|
||||||
|
err := sqlite.StoreResults(dh.client.GetResults())
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("store_results",
|
||||||
|
zap.String("message", "failed to store results"),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
fmt.Printf(" [!] Error storing results: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildRequest constructs the query map
|
||||||
|
func (dh *Dehasher) buildRequest() {
|
||||||
|
if len(dh.options.UsernameQuery) > 0 {
|
||||||
|
dh.request.AddUsernameQuery(dh.options.UsernameQuery)
|
||||||
|
}
|
||||||
|
if len(dh.options.EmailQuery) > 0 {
|
||||||
|
dh.request.AddEmailQuery(dh.options.EmailQuery)
|
||||||
|
}
|
||||||
|
if len(dh.options.IpQuery) > 0 {
|
||||||
|
dh.request.AddIpAddressQuery(dh.options.IpQuery)
|
||||||
|
}
|
||||||
|
if len(dh.options.HashQuery) > 0 {
|
||||||
|
dh.request.AddHashedPasswordQuery(dh.options.HashQuery)
|
||||||
|
}
|
||||||
|
if len(dh.options.PassQuery) > 0 {
|
||||||
|
dh.request.AddPasswordQuery(dh.options.PassQuery)
|
||||||
|
}
|
||||||
|
if len(dh.options.NameQuery) > 0 {
|
||||||
|
dh.request.AddNameQuery(dh.options.NameQuery)
|
||||||
|
}
|
||||||
|
if len(dh.options.DomainQuery) > 0 {
|
||||||
|
dh.request.AddDomainQuery(dh.options.DomainQuery)
|
||||||
|
}
|
||||||
|
if len(dh.options.VinQuery) > 0 {
|
||||||
|
dh.request.AddVinQuery(dh.options.VinQuery)
|
||||||
|
}
|
||||||
|
if len(dh.options.LicensePlateQuery) > 0 {
|
||||||
|
dh.request.AddLicensePlateQuery(dh.options.LicensePlateQuery)
|
||||||
|
}
|
||||||
|
if len(dh.options.AddressQuery) > 0 {
|
||||||
|
dh.request.AddAddressQuery(dh.options.AddressQuery)
|
||||||
|
}
|
||||||
|
if len(dh.options.PhoneQuery) > 0 {
|
||||||
|
dh.request.AddPhoneQuery(dh.options.PhoneQuery)
|
||||||
|
}
|
||||||
|
if len(dh.options.SocialQuery) > 0 {
|
||||||
|
dh.request.AddSocialQuery(dh.options.SocialQuery)
|
||||||
|
}
|
||||||
|
if len(dh.options.CryptoAddressQuery) > 0 {
|
||||||
|
dh.request.AddCryptoAddressQuery(dh.options.CryptoAddressQuery)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseResults parses the results and writes them to a file
|
||||||
|
func (dh *Dehasher) parseResults() {
|
||||||
|
var data []byte
|
||||||
|
|
||||||
|
zap.L().Info("extracting_credentials")
|
||||||
|
results := dh.client.GetResults()
|
||||||
|
creds := results.ExtractCredentials()
|
||||||
|
fmt.Printf("\n\t[+] Discovered %d Credentials", len(creds))
|
||||||
|
err := sqlite.StoreCreds(creds)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("store_creds",
|
||||||
|
zap.String("message", "failed to store creds"),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
zap.L().Info("creds_stored", zap.Int("count", len(creds)))
|
||||||
|
|
||||||
|
zap.L().Info("storing_results")
|
||||||
|
err = sqlite.StoreResults(results)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("store_results",
|
||||||
|
zap.String("message", "failed to store results"),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
zap.L().Info("results_stored", zap.Int("count", len(results.Results)))
|
||||||
|
|
||||||
|
if len(results.Results) > 0 {
|
||||||
|
fmt.Printf("\n\t[*] Writing entries to file: %s.%s", dh.options.OutputFile, dh.options.OutputFormat.String())
|
||||||
|
if !dh.options.CredsOnly {
|
||||||
|
err := export.WriteToFile(results, dh.options.OutputFile, dh.options.OutputFormat)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("\n[!] Error Writing to file: %v\n\tOutputting to terminal.", err)
|
||||||
|
data, err = json.MarshalIndent(results, "", " ")
|
||||||
|
fmt.Println(string(data))
|
||||||
|
os.Exit(0)
|
||||||
|
} else {
|
||||||
|
fmt.Println("\n\t\t[*] Success\n")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
creds := results.ExtractCredentials()
|
||||||
|
err := export.WriteCredsToFile(creds, dh.options.OutputFile, dh.options.OutputFormat)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("\n[!] Error Writing to file: %v\n\tOutputting to terminal.", err)
|
||||||
|
data, err = json.MarshalIndent(creds, "", " ")
|
||||||
|
fmt.Println(string(data))
|
||||||
|
os.Exit(0)
|
||||||
|
} else {
|
||||||
|
fmt.Println("\n\t\t[*] Success\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package query
|
package dehashed
|
||||||
|
|
||||||
type DehashError struct {
|
type DehashError struct {
|
||||||
Message string
|
Message string
|
||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func WriteCredsToFile(creds []sqlite.Creds, outputFile string, fileType files.FileType) error {
|
func WriteCredsToFile(creds []sqlite.Creds, outputFile string, fileType files.FileType) error {
|
||||||
@@ -131,3 +132,121 @@ func WriteQueryResultsToFile(results []map[string]interface{}, outputFile string
|
|||||||
filePath := fmt.Sprintf("%s.%s", outputFile, fileType.String())
|
filePath := fmt.Sprintf("%s.%s", outputFile, fileType.String())
|
||||||
return os.WriteFile(filePath, data, 0644)
|
return os.WriteFile(filePath, data, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WriteWhoIsHistoryToFile(results []sqlite.HistoryRecord, outputFile string, fileType files.FileType) error {
|
||||||
|
var data []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch fileType {
|
||||||
|
case files.JSON:
|
||||||
|
data, err = json.MarshalIndent(results, "", " ")
|
||||||
|
case files.XML:
|
||||||
|
data, err = xml.MarshalIndent(results, "", " ")
|
||||||
|
case files.YAML:
|
||||||
|
data, err = yaml.Marshal(results)
|
||||||
|
case files.TEXT:
|
||||||
|
var outStrings []string
|
||||||
|
for _, r := range results {
|
||||||
|
outStrings = append(outStrings, r.String()+"\n\n")
|
||||||
|
}
|
||||||
|
data = []byte(strings.Join(outStrings, ""))
|
||||||
|
default:
|
||||||
|
return errors.New("unsupported file type")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := fmt.Sprintf("%s.%s", outputFile, fileType.String())
|
||||||
|
return os.WriteFile(filePath, data, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteWhoIsRecordToFile(record sqlite.WhoisRecord, outputFile string, fileType files.FileType) error {
|
||||||
|
var data []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch fileType {
|
||||||
|
case files.JSON:
|
||||||
|
data, err = json.MarshalIndent(record, "", " ")
|
||||||
|
case files.XML:
|
||||||
|
data, err = xml.MarshalIndent(record, "", " ")
|
||||||
|
case files.YAML:
|
||||||
|
data, err = yaml.Marshal(record)
|
||||||
|
case files.TEXT:
|
||||||
|
data = []byte(record.String())
|
||||||
|
default:
|
||||||
|
return errors.New("unsupported file type")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := fmt.Sprintf("%s.%s", outputFile, fileType.String())
|
||||||
|
return os.WriteFile(filePath, data, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteSubdomainsToFile(records []sqlite.SubdomainRecord, outputFile string, fileType files.FileType) error {
|
||||||
|
var data []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch fileType {
|
||||||
|
case files.JSON:
|
||||||
|
data, err = json.MarshalIndent(records, "", " ")
|
||||||
|
case files.XML:
|
||||||
|
data, err = xml.MarshalIndent(records, "", " ")
|
||||||
|
case files.YAML:
|
||||||
|
data, err = yaml.Marshal(records)
|
||||||
|
case files.TEXT:
|
||||||
|
var outStrings []string
|
||||||
|
for _, r := range records {
|
||||||
|
out := fmt.Sprintf(
|
||||||
|
"Domain: %s\nFirst Seen: %s\nLast Seen: %s\n\n",
|
||||||
|
r.Domain, time.Unix(r.FirstSeen, 0).String(), time.Unix(r.LastSeen, 0).String())
|
||||||
|
outStrings = append(outStrings, out)
|
||||||
|
}
|
||||||
|
data = []byte(strings.Join(outStrings, ""))
|
||||||
|
default:
|
||||||
|
return errors.New("unsupported file type")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := fmt.Sprintf("%s.%s", outputFile, fileType.String())
|
||||||
|
return os.WriteFile(filePath, data, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteIPLookupToFile(records []sqlite.LookupResult, outputFile string, fileType files.FileType) error {
|
||||||
|
var data []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch fileType {
|
||||||
|
case files.JSON:
|
||||||
|
data, err = json.MarshalIndent(records, "", " ")
|
||||||
|
case files.XML:
|
||||||
|
data, err = xml.MarshalIndent(records, "", " ")
|
||||||
|
case files.YAML:
|
||||||
|
data, err = yaml.Marshal(records)
|
||||||
|
case files.TEXT:
|
||||||
|
var outStrings []string
|
||||||
|
for _, r := range records {
|
||||||
|
out := fmt.Sprintf(
|
||||||
|
"Name: %s\nSearch Term: %s\nFirst Seen: %s\nLast Visit: %s\nType: %s\n\n",
|
||||||
|
r.Name, r.SearchTerm, time.Unix(r.FirstSeen, 0).String(), time.Unix(r.LastVisit, 0).String(), r.Type)
|
||||||
|
outStrings = append(outStrings, out)
|
||||||
|
}
|
||||||
|
data = []byte(strings.Join(outStrings, ""))
|
||||||
|
default:
|
||||||
|
return errors.New("unsupported file type")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := fmt.Sprintf("%s.%s", outputFile, fileType.String())
|
||||||
|
return os.WriteFile(filePath, data, 0644)
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const (
|
|||||||
XML
|
XML
|
||||||
YAML
|
YAML
|
||||||
TEXT
|
TEXT
|
||||||
|
UNKNOWN
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetFileType(filetype string) FileType {
|
func GetFileType(filetype string) FileType {
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package pretty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"dehasher/internal/sqlite"
|
||||||
|
"fmt"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
"github.com/charmbracelet/lipgloss/tree"
|
||||||
|
)
|
||||||
|
|
||||||
|
func WhoIsTree(root string, record sqlite.WhoisRecord) {
|
||||||
|
enumeratorStyle := lipgloss.NewStyle().Foreground(purple).MarginRight(1)
|
||||||
|
rootStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("63"))
|
||||||
|
itemStyle := lipgloss.NewStyle().Foreground(gray)
|
||||||
|
|
||||||
|
rootTree := tree.Root(root)
|
||||||
|
|
||||||
|
// Child Trees
|
||||||
|
// Root Audit Tree
|
||||||
|
auditTree := tree.Root("Audit")
|
||||||
|
auditTree.Child(fmt.Sprintf("Created Date: %s", record.Audit.CreatedDate))
|
||||||
|
auditTree.Child(fmt.Sprintf("Updated Date: %s", record.Audit.UpdatedDate))
|
||||||
|
rootTree.Child(auditTree)
|
||||||
|
|
||||||
|
// Root Name Servers Tree
|
||||||
|
nameServersTree := tree.Root("Name Servers")
|
||||||
|
nameServersTree.Child("Host Names: " + fmt.Sprintf("%v", record.NameServers.HostNames))
|
||||||
|
nameServersTree.Child("IPs: " + fmt.Sprintf("%v", record.NameServers.IPs))
|
||||||
|
nameServersTree.Child("Raw Text: " + record.NameServers.RawText)
|
||||||
|
|
||||||
|
// Root Registry Data Tree
|
||||||
|
registryDataTree := tree.Root("Registry Data")
|
||||||
|
registryDataTree.Child("Audit: " + fmt.Sprintf("%v", record.RegistryData.Audit))
|
||||||
|
registryDataTree.Child("Created Date: " + record.RegistryData.CreatedDate)
|
||||||
|
registryDataTree.Child("Created Date Normalized: " + record.RegistryData.CreatedDateNormalized)
|
||||||
|
registryDataTree.Child("Domain Name: " + record.RegistryData.DomainName)
|
||||||
|
registryDataTree.Child("Expires Date: " + record.RegistryData.ExpiresDate)
|
||||||
|
registryDataTree.Child("Expires Date Normalized: " + record.RegistryData.ExpiresDateNormalized)
|
||||||
|
registryDataTree.Child("Footer: " + record.RegistryData.Footer)
|
||||||
|
registryDataTree.Child("Header: " + record.RegistryData.Header)
|
||||||
|
|
||||||
|
// Registry Data Name Servers Tree
|
||||||
|
registryNameServersTree := tree.Root("Name Servers")
|
||||||
|
registryNameServersTree.Child("Host Names: " + fmt.Sprintf("%v", record.RegistryData.NameServers.HostNames))
|
||||||
|
registryNameServersTree.Child("IPs: " + fmt.Sprintf("%v", record.RegistryData.NameServers.IPs))
|
||||||
|
registryNameServersTree.Child("Raw Text: " + record.RegistryData.NameServers.RawText)
|
||||||
|
registryDataTree.Child(registryNameServersTree)
|
||||||
|
|
||||||
|
// Root Registry Data Tree
|
||||||
|
registryDataTree.Child("Parse Code: " + fmt.Sprintf("%d", record.RegistryData.ParseCode))
|
||||||
|
registryDataTree.Child("Raw Text: " + record.RegistryData.RawText)
|
||||||
|
registryDataTree.Child("Registrar IANA ID: " + record.RegistryData.RegistrarIANAID)
|
||||||
|
registryDataTree.Child("Registrar Name: " + record.RegistryData.RegistrarName)
|
||||||
|
registryDataTree.Child("Status: " + record.RegistryData.Status)
|
||||||
|
registryDataTree.Child("Stripped Text: " + record.RegistryData.StrippedText)
|
||||||
|
registryDataTree.Child("Updated Date: " + record.RegistryData.UpdatedDate)
|
||||||
|
registryDataTree.Child("Updated Date Normalized: " + record.RegistryData.UpdatedDateNormalized)
|
||||||
|
registryDataTree.Child("Whois Server: " + record.RegistryData.WhoisServer)
|
||||||
|
|
||||||
|
// Root Contract Tree
|
||||||
|
technicalContactTree := tree.Root("Technical Contact")
|
||||||
|
technicalContactTree.Child("City: " + record.TechnicalContact.City)
|
||||||
|
technicalContactTree.Child("Country: " + record.TechnicalContact.Country)
|
||||||
|
technicalContactTree.Child("Country Code: " + record.TechnicalContact.CountryCode)
|
||||||
|
technicalContactTree.Child("Name: " + record.TechnicalContact.Name)
|
||||||
|
technicalContactTree.Child("Organization: " + record.TechnicalContact.Organization)
|
||||||
|
technicalContactTree.Child("Postal Code: " + record.TechnicalContact.PostalCode)
|
||||||
|
technicalContactTree.Child("Raw Text: " + record.TechnicalContact.RawText)
|
||||||
|
technicalContactTree.Child("State: " + record.TechnicalContact.State)
|
||||||
|
technicalContactTree.Child("Street 1: " + record.TechnicalContact.Street1)
|
||||||
|
technicalContactTree.Child("Telephone: " + record.TechnicalContact.Telephone)
|
||||||
|
|
||||||
|
// Root Tree Children
|
||||||
|
rootTree.Child("Contact Email: " + record.ContactEmail)
|
||||||
|
rootTree.Child("Created Date: " + record.CreatedDate)
|
||||||
|
rootTree.Child("Created Date Normalized: " + record.CreatedDateNormalized)
|
||||||
|
rootTree.Child("Domain Name: " + record.DomainName)
|
||||||
|
rootTree.Child("Domain Name Ext: " + record.DomainNameExt)
|
||||||
|
rootTree.Child("Estimated Domain Age: " + fmt.Sprintf("%d", record.EstimatedDomainAge))
|
||||||
|
rootTree.Child("Expires Date: " + record.ExpiresDate)
|
||||||
|
rootTree.Child("Expires Date Normalized: " + record.ExpiresDateNormalized)
|
||||||
|
rootTree.Child("Footer: " + record.Footer)
|
||||||
|
rootTree.Child("Header: " + record.Header)
|
||||||
|
rootTree.Child(nameServersTree)
|
||||||
|
rootTree.Child("Parse Code: " + fmt.Sprintf("%d", record.ParseCode))
|
||||||
|
rootTree.Child("Raw Text: " + record.RawText)
|
||||||
|
rootTree.Child("Registrant: " + fmt.Sprintf("%v", record.Registrant))
|
||||||
|
rootTree.Child("Registrar IANA ID: " + record.RegistrarIANAID)
|
||||||
|
rootTree.Child("Registrar Name: " + record.RegistrarName)
|
||||||
|
rootTree.Child(registryDataTree)
|
||||||
|
rootTree.Child("Status: " + record.Status)
|
||||||
|
rootTree.Child("Stripped Text: " + record.StrippedText)
|
||||||
|
rootTree.Child(technicalContactTree)
|
||||||
|
rootTree.Child("Updated Date: " + record.UpdatedDate)
|
||||||
|
rootTree.Child("Updated Date Normalized: " + record.UpdatedDateNormalized)
|
||||||
|
|
||||||
|
// Styles
|
||||||
|
rootTree.Enumerator(tree.RoundedEnumerator)
|
||||||
|
rootTree.EnumeratorStyle(enumeratorStyle)
|
||||||
|
rootTree.RootStyle(rootStyle)
|
||||||
|
rootTree.ItemStyle(itemStyle)
|
||||||
|
|
||||||
|
// Print Tree
|
||||||
|
fmt.Println(rootTree)
|
||||||
|
}
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
package query
|
|
||||||
|
|
||||||
import (
|
|
||||||
"dehasher/internal/export"
|
|
||||||
"dehasher/internal/sqlite"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Dehasher is a struct for querying the Dehashed API
|
|
||||||
type Dehasher struct {
|
|
||||||
options sqlite.QueryOptions
|
|
||||||
nextPage int
|
|
||||||
request *DehashedSearchRequest
|
|
||||||
client *DehashedClientV2
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDehasher creates a new Dehasher
|
|
||||||
func NewDehasher(options *sqlite.QueryOptions) *Dehasher {
|
|
||||||
dh := &Dehasher{
|
|
||||||
options: *options,
|
|
||||||
nextPage: options.StartingPage + 1,
|
|
||||||
}
|
|
||||||
dh.setQueries()
|
|
||||||
dh.request = NewDehashedSearchRequest(dh.options.StartingPage, dh.options.MaxRecords, dh.options.WildcardMatch, dh.options.RegexMatch, false)
|
|
||||||
dh.buildRequest()
|
|
||||||
return dh
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetClientCredentials sets the client credentials for the dehasher
|
|
||||||
func (dh *Dehasher) SetClientCredentials(key string) {
|
|
||||||
dh.client = NewDehashedClientV2(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dh *Dehasher) getNextPage() int {
|
|
||||||
nextPage := dh.nextPage
|
|
||||||
dh.nextPage += 1
|
|
||||||
return nextPage
|
|
||||||
}
|
|
||||||
|
|
||||||
// setQueries sets the number of queries to make based on the number of records and requests
|
|
||||||
func (dh *Dehasher) setQueries() {
|
|
||||||
var numQueries int
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case dh.options.MaxRequests == 0:
|
|
||||||
zap.L().Error("max requests cannot be zero")
|
|
||||||
fmt.Println("[!] Max Requests cannot be zero")
|
|
||||||
os.Exit(1)
|
|
||||||
case dh.options.MaxRecords <= 10000 || dh.options.MaxRequests == 1:
|
|
||||||
numQueries = 1
|
|
||||||
if dh.options.MaxRecords > 10000 {
|
|
||||||
dh.options.MaxRecords = 10000
|
|
||||||
}
|
|
||||||
zap.L().Info("max requests set to 1", zap.Int("max_records", dh.options.MaxRecords))
|
|
||||||
case dh.options.MaxRequests < 0 && dh.options.MaxRecords > 20000:
|
|
||||||
numQueries = 3
|
|
||||||
dh.options.MaxRecords = 10000
|
|
||||||
zap.L().Info("max requests set to 3", zap.Int("max_records", dh.options.MaxRecords))
|
|
||||||
case dh.options.MaxRequests < 0 && dh.options.MaxRecords > 10000:
|
|
||||||
numQueries = 2
|
|
||||||
dh.options.MaxRecords = 10000
|
|
||||||
zap.L().Info("max requests set to 2", zap.Int("max_records", dh.options.MaxRecords))
|
|
||||||
case dh.options.MaxRecords < 0 && dh.options.MaxRecords < 10000:
|
|
||||||
numQueries = 1
|
|
||||||
zap.L().Info("max requests set to 1", zap.Int("max_records", dh.options.MaxRecords))
|
|
||||||
case dh.options.MaxRequests == 2 && dh.options.MaxRecords > 20000:
|
|
||||||
numQueries = 2
|
|
||||||
dh.options.MaxRecords = 10000
|
|
||||||
zap.L().Info("max requests set to 2", zap.Int("max_records", dh.options.MaxRecords))
|
|
||||||
case dh.options.MaxRequests == 2 && dh.options.MaxRecords <= 10000:
|
|
||||||
numQueries = 1
|
|
||||||
zap.L().Info("max requests set to 1", zap.Int("max_records", dh.options.MaxRecords))
|
|
||||||
default:
|
|
||||||
numQueries = 3
|
|
||||||
dh.options.MaxRecords = 10000
|
|
||||||
zap.L().Info("max requests set to 3", zap.Int("max_records", dh.options.MaxRecords))
|
|
||||||
}
|
|
||||||
|
|
||||||
dh.options.MaxRequests = numQueries
|
|
||||||
fmt.Printf("Making %d Requests for %d Records (%d Total)\n", dh.options.MaxRequests, dh.options.MaxRecords, dh.options.MaxRequests*dh.options.MaxRecords)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start starts the querying process
|
|
||||||
func (dh *Dehasher) Start() {
|
|
||||||
fmt.Printf("[*] Querying Dehashed API...\n")
|
|
||||||
for i := 0; i < dh.options.MaxRequests; i++ {
|
|
||||||
fmt.Printf(" [*] Performing Request...\n")
|
|
||||||
count, err := dh.client.Search(*dh.request)
|
|
||||||
if err != nil {
|
|
||||||
// Check if it's a DehashError
|
|
||||||
if dhErr, ok := err.(*DehashError); ok {
|
|
||||||
fmt.Printf(" [!] Dehashed API Error: %s (Code: %d)\n", dhErr.Message, dhErr.Code)
|
|
||||||
zap.L().Error("dehashed_api_error",
|
|
||||||
zap.String("message", dhErr.Message),
|
|
||||||
zap.Int("code", dhErr.Code),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
fmt.Printf(" [!] Error performing request: %v\n", err)
|
|
||||||
zap.L().Error("request_error",
|
|
||||||
zap.String("message", "failed to perform request"),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
os.Exit(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if count < dh.options.MaxRecords {
|
|
||||||
fmt.Printf(" [+] Retrieved %d records\n", count)
|
|
||||||
fmt.Printf(" [-] Not enough entries, ending queries\n")
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
fmt.Printf(" [+] Retrieved %d records\n", dh.options.MaxRecords)
|
|
||||||
}
|
|
||||||
|
|
||||||
dh.request.Page = dh.getNextPage()
|
|
||||||
}
|
|
||||||
|
|
||||||
dh.parseResults()
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildRequest constructs the query map
|
|
||||||
func (dh *Dehasher) buildRequest() {
|
|
||||||
if len(dh.options.UsernameQuery) > 0 {
|
|
||||||
dh.request.AddUsernameQuery(dh.options.UsernameQuery)
|
|
||||||
}
|
|
||||||
if len(dh.options.EmailQuery) > 0 {
|
|
||||||
dh.request.AddEmailQuery(dh.options.EmailQuery)
|
|
||||||
}
|
|
||||||
if len(dh.options.IpQuery) > 0 {
|
|
||||||
dh.request.AddIpAddressQuery(dh.options.IpQuery)
|
|
||||||
}
|
|
||||||
if len(dh.options.HashQuery) > 0 {
|
|
||||||
dh.request.AddHashedPasswordQuery(dh.options.HashQuery)
|
|
||||||
}
|
|
||||||
if len(dh.options.PassQuery) > 0 {
|
|
||||||
dh.request.AddPasswordQuery(dh.options.PassQuery)
|
|
||||||
}
|
|
||||||
if len(dh.options.NameQuery) > 0 {
|
|
||||||
dh.request.AddNameQuery(dh.options.NameQuery)
|
|
||||||
}
|
|
||||||
if len(dh.options.DomainQuery) > 0 {
|
|
||||||
dh.request.AddDomainQuery(dh.options.DomainQuery)
|
|
||||||
}
|
|
||||||
if len(dh.options.VinQuery) > 0 {
|
|
||||||
dh.request.AddVinQuery(dh.options.VinQuery)
|
|
||||||
}
|
|
||||||
if len(dh.options.LicensePlateQuery) > 0 {
|
|
||||||
dh.request.AddLicensePlateQuery(dh.options.LicensePlateQuery)
|
|
||||||
}
|
|
||||||
if len(dh.options.AddressQuery) > 0 {
|
|
||||||
dh.request.AddAddressQuery(dh.options.AddressQuery)
|
|
||||||
}
|
|
||||||
if len(dh.options.PhoneQuery) > 0 {
|
|
||||||
dh.request.AddPhoneQuery(dh.options.PhoneQuery)
|
|
||||||
}
|
|
||||||
if len(dh.options.SocialQuery) > 0 {
|
|
||||||
dh.request.AddSocialQuery(dh.options.SocialQuery)
|
|
||||||
}
|
|
||||||
if len(dh.options.CryptoAddressQuery) > 0 {
|
|
||||||
dh.request.AddCryptoAddressQuery(dh.options.CryptoAddressQuery)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseResults parses the results and writes them to a file
|
|
||||||
func (dh *Dehasher) parseResults() {
|
|
||||||
var data []byte
|
|
||||||
|
|
||||||
zap.L().Info("extracting_credentials")
|
|
||||||
results := dh.client.GetResults()
|
|
||||||
creds := results.ExtractCredentials()
|
|
||||||
fmt.Printf("\n\t[+] Discovered %d Credentials", len(creds))
|
|
||||||
err := sqlite.StoreCreds(creds)
|
|
||||||
if err != nil {
|
|
||||||
zap.L().Error("store_creds",
|
|
||||||
zap.String("message", "failed to store creds"),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
zap.L().Info("creds_stored", zap.Int("count", len(creds)))
|
|
||||||
|
|
||||||
zap.L().Info("storing_results")
|
|
||||||
err = sqlite.StoreResults(results)
|
|
||||||
if err != nil {
|
|
||||||
zap.L().Error("store_results",
|
|
||||||
zap.String("message", "failed to store results"),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
zap.L().Info("results_stored", zap.Int("count", len(results.Results)))
|
|
||||||
|
|
||||||
if len(results.Results) > 0 {
|
|
||||||
fmt.Printf("\n\t[*] Writing entries to file: %s.%s", dh.options.OutputFile, dh.options.OutputFormat.String())
|
|
||||||
if !dh.options.CredsOnly {
|
|
||||||
err := export.WriteToFile(results, dh.options.OutputFile, dh.options.OutputFormat)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("\n[!] Error Writing to file: %v\n\tOutputting to terminal.", err)
|
|
||||||
data, err = json.MarshalIndent(results, "", " ")
|
|
||||||
fmt.Println(string(data))
|
|
||||||
os.Exit(0)
|
|
||||||
} else {
|
|
||||||
fmt.Println("\n\t\t[*] Success\n")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
creds := results.ExtractCredentials()
|
|
||||||
err := export.WriteCredsToFile(creds, dh.options.OutputFile, dh.options.OutputFormat)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("\n[!] Error Writing to file: %v\n\tOutputting to terminal.", err)
|
|
||||||
data, err = json.MarshalIndent(creds, "", " ")
|
|
||||||
fmt.Println(string(data))
|
|
||||||
os.Exit(0)
|
|
||||||
} else {
|
|
||||||
fmt.Println("\n\t\t[*] Success\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,419 +0,0 @@
|
|||||||
package sqlite
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// QueryResults queries the database for results based on the provided options
|
|
||||||
func QueryResults(options *DBOptions) ([]Result, error) {
|
|
||||||
db := GetDB()
|
|
||||||
var results []Result
|
|
||||||
query := db.Model(&Result{})
|
|
||||||
|
|
||||||
// Apply filters based on the provided options
|
|
||||||
query = applyFilters(query, options)
|
|
||||||
|
|
||||||
// Apply limit
|
|
||||||
if options.Limit > 0 {
|
|
||||||
query = query.Limit(options.Limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute the query
|
|
||||||
if err := query.Find(&results).Error; err != nil {
|
|
||||||
zap.L().Error("query_results",
|
|
||||||
zap.String("message", "failed to query results"),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return nil, fmt.Errorf("failed to query results: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// applyFilters applies filters to the query based on the provided options
|
|
||||||
func applyFilters(query *gorm.DB, options *DBOptions) *gorm.DB {
|
|
||||||
// Helper function to apply filter based on exact match setting
|
|
||||||
applyFilter := func(field, value string) *gorm.DB {
|
|
||||||
if value == "" {
|
|
||||||
return query
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.ExactMatch {
|
|
||||||
return query.Where(field+" = ?", value)
|
|
||||||
} else {
|
|
||||||
return query.Where(field+" LIKE ?", "%"+value+"%")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply filters for each field if provided
|
|
||||||
if options.Email != "" {
|
|
||||||
query = applyFilter("email", options.Email)
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.Username != "" {
|
|
||||||
query = applyFilter("username", options.Username)
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.IPAddress != "" {
|
|
||||||
query = applyFilter("ip_address", options.IPAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.Password != "" {
|
|
||||||
query = applyFilter("password", options.Password)
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.HashedPassword != "" {
|
|
||||||
query = applyFilter("hashed_password", options.HashedPassword)
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.Name != "" {
|
|
||||||
query = applyFilter("name", options.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.Vin != "" {
|
|
||||||
query = applyFilter("vin", options.Vin)
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.LicensePlate != "" {
|
|
||||||
query = applyFilter("license_plate", options.LicensePlate)
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.Address != "" {
|
|
||||||
query = applyFilter("address", options.Address)
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.Phone != "" {
|
|
||||||
query = applyFilter("phone", options.Phone)
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.Social != "" {
|
|
||||||
query = applyFilter("social", options.Social)
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.CryptoCurrencyAddress != "" {
|
|
||||||
query = applyFilter("cryptocurrency_address", options.CryptoCurrencyAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.Domain != "" {
|
|
||||||
query = applyFilter("url", options.Domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply non-empty field filters
|
|
||||||
for _, field := range options.NonEmptyFields {
|
|
||||||
switch field {
|
|
||||||
case "username":
|
|
||||||
query = query.Where("JSON_ARRAY_LENGTH(username) > 0")
|
|
||||||
case "email":
|
|
||||||
query = query.Where("JSON_ARRAY_LENGTH(email) > 0")
|
|
||||||
case "ip_address", "ipaddress", "ip":
|
|
||||||
query = query.Where("JSON_ARRAY_LENGTH(ip_address) > 0")
|
|
||||||
case "password":
|
|
||||||
query = query.Where("JSON_ARRAY_LENGTH(password) > 0")
|
|
||||||
case "hashed_password", "hash":
|
|
||||||
query = query.Where("JSON_ARRAY_LENGTH(hashed_password) > 0")
|
|
||||||
case "name":
|
|
||||||
query = query.Where("JSON_ARRAY_LENGTH(name) > 0")
|
|
||||||
case "vin":
|
|
||||||
query = query.Where("JSON_ARRAY_LENGTH(vin) > 0")
|
|
||||||
case "license_plate", "license":
|
|
||||||
query = query.Where("JSON_ARRAY_LENGTH(license_plate) > 0")
|
|
||||||
case "address":
|
|
||||||
query = query.Where("JSON_ARRAY_LENGTH(address) > 0")
|
|
||||||
case "phone":
|
|
||||||
query = query.Where("JSON_ARRAY_LENGTH(phone) > 0")
|
|
||||||
case "social":
|
|
||||||
query = query.Where("JSON_ARRAY_LENGTH(social) > 0")
|
|
||||||
case "cryptocurrency_address", "crypto":
|
|
||||||
query = query.Where("JSON_ARRAY_LENGTH(cryptocurrency_address) > 0")
|
|
||||||
case "url", "domain":
|
|
||||||
query = query.Where("JSON_ARRAY_LENGTH(url) > 0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return query
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetResultsCount returns the count of results matching the provided options
|
|
||||||
func GetResultsCount(options *DBOptions) (int64, error) {
|
|
||||||
db := GetDB()
|
|
||||||
var count int64
|
|
||||||
query := db.Model(&Result{})
|
|
||||||
|
|
||||||
// Apply filters based on the provided options
|
|
||||||
query = applyFilters(query, options)
|
|
||||||
|
|
||||||
// Count the results
|
|
||||||
if err := query.Count(&count).Error; err != nil {
|
|
||||||
zap.L().Error("get_results_count",
|
|
||||||
zap.String("message", "failed to count results"),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return 0, fmt.Errorf("failed to count results: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return count, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryRuns queries the database for previous query runs (QueryOptions) based on the provided filters
|
|
||||||
func QueryRuns(limit, lastXRuns int, startDate, endDate time.Time, containsQuery string) ([]QueryOptions, error) {
|
|
||||||
db := GetDB()
|
|
||||||
var runs []QueryOptions
|
|
||||||
query := db.Model(&QueryOptions{})
|
|
||||||
|
|
||||||
// Apply date range filter if provided
|
|
||||||
if lastXRuns > 0 {
|
|
||||||
query = query.Order("created_at DESC").Limit(lastXRuns)
|
|
||||||
} else if !startDate.IsZero() && !endDate.IsZero() {
|
|
||||||
query = query.Where("created_at BETWEEN ? AND ?", startDate, endDate)
|
|
||||||
} else if !startDate.IsZero() {
|
|
||||||
query = query.Where("created_at >= ?", startDate)
|
|
||||||
} else if !endDate.IsZero() {
|
|
||||||
query = query.Where("created_at <= ?", endDate)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply query filter if provided
|
|
||||||
if containsQuery != "" {
|
|
||||||
// Search in all query fields
|
|
||||||
query = query.Where(
|
|
||||||
"username_query LIKE ? OR "+
|
|
||||||
"email_query LIKE ? OR "+
|
|
||||||
"ip_query LIKE ? OR "+
|
|
||||||
"pass_query LIKE ? OR "+
|
|
||||||
"hash_query LIKE ? OR "+
|
|
||||||
"name_query LIKE ? OR "+
|
|
||||||
"domain_query LIKE ? OR "+
|
|
||||||
"vin_query LIKE ? OR "+
|
|
||||||
"license_plate_query LIKE ? OR "+
|
|
||||||
"address_query LIKE ? OR "+
|
|
||||||
"phone_query LIKE ? OR "+
|
|
||||||
"social_query LIKE ? OR "+
|
|
||||||
"crypto_address_query LIKE ?",
|
|
||||||
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
|
|
||||||
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
|
|
||||||
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
|
|
||||||
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
|
|
||||||
"%"+containsQuery+"%",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply limit
|
|
||||||
if limit > 0 {
|
|
||||||
query = query.Limit(limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Order by most recent first
|
|
||||||
query = query.Order("created_at DESC")
|
|
||||||
|
|
||||||
// Execute the query
|
|
||||||
if err := query.Find(&runs).Error; err != nil {
|
|
||||||
zap.L().Error("query_runs",
|
|
||||||
zap.String("message", "failed to query runs"),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return nil, fmt.Errorf("failed to query runs: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return runs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRunsCount returns the count of runs matching the provided filters
|
|
||||||
func GetRunsCount(lastXRuns int, startDate, endDate time.Time, containsQuery string) (int64, error) {
|
|
||||||
db := GetDB()
|
|
||||||
var count int64
|
|
||||||
query := db.Model(&QueryOptions{})
|
|
||||||
|
|
||||||
// Apply date range filter if provided
|
|
||||||
if lastXRuns > 0 {
|
|
||||||
query = query.Order("created_at DESC").Limit(lastXRuns)
|
|
||||||
} else if !startDate.IsZero() && !endDate.IsZero() {
|
|
||||||
query = query.Where("created_at BETWEEN ? AND ?", startDate, endDate)
|
|
||||||
} else if !startDate.IsZero() {
|
|
||||||
query = query.Where("created_at >= ?", startDate)
|
|
||||||
} else if !endDate.IsZero() {
|
|
||||||
query = query.Where("created_at <= ?", endDate)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply query filter if provided
|
|
||||||
if containsQuery != "" {
|
|
||||||
// Search in all query fields
|
|
||||||
query = query.Where(
|
|
||||||
"username_query LIKE ? OR "+
|
|
||||||
"email_query LIKE ? OR "+
|
|
||||||
"ip_query LIKE ? OR "+
|
|
||||||
"pass_query LIKE ? OR "+
|
|
||||||
"hash_query LIKE ? OR "+
|
|
||||||
"name_query LIKE ? OR "+
|
|
||||||
"domain_query LIKE ? OR "+
|
|
||||||
"vin_query LIKE ? OR "+
|
|
||||||
"license_plate_query LIKE ? OR "+
|
|
||||||
"address_query LIKE ? OR "+
|
|
||||||
"phone_query LIKE ? OR "+
|
|
||||||
"social_query LIKE ? OR "+
|
|
||||||
"crypto_address_query LIKE ?",
|
|
||||||
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
|
|
||||||
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
|
|
||||||
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
|
|
||||||
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
|
|
||||||
"%"+containsQuery+"%",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count the results
|
|
||||||
if err := query.Count(&count).Error; err != nil {
|
|
||||||
zap.L().Error("get_runs_count",
|
|
||||||
zap.String("message", "failed to count runs"),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return 0, fmt.Errorf("failed to count runs: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return count, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryCreds queries the database for credentials based on the provided filters
|
|
||||||
func QueryCreds(options *DBOptions) ([]Creds, error) {
|
|
||||||
db := GetDB()
|
|
||||||
var creds []Creds
|
|
||||||
query := db.Model(&Creds{})
|
|
||||||
|
|
||||||
// Apply filters based on the provided options
|
|
||||||
if options.Username != "" {
|
|
||||||
if options.ExactMatch {
|
|
||||||
query = query.Where("username = ?", options.Username)
|
|
||||||
} else {
|
|
||||||
query = query.Where("username LIKE ?", "%"+options.Username+"%")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.Email != "" {
|
|
||||||
if options.ExactMatch {
|
|
||||||
query = query.Where("email = ?", options.Email)
|
|
||||||
} else {
|
|
||||||
query = query.Where("email LIKE ?", "%"+options.Email+"%")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.Password != "" {
|
|
||||||
if options.ExactMatch {
|
|
||||||
query = query.Where("password = ?", options.Password)
|
|
||||||
} else {
|
|
||||||
query = query.Where("password LIKE ?", "%"+options.Password+"%")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply limit
|
|
||||||
if options.Limit > 0 {
|
|
||||||
query = query.Limit(options.Limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute the query
|
|
||||||
if err := query.Find(&creds).Error; err != nil {
|
|
||||||
zap.L().Error("query_creds",
|
|
||||||
zap.String("message", "failed to query credentials"),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return nil, fmt.Errorf("failed to query credentials: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return creds, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCredsCount returns the count of credentials matching the provided filters
|
|
||||||
func GetCredsCount(options *DBOptions) (int64, error) {
|
|
||||||
db := GetDB()
|
|
||||||
var count int64
|
|
||||||
query := db.Model(&Creds{})
|
|
||||||
|
|
||||||
// Apply filters based on the provided options
|
|
||||||
if options.Username != "" {
|
|
||||||
if options.ExactMatch {
|
|
||||||
query = query.Where("username = ?", options.Username)
|
|
||||||
} else {
|
|
||||||
query = query.Where("username LIKE ?", "%"+options.Username+"%")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.Email != "" {
|
|
||||||
if options.ExactMatch {
|
|
||||||
query = query.Where("email = ?", options.Email)
|
|
||||||
} else {
|
|
||||||
query = query.Where("email LIKE ?", "%"+options.Email+"%")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.Password != "" {
|
|
||||||
if options.ExactMatch {
|
|
||||||
query = query.Where("password = ?", options.Password)
|
|
||||||
} else {
|
|
||||||
query = query.Where("password LIKE ?", "%"+options.Password+"%")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count the results
|
|
||||||
if err := query.Count(&count).Error; err != nil {
|
|
||||||
zap.L().Error("get_creds_count",
|
|
||||||
zap.String("message", "failed to count credentials"),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return 0, fmt.Errorf("failed to count credentials: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return count, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExecuteRawQuery executes a raw SQL query and returns the results as a slice of maps
|
|
||||||
func ExecuteRawQuery(query string) ([]map[string]interface{}, error) {
|
|
||||||
db := GetDB()
|
|
||||||
rows, err := db.Raw(query).Rows()
|
|
||||||
if err != nil {
|
|
||||||
zap.L().Error("raw_query",
|
|
||||||
zap.String("message", "failed to execute raw query"),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return nil, fmt.Errorf("failed to execute raw query: %w", err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
columns, err := rows.Columns()
|
|
||||||
if err != nil {
|
|
||||||
zap.L().Error("raw_query",
|
|
||||||
zap.String("message", "failed to get columns from raw query"),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return nil, fmt.Errorf("failed to get columns from raw query: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var results []map[string]interface{}
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
// Create a slice of interface{} to hold the values
|
|
||||||
values := make([]interface{}, len(columns))
|
|
||||||
pointers := make([]interface{}, len(columns))
|
|
||||||
for i := range values {
|
|
||||||
pointers[i] = &values[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan the result into the pointers
|
|
||||||
if err := rows.Scan(pointers...); err != nil {
|
|
||||||
zap.L().Error("raw_query",
|
|
||||||
zap.String("message", "failed to scan row from raw query"),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return nil, fmt.Errorf("failed to scan row from raw query: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a map for this row
|
|
||||||
rowMap := make(map[string]interface{})
|
|
||||||
for i, col := range columns {
|
|
||||||
val := values[i]
|
|
||||||
rowMap[col] = val
|
|
||||||
}
|
|
||||||
|
|
||||||
results = append(results, rowMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
+33
-2
@@ -51,7 +51,7 @@ func InitDB(dbPath string) (*gorm.DB, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Auto migrate your models
|
// Auto migrate your models
|
||||||
err = db.AutoMigrate(&Result{}, &Creds{}, QueryOptions{}, Creds{}, WhoisRecord{}, SubdomainRecord{}, HistoryRecord{})
|
err = db.AutoMigrate(&Result{}, &Creds{}, &QueryOptions{}, &Creds{}, &WhoisRecord{}, &SubdomainRecord{}, &HistoryRecord{}, &LookupResult{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Error("Failed to migrate database", zap.Error(err))
|
zap.L().Error("Failed to migrate database", zap.Error(err))
|
||||||
return nil, fmt.Errorf("failed to migrate database: %w", err)
|
return nil, fmt.Errorf("failed to migrate database: %w", err)
|
||||||
@@ -163,7 +163,7 @@ func StoreWhoisRecord(whoisRecord WhoisRecord) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func StoreSubdomainRecord(subdomainRecords []SubdomainRecord) error {
|
func StoreSubdomainRecords(subdomainRecords []SubdomainRecord) error {
|
||||||
if len(subdomainRecords) == 0 {
|
if len(subdomainRecords) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -224,3 +224,34 @@ func StoreHistoryRecord(historyRecords []HistoryRecord) error {
|
|||||||
|
|
||||||
return lastErr
|
return lastErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StoreIPLookup(ipLookup []LookupResult) error {
|
||||||
|
if len(ipLookup) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
zap.L().Info("Storing IP lookup records", zap.Int("count", len(ipLookup)))
|
||||||
|
db := GetDB()
|
||||||
|
|
||||||
|
// Use batch insert with conflict handling
|
||||||
|
const batchSize = 100
|
||||||
|
var lastErr error
|
||||||
|
|
||||||
|
for i := 0; i < len(ipLookup); i += batchSize {
|
||||||
|
end := i + batchSize
|
||||||
|
if end > len(ipLookup) {
|
||||||
|
end = len(ipLookup)
|
||||||
|
}
|
||||||
|
|
||||||
|
batch := ipLookup[i:end]
|
||||||
|
// Use Clauses with OnConflict DoNothing to skip conflicts
|
||||||
|
err := db.Clauses(clause.OnConflict{DoNothing: true}).CreateInBatches(&batch, batchSize).Error
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Warn("Error storing some IP lookup records", zap.Error(err))
|
||||||
|
lastErr = err
|
||||||
|
// Continue with next batch despite error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastErr
|
||||||
|
}
|
||||||
|
|||||||
@@ -67,13 +67,14 @@ type QueryOptions struct {
|
|||||||
CryptoAddressQuery string `json:"crypto_address_query"`
|
CryptoAddressQuery string `json:"crypto_address_query"`
|
||||||
PrintBalance bool `json:"print_balance"`
|
PrintBalance bool `json:"print_balance"`
|
||||||
CredsOnly bool `json:"creds_only"`
|
CredsOnly bool `json:"creds_only"`
|
||||||
|
Debug bool `json:"debug"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (QueryOptions) TableName() string {
|
func (QueryOptions) TableName() string {
|
||||||
return "query_options"
|
return "query_options"
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewQueryOptions(maxRecords, maxRequests, startingPage int, outputFormat, outputFile, usernameQuery, emailQuery, ipQuery, passQuery, hashQuery, nameQuery, domainQuery, vinQuery, licensePlateQuery, addressQuery, phoneQuery, socialQuery, cryptoAddressQuery string, regexMatch, wildcardMatch, printBalance, credsOnly bool) *QueryOptions {
|
func NewQueryOptions(maxRecords, maxRequests, startingPage int, outputFormat, outputFile, usernameQuery, emailQuery, ipQuery, passQuery, hashQuery, nameQuery, domainQuery, vinQuery, licensePlateQuery, addressQuery, phoneQuery, socialQuery, cryptoAddressQuery string, regexMatch, wildcardMatch, printBalance, credsOnly, debug bool) *QueryOptions {
|
||||||
return &QueryOptions{
|
return &QueryOptions{
|
||||||
MaxRecords: maxRecords,
|
MaxRecords: maxRecords,
|
||||||
MaxRequests: maxRequests,
|
MaxRequests: maxRequests,
|
||||||
@@ -97,14 +98,15 @@ func NewQueryOptions(maxRecords, maxRequests, startingPage int, outputFormat, ou
|
|||||||
PhoneQuery: phoneQuery,
|
PhoneQuery: phoneQuery,
|
||||||
SocialQuery: socialQuery,
|
SocialQuery: socialQuery,
|
||||||
CryptoAddressQuery: cryptoAddressQuery,
|
CryptoAddressQuery: cryptoAddressQuery,
|
||||||
|
Debug: debug,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Creds struct {
|
type Creds struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
Email string `json:"email" yaml:"email" xml:"email"`
|
Email string `json:"email" yaml:"email" xml:"email" gorm:"uniqueIndex:idx_email_username_password"`
|
||||||
Username string `json:"username" yaml:"username" xml:"username"`
|
Username string `json:"username" yaml:"username" xml:"username" gorm:"uniqueIndex:idx_email_username_password"`
|
||||||
Password string `json:"password" yaml:"password" xml:"password"`
|
Password string `json:"password" yaml:"password" xml:"password" gorm:"uniqueIndex:idx_email_username_password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Creds) TableName() string {
|
func (Creds) TableName() string {
|
||||||
|
|||||||
+346
-4
@@ -1,6 +1,10 @@
|
|||||||
package sqlite
|
package sqlite
|
||||||
|
|
||||||
import "gorm.io/gorm"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
type WhoIsLookupResult struct {
|
type WhoIsLookupResult struct {
|
||||||
RemainingCredits int `json:"remaining_credits"`
|
RemainingCredits int `json:"remaining_credits"`
|
||||||
@@ -17,7 +21,7 @@ type WhoisRecord struct {
|
|||||||
ContactEmail string `json:"contactEmail"`
|
ContactEmail string `json:"contactEmail"`
|
||||||
CreatedDate string `json:"createdDate"`
|
CreatedDate string `json:"createdDate"`
|
||||||
CreatedDateNormalized string `json:"createdDateNormalized"`
|
CreatedDateNormalized string `json:"createdDateNormalized"`
|
||||||
DomainName string `json:"domainName"`
|
DomainName string `json:"domainName" gorm:"unique"`
|
||||||
DomainNameExt string `json:"domainNameExt"`
|
DomainNameExt string `json:"domainNameExt"`
|
||||||
EstimatedDomainAge int `json:"estimatedDomainAge"`
|
EstimatedDomainAge int `json:"estimatedDomainAge"`
|
||||||
ExpiresDate string `json:"expiresDate"`
|
ExpiresDate string `json:"expiresDate"`
|
||||||
@@ -38,6 +42,142 @@ type WhoisRecord struct {
|
|||||||
UpdatedDateNormalized string `json:"updatedDateNormalized"`
|
UpdatedDateNormalized string `json:"updatedDateNormalized"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w WhoisRecord) String() string {
|
||||||
|
var sb strings.Builder
|
||||||
|
|
||||||
|
// Main domain information
|
||||||
|
sb.WriteString(fmt.Sprintf("Domain Name: %s\n", w.DomainName))
|
||||||
|
sb.WriteString(fmt.Sprintf("Domain Name Ext: %s\n", w.DomainNameExt))
|
||||||
|
sb.WriteString(fmt.Sprintf("Registrar Name: %s\n", w.RegistrarName))
|
||||||
|
sb.WriteString(fmt.Sprintf("Registrar IANA ID: %s\n", w.RegistrarIANAID))
|
||||||
|
sb.WriteString(fmt.Sprintf("Contact Email: %s\n", w.ContactEmail))
|
||||||
|
sb.WriteString(fmt.Sprintf("Estimated Domain Age: %d days\n", w.EstimatedDomainAge))
|
||||||
|
|
||||||
|
// Dates
|
||||||
|
sb.WriteString(fmt.Sprintf("Created Date: %s (Normalized: %s)\n", w.CreatedDate, w.CreatedDateNormalized))
|
||||||
|
sb.WriteString(fmt.Sprintf("Updated Date: %s (Normalized: %s)\n", w.UpdatedDate, w.UpdatedDateNormalized))
|
||||||
|
sb.WriteString(fmt.Sprintf("Expires Date: %s (Normalized: %s)\n", w.ExpiresDate, w.ExpiresDateNormalized))
|
||||||
|
|
||||||
|
// Status
|
||||||
|
sb.WriteString(fmt.Sprintf("Status: %s\n", w.Status))
|
||||||
|
|
||||||
|
// Parse code
|
||||||
|
sb.WriteString(fmt.Sprintf("Parse Code: %d\n", w.ParseCode))
|
||||||
|
|
||||||
|
// Audit information
|
||||||
|
sb.WriteString("\nAudit Information:\n")
|
||||||
|
sb.WriteString(fmt.Sprintf(" Created Date: %s\n", w.Audit.CreatedDate))
|
||||||
|
sb.WriteString(fmt.Sprintf(" Updated Date: %s\n", w.Audit.UpdatedDate))
|
||||||
|
|
||||||
|
// Name servers
|
||||||
|
sb.WriteString("\nName Servers:\n")
|
||||||
|
if len(w.NameServers.HostNames) > 0 {
|
||||||
|
for i, ns := range w.NameServers.HostNames {
|
||||||
|
ip := ""
|
||||||
|
if i < len(w.NameServers.IPs) {
|
||||||
|
ip = fmt.Sprintf(" (%s)", w.NameServers.IPs[i])
|
||||||
|
}
|
||||||
|
sb.WriteString(fmt.Sprintf(" %d. %s%s\n", i+1, ns, ip))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sb.WriteString(" None listed\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.NameServers.RawText != "" {
|
||||||
|
sb.WriteString(fmt.Sprintf(" Raw Text: %s\n", w.NameServers.RawText))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contact information
|
||||||
|
sb.WriteString("\nRegistrant Contact:\n")
|
||||||
|
formatWhoisContact(&sb, w.Registrant, " ")
|
||||||
|
|
||||||
|
sb.WriteString("\nTechnical Contact:\n")
|
||||||
|
formatWhoisContact(&sb, w.TechnicalContact, " ")
|
||||||
|
|
||||||
|
// Registry Data
|
||||||
|
sb.WriteString("\nRegistry Data:\n")
|
||||||
|
if w.RegistryData.DomainName != "" {
|
||||||
|
sb.WriteString(fmt.Sprintf(" Domain Name: %s\n", w.RegistryData.DomainName))
|
||||||
|
sb.WriteString(fmt.Sprintf(" Registrar Name: %s\n", w.RegistryData.RegistrarName))
|
||||||
|
sb.WriteString(fmt.Sprintf(" Registrar IANA ID: %s\n", w.RegistryData.RegistrarIANAID))
|
||||||
|
sb.WriteString(fmt.Sprintf(" Whois Server: %s\n", w.RegistryData.WhoisServer))
|
||||||
|
sb.WriteString(fmt.Sprintf(" Status: %s\n", w.RegistryData.Status))
|
||||||
|
|
||||||
|
// Registry dates
|
||||||
|
sb.WriteString(fmt.Sprintf(" Created Date: %s (Normalized: %s)\n",
|
||||||
|
w.RegistryData.CreatedDate, w.RegistryData.CreatedDateNormalized))
|
||||||
|
sb.WriteString(fmt.Sprintf(" Updated Date: %s (Normalized: %s)\n",
|
||||||
|
w.RegistryData.UpdatedDate, w.RegistryData.UpdatedDateNormalized))
|
||||||
|
sb.WriteString(fmt.Sprintf(" Expires Date: %s (Normalized: %s)\n",
|
||||||
|
w.RegistryData.ExpiresDate, w.RegistryData.ExpiresDateNormalized))
|
||||||
|
|
||||||
|
// Registry nameservers
|
||||||
|
sb.WriteString(" Name Servers:\n")
|
||||||
|
if len(w.RegistryData.NameServers.HostNames) > 0 {
|
||||||
|
for i, ns := range w.RegistryData.NameServers.HostNames {
|
||||||
|
ip := ""
|
||||||
|
if i < len(w.RegistryData.NameServers.IPs) {
|
||||||
|
ip = fmt.Sprintf(" (%s)", w.RegistryData.NameServers.IPs[i])
|
||||||
|
}
|
||||||
|
sb.WriteString(fmt.Sprintf(" %d. %s%s\n", i+1, ns, ip))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sb.WriteString(" None listed\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Registry audit
|
||||||
|
sb.WriteString(" Audit Information:\n")
|
||||||
|
sb.WriteString(fmt.Sprintf(" Created Date: %s\n", w.RegistryData.Audit.CreatedDate))
|
||||||
|
sb.WriteString(fmt.Sprintf(" Updated Date: %s\n", w.RegistryData.Audit.UpdatedDate))
|
||||||
|
} else {
|
||||||
|
sb.WriteString(" No registry data available\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header and footer
|
||||||
|
if w.Header != "" {
|
||||||
|
headerPreview := w.Header
|
||||||
|
if len(headerPreview) > 100 {
|
||||||
|
headerPreview = headerPreview[:100] + "... [truncated]"
|
||||||
|
}
|
||||||
|
sb.WriteString("\nHeader:\n")
|
||||||
|
sb.WriteString(headerPreview)
|
||||||
|
sb.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.Footer != "" {
|
||||||
|
footerPreview := w.Footer
|
||||||
|
if len(footerPreview) > 100 {
|
||||||
|
footerPreview = footerPreview[:100] + "... [truncated]"
|
||||||
|
}
|
||||||
|
sb.WriteString("\nFooter:\n")
|
||||||
|
sb.WriteString(footerPreview)
|
||||||
|
sb.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raw text (truncated if too long)
|
||||||
|
if w.RawText != "" {
|
||||||
|
rawTextPreview := w.RawText
|
||||||
|
if len(rawTextPreview) > 500 {
|
||||||
|
rawTextPreview = rawTextPreview[:500] + "... [truncated]"
|
||||||
|
}
|
||||||
|
sb.WriteString("\nRaw Text:\n")
|
||||||
|
sb.WriteString(rawTextPreview)
|
||||||
|
sb.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.StrippedText != "" {
|
||||||
|
strippedTextPreview := w.StrippedText
|
||||||
|
if len(strippedTextPreview) > 500 {
|
||||||
|
strippedTextPreview = strippedTextPreview[:500] + "... [truncated]"
|
||||||
|
}
|
||||||
|
sb.WriteString("\nStripped Text:\n")
|
||||||
|
sb.WriteString(strippedTextPreview)
|
||||||
|
sb.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
func (WhoisRecord) TableName() string {
|
func (WhoisRecord) TableName() string {
|
||||||
return "whois"
|
return "whois"
|
||||||
}
|
}
|
||||||
@@ -104,7 +244,7 @@ type ScanResult struct {
|
|||||||
|
|
||||||
type SubdomainRecord struct {
|
type SubdomainRecord struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
Domain string `json:"domain"`
|
Domain string `json:"domain" gorm:"unique"`
|
||||||
FirstSeen int64 `json:"firstSeen"`
|
FirstSeen int64 `json:"firstSeen"`
|
||||||
LastSeen int64 `json:"lastSeen"`
|
LastSeen int64 `json:"lastSeen"`
|
||||||
}
|
}
|
||||||
@@ -131,7 +271,7 @@ type HistoryRecord struct {
|
|||||||
CleanText string `json:"cleanText"`
|
CleanText string `json:"cleanText"`
|
||||||
CreatedDateISO8601 string `json:"createdDateISO8601"`
|
CreatedDateISO8601 string `json:"createdDateISO8601"`
|
||||||
CreatedDateRaw string `json:"createdDateRaw"`
|
CreatedDateRaw string `json:"createdDateRaw"`
|
||||||
DomainName string `json:"domainName"`
|
DomainName string `json:"domainName" gorm:"unique"`
|
||||||
DomainType string `json:"domainType"`
|
DomainType string `json:"domainType"`
|
||||||
ExpiresDateISO8601 string `json:"expiresDateISO8601"`
|
ExpiresDateISO8601 string `json:"expiresDateISO8601"`
|
||||||
ExpiresDateRaw string `json:"expiresDateRaw"`
|
ExpiresDateRaw string `json:"expiresDateRaw"`
|
||||||
@@ -147,6 +287,131 @@ type HistoryRecord struct {
|
|||||||
ZoneContact ContactInfo `json:"zoneContact" gorm:"serializer:json"`
|
ZoneContact ContactInfo `json:"zoneContact" gorm:"serializer:json"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h HistoryRecord) String() string {
|
||||||
|
var sb strings.Builder
|
||||||
|
|
||||||
|
// Main domain information
|
||||||
|
sb.WriteString(fmt.Sprintf("Domain Name: %s\n", h.DomainName))
|
||||||
|
sb.WriteString(fmt.Sprintf("Domain Type: %s\n", h.DomainType))
|
||||||
|
sb.WriteString(fmt.Sprintf("Registrar Name: %s\n", h.RegistrarName))
|
||||||
|
sb.WriteString(fmt.Sprintf("Whois Server: %s\n", h.WhoisServer))
|
||||||
|
|
||||||
|
// Dates
|
||||||
|
sb.WriteString(fmt.Sprintf("Created Date: %s (Raw: %s)\n", h.CreatedDateISO8601, h.CreatedDateRaw))
|
||||||
|
sb.WriteString(fmt.Sprintf("Updated Date: %s (Raw: %s)\n", h.UpdatedDateISO8601, h.UpdatedDateRaw))
|
||||||
|
sb.WriteString(fmt.Sprintf("Expires Date: %s (Raw: %s)\n", h.ExpiresDateISO8601, h.ExpiresDateRaw))
|
||||||
|
|
||||||
|
// Status
|
||||||
|
sb.WriteString("Status: ")
|
||||||
|
if len(h.Status) > 0 {
|
||||||
|
sb.WriteString(strings.Join(h.Status, ", "))
|
||||||
|
} else {
|
||||||
|
sb.WriteString("N/A")
|
||||||
|
}
|
||||||
|
sb.WriteString("\n")
|
||||||
|
|
||||||
|
// Audit information
|
||||||
|
sb.WriteString("\nAudit Information:\n")
|
||||||
|
sb.WriteString(fmt.Sprintf(" Created Date: %s\n", h.Audit.CreatedDate))
|
||||||
|
sb.WriteString(fmt.Sprintf(" Updated Date: %s\n", h.Audit.UpdatedDate))
|
||||||
|
|
||||||
|
// Name servers
|
||||||
|
sb.WriteString("\nName Servers:\n")
|
||||||
|
if len(h.NameServers) > 0 {
|
||||||
|
for i, ns := range h.NameServers {
|
||||||
|
sb.WriteString(fmt.Sprintf(" %d. %s\n", i+1, ns))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sb.WriteString(" None listed\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contact information
|
||||||
|
sb.WriteString("\nRegistrant Contact:\n")
|
||||||
|
formatContact(&sb, h.RegistrantContact, " ")
|
||||||
|
|
||||||
|
sb.WriteString("\nAdministrative Contact:\n")
|
||||||
|
formatContact(&sb, h.AdministrativeContact, " ")
|
||||||
|
|
||||||
|
sb.WriteString("\nTechnical Contact:\n")
|
||||||
|
formatContact(&sb, h.TechnicalContact, " ")
|
||||||
|
|
||||||
|
sb.WriteString("\nBilling Contact:\n")
|
||||||
|
formatContact(&sb, h.BillingContact, " ")
|
||||||
|
|
||||||
|
sb.WriteString("\nZone Contact:\n")
|
||||||
|
formatContact(&sb, h.ZoneContact, " ")
|
||||||
|
|
||||||
|
// Raw text (truncated if too long)
|
||||||
|
if len(h.RawText) > 0 {
|
||||||
|
rawTextPreview := h.RawText
|
||||||
|
if len(rawTextPreview) > 500 {
|
||||||
|
rawTextPreview = rawTextPreview[:500] + "... [truncated]"
|
||||||
|
}
|
||||||
|
sb.WriteString("\nRaw Text:\n")
|
||||||
|
sb.WriteString(rawTextPreview)
|
||||||
|
sb.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(h.CleanText) > 0 {
|
||||||
|
cleanTextPreview := h.CleanText
|
||||||
|
if len(cleanTextPreview) > 500 {
|
||||||
|
cleanTextPreview = cleanTextPreview[:500] + "... [truncated]"
|
||||||
|
}
|
||||||
|
sb.WriteString("\nClean Text:\n")
|
||||||
|
sb.WriteString(cleanTextPreview)
|
||||||
|
sb.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to format contact information
|
||||||
|
func formatContact(sb *strings.Builder, contact ContactInfo, indent string) {
|
||||||
|
if contact.Name == "" && contact.Organization == "" && contact.Email == "" {
|
||||||
|
sb.WriteString(indent + "No contact information available\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if contact.Name != "" {
|
||||||
|
sb.WriteString(indent + "Name: " + contact.Name + "\n")
|
||||||
|
}
|
||||||
|
if contact.Organization != "" {
|
||||||
|
sb.WriteString(indent + "Organization: " + contact.Organization + "\n")
|
||||||
|
}
|
||||||
|
if contact.Email != "" {
|
||||||
|
sb.WriteString(indent + "Email: " + contact.Email + "\n")
|
||||||
|
}
|
||||||
|
if contact.Street != "" {
|
||||||
|
sb.WriteString(indent + "Street: " + contact.Street + "\n")
|
||||||
|
}
|
||||||
|
if contact.City != "" {
|
||||||
|
sb.WriteString(indent + "City: " + contact.City + "\n")
|
||||||
|
}
|
||||||
|
if contact.State != "" {
|
||||||
|
sb.WriteString(indent + "State: " + contact.State + "\n")
|
||||||
|
}
|
||||||
|
if contact.PostalCode != "" {
|
||||||
|
sb.WriteString(indent + "Postal Code: " + contact.PostalCode + "\n")
|
||||||
|
}
|
||||||
|
if contact.Country != "" {
|
||||||
|
sb.WriteString(indent + "Country: " + contact.Country + "\n")
|
||||||
|
}
|
||||||
|
if contact.Telephone != "" {
|
||||||
|
phone := contact.Telephone
|
||||||
|
if contact.TelephoneExt != "" {
|
||||||
|
phone += " ext. " + contact.TelephoneExt
|
||||||
|
}
|
||||||
|
sb.WriteString(indent + "Telephone: " + phone + "\n")
|
||||||
|
}
|
||||||
|
if contact.Fax != "" {
|
||||||
|
fax := contact.Fax
|
||||||
|
if contact.FaxExt != "" {
|
||||||
|
fax += " ext. " + contact.FaxExt
|
||||||
|
}
|
||||||
|
sb.WriteString(indent + "Fax: " + fax + "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (HistoryRecord) TableName() string {
|
func (HistoryRecord) TableName() string {
|
||||||
return "history"
|
return "history"
|
||||||
}
|
}
|
||||||
@@ -170,3 +435,80 @@ type ContactInfo struct {
|
|||||||
type WhoIsCredits struct {
|
type WhoIsCredits struct {
|
||||||
WhoisCredits int `json:"whois_credits"`
|
WhoisCredits int `json:"whois_credits"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WhoIsIPLookup struct {
|
||||||
|
RemainingCredits int `json:"remaining_credits"`
|
||||||
|
Data IPData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WhoIsMXLookup struct {
|
||||||
|
RemainingCredits int `json:"remaining_credits"`
|
||||||
|
Data IPData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WhoIsNSLookup struct {
|
||||||
|
RemainingCredits int `json:"remaining_credits"`
|
||||||
|
Data IPData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IPData struct {
|
||||||
|
CurrentPage string `json:"current_page"`
|
||||||
|
Result []LookupResult `json:"result"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LookupResult struct {
|
||||||
|
gorm.Model
|
||||||
|
FirstSeen int64 `json:"first_seen"`
|
||||||
|
LastVisit int64 `json:"last_visit"`
|
||||||
|
Name string `json:"name" gorm:"unique"`
|
||||||
|
SearchTerm string `json:"search_term,omitempty"` // For storing the IP address this domain is associated with
|
||||||
|
Type string `json:"type,omitempty"` // For storing the MX address this domain is associated with
|
||||||
|
}
|
||||||
|
|
||||||
|
func (LookupResult) TableName() string {
|
||||||
|
return "lookup"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to format contact information for WhoisRecord
|
||||||
|
func formatWhoisContact(sb *strings.Builder, contact Contact, indent string) {
|
||||||
|
if contact.Name == "" && contact.Organization == "" {
|
||||||
|
sb.WriteString(indent + "No contact information available\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if contact.Name != "" {
|
||||||
|
sb.WriteString(indent + "Name: " + contact.Name + "\n")
|
||||||
|
}
|
||||||
|
if contact.Organization != "" {
|
||||||
|
sb.WriteString(indent + "Organization: " + contact.Organization + "\n")
|
||||||
|
}
|
||||||
|
if contact.Street1 != "" {
|
||||||
|
sb.WriteString(indent + "Street: " + contact.Street1 + "\n")
|
||||||
|
}
|
||||||
|
if contact.City != "" {
|
||||||
|
sb.WriteString(indent + "City: " + contact.City + "\n")
|
||||||
|
}
|
||||||
|
if contact.State != "" {
|
||||||
|
sb.WriteString(indent + "State: " + contact.State + "\n")
|
||||||
|
}
|
||||||
|
if contact.PostalCode != "" {
|
||||||
|
sb.WriteString(indent + "Postal Code: " + contact.PostalCode + "\n")
|
||||||
|
}
|
||||||
|
if contact.Country != "" {
|
||||||
|
sb.WriteString(indent + "Country: " + contact.Country + "\n")
|
||||||
|
}
|
||||||
|
if contact.CountryCode != "" {
|
||||||
|
sb.WriteString(indent + "Country Code: " + contact.CountryCode + "\n")
|
||||||
|
}
|
||||||
|
if contact.Telephone != "" {
|
||||||
|
sb.WriteString(indent + "Telephone: " + contact.Telephone + "\n")
|
||||||
|
}
|
||||||
|
if contact.RawText != "" {
|
||||||
|
rawTextPreview := contact.RawText
|
||||||
|
if len(rawTextPreview) > 100 {
|
||||||
|
rawTextPreview = rawTextPreview[:100] + "... [truncated]"
|
||||||
|
}
|
||||||
|
sb.WriteString(indent + "Raw Text: " + rawTextPreview + "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+732
-141
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user