1 Commits

Author SHA1 Message Date
Evan Hosinski 24b1f99413 Add mutually exclusive flags to targets command 2025-06-03 20:08:40 -04:00
30 changed files with 168 additions and 989 deletions
+2 -2
View File
@@ -30,14 +30,14 @@ clean:
# Build for current platform # Build for current platform
build: build:
CGO_ENABLED=0 $(GO) build -o $(BUILD_DIR)/$(BINARY_NAME) -ldflags "-X main.version=$(VERSION) -s -w" crowsnest.go CGO_ENABLED=0 $(GO) build -o $(BUILD_DIR)/$(BINARY_NAME) -ldflags "-X main.version=$(VERSION)" crowsnest.go
# Build for all platforms # Build for all platforms
build-all: clean build-all: clean
@for platform in $(PLATFORMS); do \ @for platform in $(PLATFORMS); do \
for arch in $(ARCHS); do \ for arch in $(ARCHS); do \
echo "Building for $$platform/$$arch..."; \ echo "Building for $$platform/$$arch..."; \
GOOS=$$platform GOARCH=$$arch CGO_ENABLED=0 $(GO) build -o $(BUILD_DIR)/$(BINARY_NAME)-$$platform-$$arch -ldflags "-X main.version=$(VERSION) -s -w" crowsnest.go; \ GOOS=$$platform GOARCH=$$arch CGO_ENABLED=0 $(GO) build -o $(BUILD_DIR)/$(BINARY_NAME)-$$platform-$$arch -ldflags "-X main.version=$(VERSION)" crowsnest.go; \
if [ "$$platform" = "windows" ]; then \ if [ "$$platform" = "windows" ]; then \
mv $(BUILD_DIR)/$(BINARY_NAME)-$$platform-$$arch $(BUILD_DIR)/$(BINARY_NAME)-$$platform-$$arch.exe; \ mv $(BUILD_DIR)/$(BINARY_NAME)-$$platform-$$arch $(BUILD_DIR)/$(BINARY_NAME)-$$platform-$$arch.exe; \
fi; \ fi; \
+4 -17
View File
@@ -63,7 +63,7 @@ To configure the database location:
CrowsNest requires an API key from Dehashed. Set it up with: CrowsNest requires an API key from Dehashed. Set it up with:
![Alt text](.img/set-dehashed.png "Set Dehashed Key") ![Alt text](.img/set-dehashed.png "Set Dehashed Key")
```bash ```bash
ar1ste1a@kali:~$ crowsnest set dehashed <redacted> ar1ste1a@kali:~$ crowsnest set-dehashed <redacted>
``` ```
### Simple Query ### Simple Query
@@ -134,23 +134,10 @@ crowsnest dehashed -R -E 'joh?n(ath[oa]n)' -D hotmail.com'
CrowsNest is capable of handling output formats. CrowsNest is capable of handling output formats.
The default output format is JSON. The default output format is JSON.
To change the output format, use the `-f` flag. To change the output format, use the `-f` flag.
CrowsNest currently supports JSON, YAML, XML, TEXT, and GREP output formats. CrowsNest 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'
crowsnest dehashed -U admin -o admins_file -f txt crowsnest dehashed -U admin -o admins_file -f txt
# Return one key=value record per line in a greppable file 'admins_file.grep'
crowsnest dehashed -U admin -o admins_file -f grep
```
### Data Wells
DeHashed data wells are free to query and do not require a paid API account.
``` go
# List the first page of data wells and write 'data_wells.json'
crowsnest dehashed data-wells
# Sort by record count and write one key=value record per line
crowsnest dehashed data-wells --sort records-DESC --count 50 -f grep -o data_wells
``` ```
--- ---
@@ -229,11 +216,11 @@ crowsnest whois -n google.com
## 🌐 Hunter.io ## 🌐 Hunter.io
CrowsNest supports Hunter.io lookups. CrowsNest supports Hunter.io lookups.
Hunter.io lookups require a separate API Key from the Dehashed API. Hunter.io lookups require a separate API Key from the Dehashed API.
This can be set using the `set hunter` command. This can be set using the `set-hunter` command.
![Alt text](.img/set-hunter.png "Set Dehashed Key") ![Alt text](.img/set-hunter.png "Set Dehashed Key")
```bash ```bash
# Set the Hunter.io API key # Set the Hunter.io API key
crowsnest set hunter <redacted> crowsnest set-hunter <redacted>
``` ```
### Domain Search ### Domain Search
+9 -69
View File
@@ -1,35 +1,31 @@
package cmd package cmd
import ( import (
"crowsnest/internal/badger"
"crowsnest/internal/debug"
"crowsnest/internal/dehashed"
"crowsnest/internal/sqlite"
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.uber.org/zap" "go.uber.org/zap"
"hub.krkn.tech/KrakenTech/crowsnest/internal/badger"
"hub.krkn.tech/KrakenTech/crowsnest/internal/debug"
"hub.krkn.tech/KrakenTech/crowsnest/internal/dehashed"
"hub.krkn.tech/KrakenTech/crowsnest/internal/files"
"hub.krkn.tech/KrakenTech/crowsnest/internal/pretty"
"hub.krkn.tech/KrakenTech/crowsnest/internal/sqlite"
) )
func init() { func init() {
// Add api command to root command // Add api command to root command
rootCmd.AddCommand(dehashedCmd) rootCmd.AddCommand(dehashedCmd)
dehashedCmd.AddCommand(dehashedDataWellsCmd)
// Add flags specific to api command // Add flags specific to api command
dehashedCmd.Flags().IntVarP(&maxRecords, "max-records", "m", 50000, "Maximum total records to return (max 50000)") dehashedCmd.Flags().IntVarP(&maxRecords, "max-records", "m", 30000, "Maximum amount of records to return")
dehashedCmd.Flags().IntVarP(&maxRequests, "max-requests", "r", -1, "Maximum number of requests to make") dehashedCmd.Flags().IntVarP(&maxRequests, "max-requests", "r", -1, "Maximum number of requests to make")
dehashedCmd.Flags().IntVarP(&startingPage, "starting-page", "s", 1, "Starting page for requests") dehashedCmd.Flags().IntVarP(&startingPage, "starting-page", "s", 1, "Starting page for requests")
dehashedCmd.Flags().BoolVarP(&printBalance, "print-balance", "b", false, "Print remaining balance after requests") dehashedCmd.Flags().BoolVarP(&printBalance, "print-balance", "b", false, "Print remaining balance after requests")
dehashedCmd.Flags().BoolVarP(&regexMatch, "regex-match", "R", false, "Use regex matching on query fields") dehashedCmd.Flags().BoolVarP(&regexMatch, "regex-match", "R", false, "Use regex matching on query fields")
dehashedCmd.Flags().BoolVarP(&wildcardMatch, "wildcard-match", "W", false, "Use wildcard matching on query fields (Use ? to replace a single character, and * for multiple characters)") dehashedCmd.Flags().BoolVarP(&wildcardMatch, "wildcard-match", "W", false, "Use wildcard matching on query fields (Use ? to replace a single character, and * for multiple characters)")
dehashedCmd.Flags().BoolVarP(&credsOnly, "creds-only", "C", false, "Return credentials only") dehashedCmd.Flags().BoolVarP(&credsOnly, "creds-only", "C", false, "Return credentials only")
dehashedCmd.Flags().StringVarP(&outputFormat, "format", "f", "json", "Output format (json, yaml, xml, txt, grep)") dehashedCmd.Flags().StringVarP(&outputFormat, "format", "f", "json", "Output format (json, yaml, xml, txt)")
dehashedCmd.Flags().StringVarP(&outputFile, "output", "o", "query", "File to output results to without extension") dehashedCmd.Flags().StringVarP(&outputFile, "output", "o", "query", "File to output results to including extension")
dehashedCmd.Flags().StringVarP(&usernameQuery, "username", "U", "", "Username query") dehashedCmd.Flags().StringVarP(&usernameQuery, "username", "U", "", "Username query")
dehashedCmd.Flags().StringVarP(&emailQuery, "email-query", "E", "", "Email query") dehashedCmd.Flags().StringVarP(&emailQuery, "email-query", "E", "", "HunterEmail query")
dehashedCmd.Flags().StringVarP(&ipQuery, "ip", "I", "", "IP address query") dehashedCmd.Flags().StringVarP(&ipQuery, "ip", "I", "", "IP address query")
dehashedCmd.Flags().StringVarP(&domainQuery, "domain", "D", "", "Domain query") dehashedCmd.Flags().StringVarP(&domainQuery, "domain", "D", "", "Domain query")
dehashedCmd.Flags().StringVarP(&passwordQuery, "password", "P", "", "Password query") dehashedCmd.Flags().StringVarP(&passwordQuery, "password", "P", "", "Password query")
@@ -44,12 +40,6 @@ func init() {
// Add mutually exclusive flags to wildcard match and regex match // Add mutually exclusive flags to wildcard match and regex match
dehashedCmd.MarkFlagsMutuallyExclusive("regex-match", "wildcard-match") dehashedCmd.MarkFlagsMutuallyExclusive("regex-match", "wildcard-match")
dehashedDataWellsCmd.Flags().IntVar(&dataWellsCount, "count", 20, "Number of data wells to return (20 or 50)")
dehashedDataWellsCmd.Flags().IntVarP(&dataWellsPage, "page", "p", 1, "Data wells page to request")
dehashedDataWellsCmd.Flags().StringVar(&dataWellsSort, "sort", "", "Sort data wells by added, name, date, or records; optionally suffix -ASC or -DESC")
dehashedDataWellsCmd.Flags().StringVarP(&dataWellsOutputFormat, "format", "f", "json", "Output format (json, yaml, xml, txt, grep)")
dehashedDataWellsCmd.Flags().StringVarP(&dataWellsOutputFile, "output", "o", "data_wells", "File to output data wells to without extension")
} }
var ( var (
@@ -76,11 +66,6 @@ var (
phoneQuery string phoneQuery string
socialQuery string socialQuery string
cryptoCurrencyAddressQuery string cryptoCurrencyAddressQuery string
dataWellsCount int
dataWellsPage int
dataWellsSort string
dataWellsOutputFormat string
dataWellsOutputFile string
// Query command // Query command
dehashedCmd = &cobra.Command{ dehashedCmd = &cobra.Command{
@@ -92,7 +77,7 @@ var (
// Validate credentials // Validate credentials
if key == "" { if key == "" {
fmt.Println("API key is required. Set the key with the \"set dehashed\" command. [crowsnest set dehashed <api_key>]") fmt.Println("API key is required. Set the key with the \"set-key\" command. [crowsnest set-key <api_key>]")
return return
} }
@@ -148,54 +133,9 @@ var (
} }
}, },
} }
dehashedDataWellsCmd = &cobra.Command{
Use: "data-wells",
Short: "List DeHashed data wells",
Long: `List DeHashed data wells. This endpoint is free and does not require a DeHashed API key or subscription.`,
Run: func(cmd *cobra.Command, args []string) {
client := dehashed.NewDehashedClientV2("", debugGlobal)
response, err := client.DataWells(dehashed.DataWellsRequest{
Count: dataWellsCount,
Page: dataWellsPage,
Sort: dataWellsSort,
})
if err != nil {
fmt.Printf("[!] Error querying data wells: %v\n", err)
return
}
fType := files.GetFileType(dataWellsOutputFormat)
if dataWellsOutputFile != "" {
fmt.Printf("[*] Writing data wells to file: %s%s\n", dataWellsOutputFile, fType.Extension())
if err := dehashed.WriteDataWellsToFile(response, dataWellsOutputFile, fType); err != nil {
fmt.Printf("[!] Error writing data wells to file: %v\n", err)
return
}
}
fmt.Printf("[+] Retrieved %d data wells (total: %d, next page: %t)\n", len(response.DataWells), response.Total, response.NextPage)
printDataWellsTable(response.DataWells)
},
}
) )
// Helper functions to get stored API credentials // Helper functions to get stored API credentials
func getDehashedApiKey() string { func getDehashedApiKey() string {
return badger.GetDehashedKey() return badger.GetDehashedKey()
} }
func printDataWellsTable(dataWells []dehashed.DataWell) {
headers := []string{"Name", "Date", "Records", "Sensitive", "Data"}
rows := make([][]string, 0, len(dataWells))
for _, well := range dataWells {
rows = append(rows, []string{
well.Name,
well.Date,
fmt.Sprintf("%d", well.Records),
fmt.Sprintf("%t", well.IsSensitive),
well.Data,
})
}
pretty.Table(headers, rows)
}
+8 -9
View File
@@ -1,18 +1,17 @@
package cmd package cmd
import ( import (
"crowsnest/internal/badger"
"crowsnest/internal/debug"
"crowsnest/internal/export"
"crowsnest/internal/files"
hunter "crowsnest/internal/hunter.io"
"crowsnest/internal/pretty"
"crowsnest/internal/sqlite"
"fmt" "fmt"
"time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.uber.org/zap" "go.uber.org/zap"
"hub.krkn.tech/KrakenTech/crowsnest/internal/badger" "time"
"hub.krkn.tech/KrakenTech/crowsnest/internal/debug"
"hub.krkn.tech/KrakenTech/crowsnest/internal/export"
"hub.krkn.tech/KrakenTech/crowsnest/internal/files"
hunter "hub.krkn.tech/KrakenTech/crowsnest/internal/hunter.io"
"hub.krkn.tech/KrakenTech/crowsnest/internal/pretty"
"hub.krkn.tech/KrakenTech/crowsnest/internal/sqlite"
) )
func init() { func init() {
+3 -4
View File
@@ -1,16 +1,15 @@
package cmd package cmd
import ( import (
"crowsnest/internal/easyTime"
"crowsnest/internal/pretty"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/spf13/cobra"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
"github.com/spf13/cobra"
"hub.krkn.tech/KrakenTech/crowsnest/internal/easyTime"
"hub.krkn.tech/KrakenTech/crowsnest/internal/pretty"
) )
func init() { func init() {
+20 -18
View File
@@ -1,17 +1,16 @@
package cmd package cmd
import ( import (
"crowsnest/internal/debug"
"crowsnest/internal/export"
"crowsnest/internal/files"
"crowsnest/internal/pretty"
"crowsnest/internal/sqlite"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.uber.org/zap" "go.uber.org/zap"
"hub.krkn.tech/KrakenTech/crowsnest/internal/debug" "strings"
"hub.krkn.tech/KrakenTech/crowsnest/internal/export"
"hub.krkn.tech/KrakenTech/crowsnest/internal/files"
"hub.krkn.tech/KrakenTech/crowsnest/internal/pretty"
"hub.krkn.tech/KrakenTech/crowsnest/internal/sqlite"
) )
func init() { func init() {
@@ -19,16 +18,15 @@ func init() {
rootCmd.AddCommand(queryCmd) rootCmd.AddCommand(queryCmd)
// Add flags specific to whois command // Add flags specific to whois command
queryCmd.Flags().StringVarP(&dbQueryTableName, "table", "t", "", "Table to query (dehashed, users, whois, subdomains, lookup, runs)") queryCmd.Flags().StringVarP(&dbQueryTableName, "table", "t", "", "Table to query (results, creds, whois, subdomains, history, runs)")
queryCmd.Flags().IntVarP(&dbQueryLimitRows, "limit", "l", 100, "Limit number of results") queryCmd.Flags().IntVarP(&dbQueryLimitRows, "limit", "l", 100, "Limit number of results")
queryCmd.Flags().StringVarP(&dbQueryNotNull, "not-null", "n", "", "Filter for non-null values (comma-separated list, e.g., 'password,email')") queryCmd.Flags().StringVarP(&dbQueryNotNull, "not-null", "n", "", "Filter for non-null values (comma-separated list, e.g., 'password,email')")
queryCmd.Flags().StringVarP(&dbQueryColumns, "columns", "c", "", "Columns to display in output (comma-separated list, e.g., 'username,email,password')") queryCmd.Flags().StringVarP(&dbQueryColumns, "columns", "c", "", "Columns to display in output (comma-separated list, e.g., 'username,email,password')")
queryCmd.Flags().StringVarP(&dbQueryUserQuery, "user-query", "q", "", "User query to execute") queryCmd.Flags().StringVarP(&dbQueryUserQuery, "user-query", "q", "", "User query to execute")
queryCmd.Flags().StringVarP(&dbQueryRawQuery, "raw-query", "r", "", "Raw SQL query to execute") queryCmd.Flags().StringVarP(&dbQueryRawQuery, "raw-query", "r", "", "Raw SQL query to execute")
queryCmd.Flags().BoolVarP(&dbQueryListAll, "list-all", "a", false, "List all tables and their columns") queryCmd.Flags().BoolVarP(&dbQueryListAll, "list-all", "a", false, "List all tables and their columns")
queryCmd.Flags().BoolVarP(&dbQueryExport, "export", "x", false, "Export results to file using --file and --format") queryCmd.Flags().StringVarP(&dbQueryFormat, "format", "f", "json", "Output format (json, yaml, xml, txt)")
queryCmd.Flags().StringVarP(&dbQueryFormat, "format", "f", "json", "Output format (json, yaml, xml, txt, grep)") queryCmd.Flags().StringVarP(&dbQueryFile, "file", "o", "query", "File to output results to")
queryCmd.Flags().StringVarP(&dbQueryFile, "file", "o", "query", "File to output results to when --export is set")
// Add mutually exclusive flags to query and raw-query // Add mutually exclusive flags to query and raw-query
// Cannot use query and raw-query at the same time // Cannot use query and raw-query at the same time
@@ -47,7 +45,6 @@ var (
dbQueryUserQuery string dbQueryUserQuery string
dbQueryRawQuery string dbQueryRawQuery string
dbQueryListAll bool dbQueryListAll bool
dbQueryExport bool
dbQueryFormat string dbQueryFormat string
dbQueryFile string dbQueryFile string
@@ -55,7 +52,7 @@ var (
Use: "query", Use: "query",
Short: "Query the database", Short: "Query the database",
Long: `Query the database for various information. Long: `Query the database for various information.
Use --export with --file and --format to write results to a file instead of displaying them.`, If file is specified, results are written to file and not displayed in the terminal.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// If list-all flag is set, list all tables and columns // If list-all flag is set, list all tables and columns
if dbQueryListAll { if dbQueryListAll {
@@ -73,14 +70,14 @@ Use --export with --file and --format to write results to a file instead of disp
// Validate table name // Validate table name
if dbQueryTableName == "" { if dbQueryTableName == "" {
fmt.Println("[!] Error: Table name is required. Use -t or --table to specify a table.") fmt.Println("[!] Error: Table name is required. Use -t or --table to specify a table.")
fmt.Println("[*] Available tables: dehashed, users, whois, subdomains, lookup, runs") fmt.Println("[*] Available tables: results, creds, whois, subdomains, history, runs")
fmt.Println("[*] Use --list-all to see all tables and their columns.") fmt.Println("[*] Use --list-all to see all tables and their columns.")
return return
} }
if !isValidTable(dbQueryTableName) { if !isValidTable(dbQueryTableName) {
fmt.Printf("[!] Error: Unknown table '%s'.\n", dbQueryTableName) fmt.Printf("[!] Error: Unknown table '%s'.\n", dbQueryTableName)
fmt.Println("[*] Available tables: dehashed, users, whois, subdomains, lookup, runs") fmt.Println("[*] Available tables: results, creds, whois, subdomains, history, runs")
fmt.Println("[*] Use --list-all to see all tables and their columns.") fmt.Println("[*] Use --list-all to see all tables and their columns.")
return return
} }
@@ -127,7 +124,7 @@ Use --export with --file and --format to write results to a file instead of disp
table := sqlite.GetTable(dbQueryTableName) table := sqlite.GetTable(dbQueryTableName)
if table == sqlite.UnknownTable { if table == sqlite.UnknownTable {
fmt.Printf("[!] Error: Unknown table type '%s'.\n", dbQueryTableName) fmt.Printf("[!] Error: Unknown table type '%s'.\n", dbQueryTableName)
fmt.Println("[*] Available tables: dehashed, users, whois, subdomains, lookup, runs") fmt.Println("[*] Available tables: results, creds, whois, subdomains, history, runs")
fmt.Println("[*] Use --list-all to see all tables and their columns.") fmt.Println("[*] Use --list-all to see all tables and their columns.")
return return
} }
@@ -206,7 +203,8 @@ func tableQuery(table sqlite.Table) {
return return
} }
if dbQueryExport { // Export results if file name is specified
if len(strings.TrimSpace(dbQueryFile)) > 0 {
fmt.Println("[*] Exporting results to file...") fmt.Println("[*] Exporting results to file...")
if debugGlobal { if debugGlobal {
@@ -246,6 +244,8 @@ func tableQuery(table sqlite.Table) {
return return
} }
fmt.Println("[*] Querying Database...")
// Prepare data for pretty.Table // Prepare data for pretty.Table
headers := cols headers := cols
var tableRows [][]string var tableRows [][]string
@@ -336,7 +336,7 @@ func rawDBQuery() {
return return
} }
if dbQueryExport { if len(strings.TrimSpace(dbQueryFile)) > 0 {
fmt.Println("[*] Exporting results to file...") fmt.Println("[*] Exporting results to file...")
if debugGlobal { if debugGlobal {
@@ -377,6 +377,8 @@ func rawDBQuery() {
return return
} }
fmt.Println("[*] Querying Database...")
// Prepare data for pretty.Table // Prepare data for pretty.Table
headers := columns headers := columns
var tableRows [][]string var tableRows [][]string
+7 -16
View File
@@ -1,14 +1,13 @@
package cmd package cmd
import ( import (
"crowsnest/internal/badger"
"fmt" "fmt"
"os"
"strings"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.uber.org/zap" "go.uber.org/zap"
"hub.krkn.tech/KrakenTech/crowsnest/internal/badger" "os"
"strings"
) )
var ( var (
@@ -54,23 +53,15 @@ func init() {
rootCmd.PersistentFlags().BoolVar(&debugGlobal, "debug", false, "Show debug information") rootCmd.PersistentFlags().BoolVar(&debugGlobal, "debug", false, "Show debug information")
// Add subcommands // Add subcommands
rootCmd.AddCommand(setCmd) rootCmd.AddCommand(setDehashedKeyCmd)
rootCmd.AddCommand(setHunterKeyCmd)
rootCmd.AddCommand(setLocalDb) rootCmd.AddCommand(setLocalDb)
rootCmd.AddCommand(buyMeCoffeeCmd) rootCmd.AddCommand(buyMeCoffeeCmd)
setCmd.AddCommand(setDehashedKeyCmd)
setCmd.AddCommand(setHunterKeyCmd)
}
var setCmd = &cobra.Command{
Use: "set",
Short: "Set CrowsNest configuration values",
Long: "Set CrowsNest configuration values such as API keys.",
} }
// Command to set API key // Command to set API key
var setDehashedKeyCmd = &cobra.Command{ var setDehashedKeyCmd = &cobra.Command{
Use: "dehashed [key]", Use: "set-dehashed [key]",
Short: "Set and store Dehashed.com API key", Short: "Set and store Dehashed.com API key",
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
@@ -86,7 +77,7 @@ var setDehashedKeyCmd = &cobra.Command{
} }
var setHunterKeyCmd = &cobra.Command{ var setHunterKeyCmd = &cobra.Command{
Use: "hunter [key]", Use: "set-hunter [key]",
Short: "Set and store Hunter.io API key", Short: "Set and store Hunter.io API key",
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
+1 -2
View File
@@ -1,10 +1,9 @@
package cmd package cmd
import ( import (
"crowsnest/internal/pretty"
"fmt" "fmt"
"strings" "strings"
"hub.krkn.tech/KrakenTech/crowsnest/internal/pretty"
) )
// Map of available tables and their columns // Map of available tables and their columns
+4 -5
View File
@@ -1,13 +1,12 @@
package cmd package cmd
import ( import (
"crowsnest/internal/sqlite"
"fmt" "fmt"
"os"
"strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.uber.org/zap" "go.uber.org/zap"
"hub.krkn.tech/KrakenTech/crowsnest/internal/sqlite" "os"
"strings"
) )
func init() { func init() {
@@ -22,7 +21,7 @@ func init() {
targetsCmd.Flags().BoolVarP(&targetsEmails, "emails", "E", false, "Output emails only (no passwords)") targetsCmd.Flags().BoolVarP(&targetsEmails, "emails", "E", false, "Output emails only (no passwords)")
targetsCmd.Flags().StringVarP(&targetsDomain, "domain", "d", "", "Filter by domain (for emails and subdomains)") targetsCmd.Flags().StringVarP(&targetsDomain, "domain", "d", "", "Filter by domain (for emails and subdomains)")
// Mark output flag as required // Add mutually exclusive flags to targets command
targetsCmd.MarkFlagsMutuallyExclusive("external", "internal", "subdomains", "emails") targetsCmd.MarkFlagsMutuallyExclusive("external", "internal", "subdomains", "emails")
} }
+11 -12
View File
@@ -1,19 +1,18 @@
package cmd package cmd
import ( import (
"crowsnest/internal/debug"
"crowsnest/internal/export"
"crowsnest/internal/files"
"crowsnest/internal/pretty"
"crowsnest/internal/sqlite"
"crowsnest/internal/whois"
"fmt" "fmt"
"github.com/spf13/cobra"
"go.uber.org/zap"
"os" "os"
"strings" "strings"
"time" "time"
"github.com/spf13/cobra"
"go.uber.org/zap"
"hub.krkn.tech/KrakenTech/crowsnest/internal/debug"
"hub.krkn.tech/KrakenTech/crowsnest/internal/export"
"hub.krkn.tech/KrakenTech/crowsnest/internal/files"
"hub.krkn.tech/KrakenTech/crowsnest/internal/pretty"
"hub.krkn.tech/KrakenTech/crowsnest/internal/sqlite"
"hub.krkn.tech/KrakenTech/crowsnest/internal/whois"
) )
func init() { func init() {
@@ -60,7 +59,7 @@ var (
// Validate credentials // Validate credentials
if key == "" { if key == "" {
fmt.Println("API key is required. Set the key with the \"set dehashed\" command. [crowsnest set dehashed <api_key>]") fmt.Println("API key is required. Set the key with the \"set-key\" command. [crowsnest set-key <api_key>]")
return return
} }
@@ -196,8 +195,8 @@ var (
// Write history records to file if any // Write history records to file if any
if len(historyRecords) > 0 { if len(historyRecords) > 0 {
fmt.Printf("[*] Records Found: %d\n", len(historyRecords)) fmt.Println("[*] Records Found: %d\n", len(historyRecords))
fmt.Printf("[*] WHOIS History being written to file: %s%s\n", whoisOutputFile, fType.Extension()) fmt.Println("[*] WHOIS History being written to file: %s%s\n", whoisOutputFile, fType.Extension())
writeErr := export.WriteWhoIsHistoryToFile(historyRecords, filename, fType) writeErr := export.WriteWhoIsHistoryToFile(historyRecords, filename, fType)
if writeErr != nil { if writeErr != nil {
if debugGlobal { if debugGlobal {
+5 -6
View File
@@ -1,17 +1,16 @@
package main package main
import ( import (
"crowsnest/cmd"
"crowsnest/internal/badger"
"crowsnest/internal/sqlite"
"fmt" "fmt"
"os"
"path/filepath"
"github.com/winking324/rzap" "github.com/winking324/rzap"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2" "gopkg.in/natefinch/lumberjack.v2"
"hub.krkn.tech/KrakenTech/crowsnest/cmd" "os"
"hub.krkn.tech/KrakenTech/crowsnest/internal/badger" "path/filepath"
"hub.krkn.tech/KrakenTech/crowsnest/internal/sqlite"
) )
var ( var (
+3 -1
View File
@@ -1,4 +1,4 @@
module hub.krkn.tech/KrakenTech/crowsnest module crowsnest
go 1.23.0 go 1.23.0
@@ -14,6 +14,7 @@ require (
go.uber.org/zap v1.20.0 go.uber.org/zap v1.20.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/sqlite v1.5.7
gorm.io/gorm v1.26.1 gorm.io/gorm v1.26.1
) )
@@ -40,6 +41,7 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/muesli/termenv v0.16.0 // indirect github.com/muesli/termenv v0.16.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
+4
View File
@@ -75,6 +75,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
@@ -169,6 +171,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
gorm.io/gorm v1.26.1 h1:ghB2gUI9FkS46luZtn6DLZ0f6ooBJ5IbVej2ENFDjRw= gorm.io/gorm v1.26.1 h1:ghB2gUI9FkS46luZtn6DLZ0f6ooBJ5IbVej2ENFDjRw=
gorm.io/gorm v1.26.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= gorm.io/gorm v1.26.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
+10 -233
View File
@@ -3,19 +3,15 @@ package badger
import ( import (
"crypto/sha256" "crypto/sha256"
"errors" "errors"
"fmt" "github.com/dgraph-io/badger/v4"
"go.uber.org/zap"
"log" "log"
"os" "os"
"os/exec"
"os/user" "os/user"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
"time"
"github.com/dgraph-io/badger/v4"
"go.uber.org/zap"
) )
var ( var (
@@ -25,35 +21,13 @@ var (
once sync.Once once sync.Once
) )
const fingerprintSalt = "CrowsNest-static-salt-value" func GetHardwareEntropy() []byte {
func GetHardwareEntropy() ([]byte, error) {
source, machineID, err := getMachineID()
if err != nil {
return nil, err
}
fingerprint := strings.Join([]string{
"v2",
runtime.GOOS,
source,
machineID,
fingerprintSalt,
}, ":")
return hashFingerprint(fingerprint), nil
}
func GetLegacyHardwareEntropy() []byte {
// Get hostname // Get hostname
hostname, err := os.Hostname() hostname, err := os.Hostname()
if err != nil { if err != nil {
hostname = "unknown-host" hostname = "unknown-host"
log.Printf("Error getting hostname: %v", err) log.Printf("Error getting hostname: %v", err)
} }
if legacyHostname := strings.TrimSpace(os.Getenv("CROWSNEST_LEGACY_HOSTNAME")); legacyHostname != "" {
hostname = legacyHostname
}
// Get username // Get username
currentUser, err := user.Current() currentUser, err := user.Current()
@@ -61,15 +35,9 @@ func GetLegacyHardwareEntropy() []byte {
if err == nil && currentUser != nil { if err == nil && currentUser != nil {
username = currentUser.Username username = currentUser.Username
} }
if legacyUsername := strings.TrimSpace(os.Getenv("CROWSNEST_LEGACY_USERNAME")); legacyUsername != "" {
username = legacyUsername
}
// Get OS and architecture info // Get OS and architecture info
osInfo := runtime.GOOS + "-" + runtime.GOARCH osInfo := runtime.GOOS + "-" + runtime.GOARCH
if legacyOSInfo := strings.TrimSpace(os.Getenv("CROWSNEST_LEGACY_OSINFO")); legacyOSInfo != "" {
osInfo = legacyOSInfo
}
// Combine all information for a unique but consistent fingerprint // Combine all information for a unique but consistent fingerprint
fingerprint := strings.Join([]string{ fingerprint := strings.Join([]string{
@@ -77,93 +45,14 @@ func GetLegacyHardwareEntropy() []byte {
username, username,
osInfo, osInfo,
// You could add a static salt here for additional security // You could add a static salt here for additional security
fingerprintSalt, "CrowsNest-static-salt-value",
}, ":") }, ":")
// Hash the fingerprint to get a 32-byte key // Hash the fingerprint to get a 32-byte key
return hashFingerprint(fingerprint)
}
func hashFingerprint(fingerprint string) []byte {
sum := sha256.Sum256([]byte(fingerprint)) sum := sha256.Sum256([]byte(fingerprint))
return sum[:] return sum[:]
} }
func getMachineID() (string, string, error) {
switch runtime.GOOS {
case "darwin":
return getDarwinMachineID()
case "linux":
return getLinuxMachineID()
case "windows":
return getWindowsMachineID()
default:
return "", "", fmt.Errorf("stable machine id is not implemented for %s", runtime.GOOS)
}
}
func getDarwinMachineID() (string, string, error) {
out, err := exec.Command("ioreg", "-rd1", "-c", "IOPlatformExpertDevice").Output()
if err != nil {
return "", "", fmt.Errorf("run ioreg: %w", err)
}
for _, line := range strings.Split(string(out), "\n") {
if !strings.Contains(line, "IOPlatformUUID") {
continue
}
if id := normalizeMachineID(lastQuotedValue(line)); id != "" {
return "darwin-ioplatformuuid", id, nil
}
}
return "", "", errors.New("IOPlatformUUID not found")
}
func getLinuxMachineID() (string, string, error) {
for _, path := range []string{"/etc/machine-id", "/var/lib/dbus/machine-id"} {
out, err := os.ReadFile(path)
if err != nil {
continue
}
if id := normalizeMachineID(string(out)); id != "" {
return "linux-machine-id", id, nil
}
}
return "", "", errors.New("machine-id not found")
}
func getWindowsMachineID() (string, string, error) {
out, err := exec.Command("reg", "query", `HKLM\SOFTWARE\Microsoft\Cryptography`, "/v", "MachineGuid").Output()
if err != nil {
return "", "", fmt.Errorf("query MachineGuid: %w", err)
}
for _, line := range strings.Split(string(out), "\n") {
fields := strings.Fields(line)
if len(fields) >= 3 && strings.EqualFold(fields[0], "MachineGuid") {
if id := normalizeMachineID(fields[len(fields)-1]); id != "" {
return "windows-machineguid", id, nil
}
}
}
return "", "", errors.New("MachineGuid not found")
}
func lastQuotedValue(line string) string {
values := strings.Split(line, "\"")
if len(values) < 4 {
return ""
}
return values[len(values)-2]
}
func normalizeMachineID(id string) string {
return strings.ToLower(strings.TrimSpace(id))
}
func Start(dirPath string) *badger.DB { func Start(dirPath string) *badger.DB {
var err error var err error
@@ -176,7 +65,7 @@ func Start(dirPath string) *badger.DB {
} }
rootDir = dirPath rootDir = dirPath
encryptionKey, err = GetHardwareEntropy() encryptionKey = GetHardwareEntropy()
if err != nil { if err != nil {
zap.L().Fatal("get_encryption_key", zap.L().Fatal("get_encryption_key",
zap.String("message", "failed to get encryption key"), zap.String("message", "failed to get encryption key"),
@@ -185,134 +74,22 @@ func Start(dirPath string) *badger.DB {
} }
badgerDB := filepath.Join(rootDir, "badger.db") badgerDB := filepath.Join(rootDir, "badger.db")
db, err = openBadger(badgerDB, encryptionKey) opts := badger.DefaultOptions(badgerDB).
if err != nil { WithEncryptionKey(encryptionKey).
zap.L().Warn("open_badger_db", WithIndexCacheSize(10 << 20). // 10MB
zap.String("message", "failed to open badger database with stable machine key; trying legacy key"), WithLoggingLevel(badger.ERROR)
zap.Error(err), db, err = badger.Open(opts)
)
db, err = openBadgerWithLegacyMigration(badgerDB, encryptionKey)
if err != nil { if err != nil {
zap.L().Fatal("new_badger_db", zap.L().Fatal("new_badger_db",
zap.String("message", "failed to open badger database"), zap.String("message", "failed to open badger database"),
zap.Error(err), zap.Error(err),
) )
} }
}
}) })
return db return db
} }
func openBadger(dbPath string, key []byte) (*badger.DB, error) {
opts := badger.DefaultOptions(dbPath).
WithEncryptionKey(key).
WithIndexCacheSize(10 << 20). // 10MB
WithLoggingLevel(badger.ERROR)
return badger.Open(opts)
}
func openBadgerWithLegacyMigration(dbPath string, stableKey []byte) (*badger.DB, error) {
legacyKey := GetLegacyHardwareEntropy()
legacyDB, err := openBadger(dbPath, legacyKey)
if err != nil {
return nil, fmt.Errorf("stable key failed and legacy key failed: %w", err)
}
migratedDB, err := migrateBadgerEncryption(dbPath, legacyDB, stableKey)
if err != nil {
if closeErr := legacyDB.Close(); closeErr != nil {
zap.L().Error("close_legacy_badger_db", zap.Error(closeErr))
}
return nil, err
}
return migratedDB, nil
}
func migrateBadgerEncryption(dbPath string, legacyDB *badger.DB, stableKey []byte) (*badger.DB, error) {
parentDir := filepath.Dir(dbPath)
timestamp := time.Now().Format("20060102-150405")
migrationPath := filepath.Join(parentDir, fmt.Sprintf(".%s.migrating-%s", filepath.Base(dbPath), timestamp))
backupPath := filepath.Join(parentDir, fmt.Sprintf("%s.legacy-backup-%s", filepath.Base(dbPath), timestamp))
newDB, err := openBadger(migrationPath, stableKey)
if err != nil {
return nil, fmt.Errorf("open migration badger db: %w", err)
}
if err := copyBadgerData(legacyDB, newDB); err != nil {
_ = newDB.Close()
_ = os.RemoveAll(migrationPath)
return nil, fmt.Errorf("copy legacy badger data: %w", err)
}
if err := legacyDB.Close(); err != nil {
_ = newDB.Close()
_ = os.RemoveAll(migrationPath)
return nil, fmt.Errorf("close legacy badger db: %w", err)
}
if err := newDB.Close(); err != nil {
_ = os.RemoveAll(migrationPath)
return nil, fmt.Errorf("close migration badger db: %w", err)
}
if err := os.Rename(dbPath, backupPath); err != nil {
_ = os.RemoveAll(migrationPath)
return nil, fmt.Errorf("backup legacy badger db: %w", err)
}
if err := os.Rename(migrationPath, dbPath); err != nil {
if restoreErr := os.Rename(backupPath, dbPath); restoreErr != nil {
return nil, fmt.Errorf("promote migrated badger db: %w; restore legacy backup: %v", err, restoreErr)
}
return nil, fmt.Errorf("promote migrated badger db: %w", err)
}
db, err := openBadger(dbPath, stableKey)
if err != nil {
return nil, fmt.Errorf("open migrated badger db: %w", err)
}
zap.L().Info("migrated_badger_encryption",
zap.String("backup", backupPath),
zap.String("path", dbPath),
)
return db, nil
}
func copyBadgerData(src *badger.DB, dst *badger.DB) error {
return src.View(func(srcTxn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
opts.PrefetchValues = true
iter := srcTxn.NewIterator(opts)
defer iter.Close()
return dst.Update(func(dstTxn *badger.Txn) error {
for iter.Rewind(); iter.Valid(); iter.Next() {
item := iter.Item()
if item.IsDeletedOrExpired() {
continue
}
key := item.KeyCopy(nil)
value, err := item.ValueCopy(nil)
if err != nil {
return err
}
entry := badger.NewEntry(key, value).WithMeta(item.UserMeta())
entry.ExpiresAt = item.ExpiresAt()
if err := dstTxn.SetEntry(entry); err != nil {
return err
}
}
return nil
})
})
}
func Close() { func Close() {
err := db.Close() err := db.Close()
if err != nil { if err != nil {
-73
View File
@@ -1,73 +0,0 @@
package badger
import (
"crypto/sha256"
"os"
"path/filepath"
"strings"
"testing"
badgerapi "github.com/dgraph-io/badger/v4"
)
func TestMigrateBadgerEncryptionCopiesDataToStableKey(t *testing.T) {
dir := t.TempDir()
dbPath := filepath.Join(dir, "badger.db")
legacyKey := testKey("legacy-key")
stableKey := testKey("stable-key")
legacyDB, err := openBadger(dbPath, legacyKey)
if err != nil {
t.Fatalf("open legacy db: %v", err)
}
if err := legacyDB.Update(func(txn *badgerapi.Txn) error {
return txn.Set([]byte("cfg:api_key"), []byte("secret"))
}); err != nil {
t.Fatalf("seed legacy db: %v", err)
}
migratedDB, err := migrateBadgerEncryption(dbPath, legacyDB, stableKey)
if err != nil {
t.Fatalf("migrate db: %v", err)
}
defer migratedDB.Close()
var got string
if err := migratedDB.View(func(txn *badgerapi.Txn) error {
item, err := txn.Get([]byte("cfg:api_key"))
if err != nil {
return err
}
return item.Value(func(value []byte) error {
got = string(value)
return nil
})
}); err != nil {
t.Fatalf("read migrated db: %v", err)
}
if got != "secret" {
t.Fatalf("migrated value = %q, want %q", got, "secret")
}
entries, err := os.ReadDir(dir)
if err != nil {
t.Fatalf("read temp dir: %v", err)
}
foundBackup := false
for _, entry := range entries {
if strings.HasPrefix(entry.Name(), "badger.db.legacy-backup-") {
foundBackup = true
break
}
}
if !foundBackup {
t.Fatal("legacy backup directory was not created")
}
}
func testKey(value string) []byte {
sum := sha256.Sum256([]byte(value))
return sum[:]
}
+5 -14
View File
@@ -2,18 +2,17 @@ package dehashed
import ( import (
"bytes" "bytes"
"crowsnest/internal/debug"
"crowsnest/internal/sqlite"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"go.uber.org/zap"
"io" "io"
"net/http" "net/http"
"strings" "strings"
"go.uber.org/zap"
"hub.krkn.tech/KrakenTech/crowsnest/internal/debug"
"hub.krkn.tech/KrakenTech/crowsnest/internal/sqlite"
) )
type DehashedParameter string type DehashedParameter string
@@ -199,7 +198,7 @@ func (dcv2 *DehashedClientV2) Search(searchRequest DehashedSearchRequest) (int,
req.Header.Set("Dehashed-Api-Key", dcv2.apiKey) req.Header.Set("Dehashed-Api-Key", dcv2.apiKey)
if dcv2.debug { if dcv2.debug {
headers := redactedHeaders(req.Header) headers := req.Header.Clone()
h := fmt.Sprintf("Headers: %v\n", headers) h := fmt.Sprintf("Headers: %v\n", headers)
debug.PrintJson(h) debug.PrintJson(h)
zap.L().Info("v2_search_debug", zap.L().Info("v2_search_debug",
@@ -287,7 +286,7 @@ func (dcv2 *DehashedClientV2) Search(searchRequest DehashedSearchRequest) (int,
} }
dcv2.results = append(dcv2.results, responseResults.Entries...) dcv2.results = append(dcv2.results, responseResults.Entries...)
return len(responseResults.Entries), responseResults.Balance, nil return responseResults.TotalResults, responseResults.Balance, nil
} }
func (dcv2 *DehashedClientV2) GetResults() sqlite.DehashedResults { func (dcv2 *DehashedClientV2) GetResults() sqlite.DehashedResults {
@@ -305,11 +304,3 @@ func enquoteSpaced(s string) string {
} }
return s return s
} }
func redactedHeaders(headers http.Header) http.Header {
redacted := headers.Clone()
if redacted.Get("Dehashed-Api-Key") != "" {
redacted.Set("Dehashed-Api-Key", "[REDACTED]")
}
return redacted
}
-186
View File
@@ -1,186 +0,0 @@
package dehashed
import (
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"gopkg.in/yaml.v3"
"hub.krkn.tech/KrakenTech/crowsnest/internal/files"
)
const dataWellsEndpoint = "https://api.dehashed.com/data-wells"
type DataWellsRequest struct {
Count int
Page int
Sort string
}
type DataWellsResponse struct {
NextPage bool `json:"next_page" xml:"next_page" yaml:"next_page"`
Total int `json:"total" xml:"total" yaml:"total"`
DataWells []DataWell `json:"data_wells" xml:"data_wells" yaml:"data_wells"`
}
type DataWell struct {
Data string `json:"data" xml:"data" yaml:"data"`
Date string `json:"date" xml:"date" yaml:"date"`
Description string `json:"description" xml:"description" yaml:"description"`
Name string `json:"name" xml:"name" yaml:"name"`
Records int `json:"records" xml:"records" yaml:"records"`
IsSensitive bool `json:"is_sensitive" xml:"is_sensitive" yaml:"is_sensitive"`
}
func (dcv2 *DehashedClientV2) DataWells(request DataWellsRequest) (DataWellsResponse, error) {
var dataWells DataWellsResponse
endpoint, err := dataWellsURL(request)
if err != nil {
return dataWells, err
}
req, err := http.NewRequest("GET", endpoint, nil)
if err != nil {
return dataWells, err
}
req.Header.Set("Accept", "application/json")
res, err := http.DefaultClient.Do(req)
if res != nil {
defer res.Body.Close()
}
if err != nil {
return dataWells, err
}
if res == nil {
return dataWells, errors.New("response was nil")
}
body, err := io.ReadAll(res.Body)
if err != nil {
return dataWells, err
}
if res.StatusCode != http.StatusOK {
return dataWells, fmt.Errorf("data wells request failed: status=%d body=%s", res.StatusCode, string(body))
}
if err := json.Unmarshal(body, &dataWells); err != nil {
return dataWells, err
}
return dataWells, nil
}
func dataWellsURL(request DataWellsRequest) (string, error) {
if request.Page <= 0 {
return "", errors.New("page must be 1 or greater")
}
if request.Count != 20 && request.Count != 50 {
return "", errors.New("count must be 20 or 50")
}
if request.Sort != "" && !validDataWellsSort(request.Sort) {
return "", fmt.Errorf("invalid sort %q; use added, name, date, records, optionally suffixed with -ASC or -DESC", request.Sort)
}
values := url.Values{}
values.Set("page", strconv.Itoa(request.Page))
values.Set("count", strconv.Itoa(request.Count))
if request.Sort != "" {
values.Set("sort", request.Sort)
}
return dataWellsEndpoint + "?" + values.Encode(), nil
}
func validDataWellsSort(sortValue string) bool {
sortValue = strings.ToLower(strings.TrimSpace(sortValue))
field := sortValue
if before, _, ok := strings.Cut(sortValue, "-"); ok {
field = before
}
switch field {
case "added", "name", "date", "records":
return strings.HasSuffix(sortValue, "-asc") || strings.HasSuffix(sortValue, "-desc") || !strings.Contains(sortValue, "-")
default:
return false
}
}
func WriteDataWellsToFile(dataWells DataWellsResponse, outputFile string, fileType files.FileType) error {
var data []byte
var err error
switch fileType {
case files.JSON:
data, err = json.MarshalIndent(dataWells, "", " ")
case files.XML:
data, err = xml.MarshalIndent(dataWells, "", " ")
case files.YAML:
data, err = yaml.Marshal(dataWells)
case files.TEXT:
data = []byte(dataWells.String())
case files.GREPPABLE:
var outStrings []string
for _, well := range dataWells.DataWells {
outStrings = append(outStrings, dataWellGreppable(well)+"\n")
}
data = []byte(strings.Join(outStrings, ""))
default:
return errors.New("unsupported file type")
}
if err != nil {
return err
}
return os.WriteFile(outputFile+fileType.Extension(), data, 0644)
}
func (dwr DataWellsResponse) String() string {
var b strings.Builder
fmt.Fprintf(&b, "Total: %d\nNext Page: %t\n\n", dwr.Total, dwr.NextPage)
for _, well := range dwr.DataWells {
fmt.Fprintf(&b, "Name: %s\nDate: %s\nRecords: %d\nSensitive: %t\nData: %s\nDescription: %s\n\n",
well.Name,
well.Date,
well.Records,
well.IsSensitive,
well.Data,
well.Description,
)
}
return b.String()
}
func dataWellGreppable(well DataWell) string {
var fields []string
fields = appendDataWellGreppableField(fields, "name", well.Name)
fields = appendDataWellGreppableField(fields, "date", well.Date)
fields = appendDataWellGreppableField(fields, "records", strconv.Itoa(well.Records))
fields = appendDataWellGreppableField(fields, "is_sensitive", strconv.FormatBool(well.IsSensitive))
fields = appendDataWellGreppableField(fields, "data", well.Data)
fields = appendDataWellGreppableField(fields, "description", well.Description)
return strings.Join(fields, " ")
}
func cleanGreppableValue(value string) string {
return strings.Join(strings.Fields(value), "_")
}
func appendDataWellGreppableField(fields []string, key, value string) []string {
value = cleanGreppableValue(value)
if value == "" {
return fields
}
return append(fields, fmt.Sprintf("%s=%s", key, value))
}
+40 -50
View File
@@ -1,20 +1,14 @@
package dehashed package dehashed
import ( import (
"crowsnest/internal/debug"
"crowsnest/internal/export"
"crowsnest/internal/pretty"
"crowsnest/internal/sqlite"
"fmt" "fmt"
"go.uber.org/zap"
"os" "os"
"strings" "strings"
"go.uber.org/zap"
"hub.krkn.tech/KrakenTech/crowsnest/internal/debug"
"hub.krkn.tech/KrakenTech/crowsnest/internal/export"
"hub.krkn.tech/KrakenTech/crowsnest/internal/pretty"
"hub.krkn.tech/KrakenTech/crowsnest/internal/sqlite"
)
const (
maxSearchResultsPerPage = 10000
maxSearchResultsPerQuery = 50000
) )
// Dehasher is a struct for querying the Dehashed API // Dehasher is a struct for querying the Dehashed API
@@ -23,7 +17,6 @@ type Dehasher struct {
nextPage int nextPage int
debug bool debug bool
balance int balance int
maxResults int
request *DehashedSearchRequest request *DehashedSearchRequest
client *DehashedClientV2 client *DehashedClientV2
} }
@@ -58,55 +51,55 @@ func (dh *Dehasher) getNextPage() int {
// setQueries sets the number of queries to make based on the number of records and requests // setQueries sets the number of queries to make based on the number of records and requests
func (dh *Dehasher) setQueries() { func (dh *Dehasher) setQueries() {
var numQueries int
if dh.debug { if dh.debug {
debug.PrintInfo("setting queries") debug.PrintInfo("setting queries")
} }
if dh.options.MaxRequests == 0 { switch {
case dh.options.MaxRequests == 0:
zap.L().Error("max requests cannot be zero") zap.L().Error("max requests cannot be zero")
fmt.Println("[!] Max Requests cannot be zero") fmt.Println("[!] Max Requests cannot be zero")
os.Exit(1) 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))
} }
requestedMaxResults := dh.options.MaxRecords
if requestedMaxResults <= 0 {
requestedMaxResults = maxSearchResultsPerQuery
}
if requestedMaxResults > maxSearchResultsPerQuery {
requestedMaxResults = maxSearchResultsPerQuery
}
pageSize := requestedMaxResults
if pageSize > maxSearchResultsPerPage {
pageSize = maxSearchResultsPerPage
}
numQueries := (requestedMaxResults + pageSize - 1) / pageSize
if dh.options.MaxRequests > 0 && dh.options.MaxRequests < numQueries {
numQueries = dh.options.MaxRequests
}
dh.maxResults = requestedMaxResults
if requestLimit := numQueries * pageSize; requestLimit < dh.maxResults {
dh.maxResults = requestLimit
}
dh.options.MaxRecords = pageSize
dh.options.MaxRequests = numQueries dh.options.MaxRequests = numQueries
zap.L().Info("dehashed_search_pagination",
zap.Int("max_results", dh.maxResults),
zap.Int("page_size", dh.options.MaxRecords),
zap.Int("max_requests", dh.options.MaxRequests),
)
if dh.debug { if dh.debug {
debug.PrintInfo(fmt.Sprintf("setting max requests: %d", numQueries)) debug.PrintInfo(fmt.Sprintf("setting max requests: %d", numQueries))
debug.PrintInfo(fmt.Sprintf("setting page size: %d", dh.options.MaxRecords)) debug.PrintInfo(fmt.Sprintf("setting max records: %d", dh.options.MaxRecords))
debug.PrintInfo(fmt.Sprintf("setting max results: %d", dh.maxResults))
} }
fmt.Printf("Making %d Requests for up to %d Records (%d per request)\n", dh.options.MaxRequests, dh.maxResults, dh.options.MaxRecords) 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 // Start starts the querying process
@@ -158,7 +151,7 @@ func (dh *Dehasher) Start() {
fmt.Printf(" [-] Not enough entries, ending queries\n") fmt.Printf(" [-] Not enough entries, ending queries\n")
break break
} else { } else {
fmt.Printf(" [+] Retrieved %d records\n", count) fmt.Printf(" [+] Retrieved %d records\n", dh.options.MaxRecords)
} }
if dh.options.PrintBalance { if dh.options.PrintBalance {
@@ -218,9 +211,6 @@ func (dh *Dehasher) buildRequest() {
func (dh *Dehasher) parseResults() { func (dh *Dehasher) parseResults() {
zap.L().Info("extracting_credentials") zap.L().Info("extracting_credentials")
results := dh.client.GetResults() results := dh.client.GetResults()
if dh.maxResults > 0 && len(results.Results) > dh.maxResults {
results.Results = results.Results[:dh.maxResults]
}
creds := results.ExtractUsers() creds := results.ExtractUsers()
fmt.Printf(" [+] Discovered %d Credentials\n", len(creds)) fmt.Printf(" [+] Discovered %d Credentials\n", len(creds))
err := sqlite.StoreUsers(creds) err := sqlite.StoreUsers(creds)
-94
View File
@@ -1,94 +0,0 @@
package dehashed
import (
"strings"
"testing"
"hub.krkn.tech/KrakenTech/crowsnest/internal/sqlite"
)
func TestSetQueriesCapsSearchAtFiftyThousandResults(t *testing.T) {
options := &sqlite.QueryOptions{
MaxRecords: 75000,
MaxRequests: -1,
StartingPage: 1,
}
dehasher := NewDehasher(options)
if dehasher.maxResults != maxSearchResultsPerQuery {
t.Fatalf("maxResults = %d, want %d", dehasher.maxResults, maxSearchResultsPerQuery)
}
if dehasher.options.MaxRecords != maxSearchResultsPerPage {
t.Fatalf("page size = %d, want %d", dehasher.options.MaxRecords, maxSearchResultsPerPage)
}
if dehasher.options.MaxRequests != 5 {
t.Fatalf("max requests = %d, want 5", dehasher.options.MaxRequests)
}
}
func TestSetQueriesHonorsExplicitRequestLimit(t *testing.T) {
options := &sqlite.QueryOptions{
MaxRecords: 50000,
MaxRequests: 1,
StartingPage: 1,
}
dehasher := NewDehasher(options)
if dehasher.maxResults != maxSearchResultsPerPage {
t.Fatalf("maxResults = %d, want %d", dehasher.maxResults, maxSearchResultsPerPage)
}
if dehasher.options.MaxRecords != maxSearchResultsPerPage {
t.Fatalf("page size = %d, want %d", dehasher.options.MaxRecords, maxSearchResultsPerPage)
}
if dehasher.options.MaxRequests != 1 {
t.Fatalf("max requests = %d, want 1", dehasher.options.MaxRequests)
}
}
func TestDataWellsURLDoesNotRequireAPIKey(t *testing.T) {
got, err := dataWellsURL(DataWellsRequest{
Count: 50,
Page: 2,
Sort: "records-DESC",
})
if err != nil {
t.Fatalf("dataWellsURL returned error: %v", err)
}
if !strings.HasPrefix(got, dataWellsEndpoint+"?") {
t.Fatalf("url = %q, want prefix %q", got, dataWellsEndpoint+"?")
}
gotLower := strings.ToLower(got)
if strings.Contains(gotLower, "api_key") || strings.Contains(gotLower, "dehashed-api-key") {
t.Fatalf("url contains API key material: %q", got)
}
for _, want := range []string{"count=50", "page=2", "sort=records-DESC"} {
if !strings.Contains(got, want) {
t.Fatalf("url = %q, want %q", got, want)
}
}
}
func TestDataWellGreppableUsesSpaceSeparatedNonEmptyTokens(t *testing.T) {
got := dataWellGreppable(DataWell{
Name: "Example Breach",
Date: "2025-03-01",
Records: 500000,
IsSensitive: true,
Data: "name,email,address",
})
if strings.Contains(got, "\t") {
t.Fatalf("greppable output contains tab: %q", got)
}
if strings.Contains(got, "description=") {
t.Fatalf("greppable output contains empty field: %q", got)
}
for _, want := range []string{"name=Example_Breach", "date=2025-03-01", "records=500000", "is_sensitive=true", "data=name,email,address"} {
if !strings.Contains(got, want) {
t.Fatalf("greppable output = %q, want token %q", got, want)
}
}
}
+2 -3
View File
@@ -1,14 +1,13 @@
package easyTime package easyTime
import ( import (
"crowsnest/internal/debug"
"fmt" "fmt"
"go.uber.org/zap"
"os" "os"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"go.uber.org/zap"
"hub.krkn.tech/KrakenTech/crowsnest/internal/debug"
) )
type TimeChunk struct { type TimeChunk struct {
+6 -92
View File
@@ -1,18 +1,17 @@
package export package export
import ( import (
"crowsnest/internal/files"
"crowsnest/internal/sqlite"
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
"errors" "errors"
"fmt" "fmt"
"gopkg.in/yaml.v3"
"io/ioutil"
"os" "os"
"sort"
"strings" "strings"
"time" "time"
"gopkg.in/yaml.v3"
"hub.krkn.tech/KrakenTech/crowsnest/internal/files"
"hub.krkn.tech/KrakenTech/crowsnest/internal/sqlite"
) )
func WriteCredsToFile(creds []sqlite.User, outputFile string, fileType files.FileType) error { func WriteCredsToFile(creds []sqlite.User, outputFile string, fileType files.FileType) error {
@@ -32,16 +31,6 @@ func WriteCredsToFile(creds []sqlite.User, outputFile string, fileType files.Fil
outStrings = append(outStrings, c.ToString()+"\n") outStrings = append(outStrings, c.ToString()+"\n")
} }
data = []byte(strings.Join(outStrings, "")) data = []byte(strings.Join(outStrings, ""))
case files.GREPPABLE:
var outStrings []string
for _, c := range creds {
var fields []string
fields = appendGreppableField(fields, "email", c.Email)
fields = appendGreppableField(fields, "username", c.Username)
fields = appendGreppableField(fields, "password", c.Password)
outStrings = append(outStrings, strings.Join(fields, " ")+"\n")
}
data = []byte(strings.Join(outStrings, ""))
default: default:
return errors.New("unsupported file type") return errors.New("unsupported file type")
} }
@@ -76,12 +65,6 @@ func WriteToFile(results sqlite.DehashedResults, outputFile string, fileType fil
outStrings = append(outStrings, out) outStrings = append(outStrings, out)
} }
data = []byte(strings.Join(outStrings, "")) data = []byte(strings.Join(outStrings, ""))
case files.GREPPABLE:
var outStrings []string
for _, r := range result {
outStrings = append(outStrings, dehashedResultGreppable(r)+"\n")
}
data = []byte(strings.Join(outStrings, ""))
default: default:
return errors.New("unsupported file type") return errors.New("unsupported file type")
} }
@@ -90,8 +73,8 @@ func WriteToFile(results sqlite.DehashedResults, outputFile string, fileType fil
return err return err
} }
filePath := fmt.Sprintf("%s.%s", outputFile, fileType.String()) filePath := fmt.Sprintf("%s.%s", outputFile, fileType)
return os.WriteFile(filePath, data, 0644) return ioutil.WriteFile(filePath, data, 0644)
} }
// WriteQueryResultsToFile writes query results to a file in the specified format // WriteQueryResultsToFile writes query results to a file in the specified format
@@ -138,22 +121,6 @@ func WriteQueryResultsToFile(results []map[string]interface{}, outputFile string
outStrings = append(outStrings, strings.Join(rowStrings, "\n")+"\n\n") outStrings = append(outStrings, strings.Join(rowStrings, "\n")+"\n\n")
} }
data = []byte(strings.Join(outStrings, "")) data = []byte(strings.Join(outStrings, ""))
case files.GREPPABLE:
var outStrings []string
for _, r := range results {
keys := make([]string, 0, len(r))
for k := range r {
keys = append(keys, k)
}
sort.Strings(keys)
rowStrings := make([]string, 0, len(keys))
for _, k := range keys {
rowStrings = appendGreppableField(rowStrings, k, greppableAnyValue(r[k]))
}
outStrings = append(outStrings, strings.Join(rowStrings, " ")+"\n")
}
data = []byte(strings.Join(outStrings, ""))
default: default:
return errors.New("unsupported file type") return errors.New("unsupported file type")
} }
@@ -166,59 +133,6 @@ func WriteQueryResultsToFile(results []map[string]interface{}, outputFile string
return os.WriteFile(filePath, data, 0644) return os.WriteFile(filePath, data, 0644)
} }
func dehashedResultGreppable(r sqlite.Result) string {
var fields []string
fields = appendGreppableField(fields, "id", r.DehashedId)
fields = appendGreppableField(fields, "email", strings.Join(r.Email, ","))
fields = appendGreppableField(fields, "ip_address", strings.Join(r.IpAddress, ","))
fields = appendGreppableField(fields, "username", strings.Join(r.Username, ","))
fields = appendGreppableField(fields, "password", strings.Join(r.Password, ","))
fields = appendGreppableField(fields, "hashed_password", strings.Join(r.HashedPassword, ","))
fields = appendGreppableField(fields, "hash_type", r.HashType)
fields = appendGreppableField(fields, "name", strings.Join(r.Name, ","))
fields = appendGreppableField(fields, "vin", strings.Join(r.Vin, ","))
fields = appendGreppableField(fields, "license_plate", strings.Join(r.LicensePlate, ","))
fields = appendGreppableField(fields, "url", strings.Join(r.Url, ","))
fields = appendGreppableField(fields, "social", strings.Join(r.Social, ","))
fields = appendGreppableField(fields, "cryptocurrency_address", strings.Join(r.CryptoCurrencyAddress, ","))
fields = appendGreppableField(fields, "address", strings.Join(r.Address, ","))
fields = appendGreppableField(fields, "phone", strings.Join(r.Phone, ","))
fields = appendGreppableField(fields, "company", strings.Join(r.Company, ","))
fields = appendGreppableField(fields, "database_name", r.DatabaseName)
return strings.Join(fields, " ")
}
func greppableAnyValue(value interface{}) string {
switch v := value.(type) {
case nil:
return ""
case []string:
return greppableValue(strings.Join(v, ","))
case []interface{}:
values := make([]string, 0, len(v))
for _, item := range v {
values = append(values, fmt.Sprintf("%v", item))
}
return greppableValue(strings.Join(values, ","))
case []byte:
return greppableValue(string(v))
default:
return greppableValue(fmt.Sprintf("%v", v))
}
}
func greppableValue(value string) string {
return strings.Join(strings.Fields(value), "_")
}
func appendGreppableField(fields []string, key, value string) []string {
value = greppableValue(value)
if value == "" {
return fields
}
return append(fields, fmt.Sprintf("%s=%s", key, value))
}
func WriteWhoIsHistoryToFile(results []sqlite.HistoryRecord, outputFile string, fileType files.FileType) error { func WriteWhoIsHistoryToFile(results []sqlite.HistoryRecord, outputFile string, fileType files.FileType) error {
var data []byte var data []byte
var err error var err error
-29
View File
@@ -1,29 +0,0 @@
package export
import (
"strings"
"testing"
"hub.krkn.tech/KrakenTech/crowsnest/internal/sqlite"
)
func TestDehashedResultGreppableUsesSpaceSeparatedNonEmptyTokens(t *testing.T) {
got := dehashedResultGreppable(sqlite.Result{
DehashedId: "123",
Name: []string{"Hargrave Mall"},
Address: []string{"irving tx"},
Url: []string{"gdt.com", "GDT.COM"},
})
if strings.Contains(got, "\t") {
t.Fatalf("greppable output contains tab: %q", got)
}
if strings.Contains(got, "vin=") {
t.Fatalf("greppable output contains empty field: %q", got)
}
for _, want := range []string{"id=123", "name=Hargrave_Mall", "address=irving_tx", "url=gdt.com,GDT.COM"} {
if !strings.Contains(got, want) {
t.Fatalf("greppable output = %q, want token %q", got, want)
}
}
}
+3 -4
View File
@@ -1,14 +1,13 @@
package export package export
import ( import (
"crowsnest/internal/files"
"crowsnest/internal/sqlite"
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"os"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"hub.krkn.tech/KrakenTech/crowsnest/internal/files" "os"
"hub.krkn.tech/KrakenTech/crowsnest/internal/sqlite"
) )
func WriteIStringToFile(iString sqlite.IString, outputFile string, fileType files.FileType) error { func WriteIStringToFile(iString sqlite.IString, outputFile string, fileType files.FileType) error {
+2 -9
View File
@@ -1,7 +1,5 @@
package files package files
import "strings"
type FileType int32 type FileType int32
const ( const (
@@ -9,22 +7,19 @@ const (
XML XML
YAML YAML
TEXT TEXT
GREPPABLE
UNKNOWN UNKNOWN
) )
func GetFileType(filetype string) FileType { func GetFileType(filetype string) FileType {
switch strings.ToLower(strings.TrimSpace(filetype)) { switch filetype {
case "json": case "json":
return JSON return JSON
case "xml": case "xml":
return XML return XML
case "yaml": case "yaml":
return YAML return YAML
case "txt", "text": case "txt":
return TEXT return TEXT
case "grep", "greppable":
return GREPPABLE
default: default:
return JSON return JSON
} }
@@ -40,8 +35,6 @@ func (ft FileType) String() string {
return "yaml" return "yaml"
case TEXT: case TEXT:
return "txt" return "txt"
case GREPPABLE:
return "grep"
default: default:
return "json" return "json"
} }
+3 -4
View File
@@ -1,15 +1,14 @@
package hunter_io package hunter_io
import ( import (
"crowsnest/internal/debug"
"crowsnest/internal/sqlite"
"encoding/json" "encoding/json"
"fmt" "fmt"
"go.uber.org/zap"
"io" "io"
"net/http" "net/http"
"strings" "strings"
"go.uber.org/zap"
"hub.krkn.tech/KrakenTech/crowsnest/internal/debug"
"hub.krkn.tech/KrakenTech/crowsnest/internal/sqlite"
) )
const ( const (
+1 -2
View File
@@ -1,11 +1,10 @@
package pretty package pretty
import ( import (
"crowsnest/internal/sqlite"
"fmt" "fmt"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/tree" "github.com/charmbracelet/lipgloss/tree"
"hub.krkn.tech/KrakenTech/crowsnest/internal/sqlite"
) )
func WhoIsTree(root string, record sqlite.WhoisRecord) { func WhoIsTree(root string, record sqlite.WhoisRecord) {
+2 -2
View File
@@ -90,11 +90,11 @@ const (
func GetTable(userInput string) Table { func GetTable(userInput string) Table {
switch strings.ToLower(userInput) { switch strings.ToLower(userInput) {
case "dehashed", "results": case "results":
return ResultsTable return ResultsTable
case "runs": case "runs":
return RunsTable return RunsTable
case "users", "creds": case "creds":
return CredsTable return CredsTable
case "whois": case "whois":
return WhoIsTable return WhoIsTable
-18
View File
@@ -1,18 +0,0 @@
package sqlite
import "testing"
func TestGetTableAcceptsDisplayedTableNames(t *testing.T) {
tests := map[string]Table{
"dehashed": ResultsTable,
"results": ResultsTable,
"users": CredsTable,
"creds": CredsTable,
}
for input, want := range tests {
if got := GetTable(input); got != want {
t.Fatalf("GetTable(%q) = %v, want %v", input, got, want)
}
}
}
+1 -2
View File
@@ -1,12 +1,11 @@
package sqlite package sqlite
import ( import (
"crowsnest/internal/files"
"fmt" "fmt"
"go.uber.org/zap" "go.uber.org/zap"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
"hub.krkn.tech/KrakenTech/crowsnest/internal/files"
) )
type QueryOptions struct { type QueryOptions struct {
+4 -5
View File
@@ -2,16 +2,15 @@ package whois
import ( import (
"bytes" "bytes"
"crowsnest/internal/debug"
"crowsnest/internal/dehashed"
"crowsnest/internal/sqlite"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"go.uber.org/zap"
"io" "io"
"net/http" "net/http"
"go.uber.org/zap"
"hub.krkn.tech/KrakenTech/crowsnest/internal/debug"
"hub.krkn.tech/KrakenTech/crowsnest/internal/dehashed"
"hub.krkn.tech/KrakenTech/crowsnest/internal/sqlite"
) )
type DehashedWHOISSearchRequest struct { type DehashedWHOISSearchRequest struct {