commit a4dffe61bf61701f67a381a71bfc42109f1bf560
Author: Ar1ste1a <107807560+Ar1ste1a@users.noreply.github.com>
Date: Wed May 14 22:00:38 2025 -0400
first commit
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8196a7d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,124 @@
+# Dehasher
+## A cli tool built for interaction with the Dehash API
+
+
+

+
+
+# Features
+- Output Format Control
+- Request Limiting
+- Record Limiting
+- Regular Expression Handling
+- Exact Match Handling
+- Error Handling
+- Credential Dumping
+- Intelligent Token Usage
+# Options
+
+```bash-session
+usage: Dehasher [-h --help] {-k --key} {-a --authorized-email} [-h --help] [-m --max-records] [-r --max-requests] [-B --print-balance] [-X --exact-match] [-R --regex-match] [-t --list-tokens] [-o --output-file-name] [-T --output-txt] [-J --output-json] [-Y --output-yaml] [-x --output-xml] [-U --username-query] [-E --email-query] [-I --ip-address-query] [-P --password-query] [-Q --hashed-password-query] [-N --name-query] [-C --creds-only]
+
+Dehashed Tool
+
+options:
+ -h --help show this help message and exit
+ -m --max-records Maximum amount of records to return
+ -r --max-requests Maximum number of requests to make
+ -B --print-balance Print remaining balance after requests
+ -X --exact-match Use Exact Matching on fields
+ -R --regex-match Use Regex Matching on fields
+ -t --list-tokens List the number of tokens remaining
+ -o --output-file-name File to output results to
+ -T --output-txt Output to text file
+ -J --output-json Output to JSON file
+ -Y --output-yaml Output to YAML file
+ -x --output-xml Output to XML file
+ -U --username-query Username Query
+ -E --email-query Email Query
+ -I --ip-address-query IP Address Query
+ -P --password-query Password Query
+ -Q --hashed-password-query Hashed Password Query
+ -N --name-query Name Query
+ -C --creds-only Return Credentials Only
+ -k --key API Key
+ -a --authorized-email Email to pair with key for authentication
+
+
+v1.0
+```
+
+# Sample Run
+```bash-session
+-k ddq -a ar1ste1a@ -E @example.com -C -o example_creds
+Making 3 Requests for 10000 Records (30000 Total)
+ [*] Performing Request...
+ [*] Retrieved 60 Records
+[-] Not Enough Entries, ending queries
+[+] Discovered 60 Records
+ [*] Writing entries file: example_creds.json
+ [*] Success
+
+```
+
+# Getting Started
+
+To begin, clone the repository
+``` bash-session
+git clone https://github.com/Ar1ste1a/Dehasher.git
+cd Dehasher
+go build dehasher.go
+```
+
+# Crafting a query
+
+## Simple Query
+``` go
+# Provide credentials for emails matching @target.com
+dehasher -k ddq -a ar1ste1a@domain.tld -E @target.com
+```
+
+## Simple Credentials Query
+``` go
+# Provide credentials for emails matching @target.com
+dehasher -k ddq -a ar1ste1a@domain.tld -E @target.com -C
+```
+
+## Simple Query Returning Balance
+``` go
+# Provide credentials for emails matching @target.com
+dehasher -k ddq -a ar1ste1a@domain.tld -E @target.com -C -B
+```
+
+## Regex Query
+``` go
+# Return matches for emails matching this given regex query
+# -R e: Specify the '-E' field as a regex entry
+dehasher -k ddq -a ar1ste1a@domain.tld -E '[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)?@target.com' -C -B -R e
+```
+
+## Exact Match Query
+``` go
+# Return matches for usernames exactly matching "admin"
+# -X u: Specify the '-U' field as an exact match entry
+dehasher -k ddq -a ar1ste1a@domain.tld -C -B -U admin -X u
+```
+
+## Output Text (default JSON)
+``` go
+# Return matches for usernames exactly matching "admin" and write to text file 'admins_file.txt'
+dehasher -k ddq -a ar1ste1a@domain.tld -C -B -U admin -X u -T -o admins_file
+```
+
+## Output YAML
+``` go
+# Return matches for usernames exactly matching "admin" and write to yaml file 'admins_file.yaml'
+dehasher -k ddq -a ar1ste1a@domain.tld -C -B -U admin -X u -Y -o admins_file
+```
+
+## Output XML
+``` go
+# Return matches for usernames exactly matching "admin" and write to xml file 'admins_file.xml'
+dehasher -k ddq -a ar1ste1a@domain.tld -C -B -U admin -X u -x -o admins_file
+```
+
diff --git a/cmd/db.go b/cmd/db.go
new file mode 100644
index 0000000..d8361c9
--- /dev/null
+++ b/cmd/db.go
@@ -0,0 +1,656 @@
+package cmd
+
+import (
+ "dehasher/internal/export"
+ "dehasher/internal/files"
+ "dehasher/internal/pretty"
+ "dehasher/internal/sqlite"
+ "encoding/json"
+ "fmt"
+ "github.com/spf13/cobra"
+ "go.uber.org/zap"
+ "strings"
+ "time"
+)
+
+var (
+ // DB command flags
+ dbPath string
+
+ // DB query command flags
+ usernameDBQuery string
+ emailDBQuery string
+ ipDBQuery string
+ passwordDBQuery string
+ hashDBQuery string
+ nameDBQuery string
+ vinDBQuery string
+ licensePlateDBQuery string
+ addressDBQuery string
+ phoneDBQuery string
+ socialDBQuery string
+ cryptoCurrencyAddressDBQuery string
+ domainDBQuery string
+ limitResultsDB int
+ exactMatchDBQuery bool
+ outputFormatDB string
+ nonEmptyFieldsDBQuery string
+ displayFieldsDBQuery string
+ tableTypeDBQuery string
+
+ // DB runs command flags
+ startDateDBRuns string
+ endDateDBRuns string
+ containsQueryDBRuns string
+ lastXRunsDBRuns int
+
+ // DB command
+ dbCmd = &cobra.Command{
+ Use: "db",
+ Short: "Database operations for Dehasher",
+ Long: `Perform database operations like export, import, and query on the local Dehasher database.`,
+ }
+)
+
+func init() {
+ // Add subcommands to db command
+ dbCmd.AddCommand(dbExportCmd)
+ dbCmd.AddCommand(dbQueryCmd)
+ dbCmd.AddCommand(dbRunsCmd)
+ dbCmd.AddCommand(dbCredsCmd)
+
+ // Add flags specific to db command
+ dbCmd.PersistentFlags().StringVarP(&dbPath, "db-path", "D", "", "Path to database (default: ~/.local/share/Dehasher/dehashed.db)")
+
+ // Add flags specific to db query command
+ dbQueryCmd.Flags().StringVarP(&usernameDBQuery, "username", "u", "", "Filter by username")
+ dbQueryCmd.Flags().StringVarP(&emailDBQuery, "email", "e", "", "Filter by email")
+ dbQueryCmd.Flags().StringVarP(&ipDBQuery, "ip", "i", "", "Filter by IP address")
+ dbQueryCmd.Flags().StringVarP(&passwordDBQuery, "password", "p", "", "Filter by password")
+ dbQueryCmd.Flags().StringVarP(&hashDBQuery, "hash", "H", "", "Filter by hashed password")
+ dbQueryCmd.Flags().StringVarP(&nameDBQuery, "name", "n", "", "Filter by name")
+ dbQueryCmd.Flags().StringVarP(&vinDBQuery, "vin", "v", "", "Filter by VIN")
+ dbQueryCmd.Flags().StringVarP(&licensePlateDBQuery, "license", "L", "", "Filter by license plate")
+ dbQueryCmd.Flags().StringVarP(&addressDBQuery, "address", "a", "", "Filter by address")
+ dbQueryCmd.Flags().StringVarP(&phoneDBQuery, "phone", "P", "", "Filter by phone number")
+ dbQueryCmd.Flags().StringVarP(&socialDBQuery, "social", "s", "", "Filter by social media handle")
+ dbQueryCmd.Flags().StringVarP(&cryptoCurrencyAddressDBQuery, "crypto", "c", "", "Filter by cryptocurrency address")
+ dbQueryCmd.Flags().StringVarP(&domainDBQuery, "domain", "d", "", "Filter by domain/URL")
+ dbQueryCmd.Flags().IntVarP(&limitResultsDB, "limit", "l", 100, "Limit number of results")
+ dbQueryCmd.Flags().BoolVarP(&exactMatchDBQuery, "exact", "x", false, "Use exact matching instead of partial matching")
+ dbQueryCmd.Flags().StringVarP(&outputFormatDB, "format", "f", "table", "Output format (json, table, simple)")
+ dbQueryCmd.Flags().StringVar(&nonEmptyFieldsDBQuery, "non-empty", "", "Filter for non-empty fields (comma-separated list, e.g., 'password,email')")
+ dbQueryCmd.Flags().StringVar(&displayFieldsDBQuery, "display", "", "Fields to display in output (comma-separated list, e.g., 'username,email,password')")
+ dbQueryCmd.Flags().StringVarP(&tableTypeDBQuery, "table", "t", "results", "Table to query (results, runs, creds)")
+
+ // Add flags specific to db runs command
+ dbRunsCmd.Flags().StringVarP(&startDateDBRuns, "start-date", "s", "", "Start date for filtering runs (format: YYYY-MM-DD)")
+ dbRunsCmd.Flags().StringVarP(&endDateDBRuns, "end-date", "e", "", "End date for filtering runs (format: YYYY-MM-DD)")
+ dbRunsCmd.Flags().StringVarP(&containsQueryDBRuns, "contains", "c", "", "Filter runs containing this query string")
+ dbRunsCmd.Flags().IntVarP(&lastXRunsDBRuns, "last", "x", 0, "Show the last X runs")
+ dbRunsCmd.Flags().IntVarP(&limitResultsDB, "limit", "l", 100, "Limit number of results")
+ dbRunsCmd.Flags().StringVarP(&outputFormatDB, "format", "f", "table", "Output format (json, table, simple)")
+
+ // Add flags specific to db creds command
+ dbCredsCmd.Flags().StringVarP(&usernameDBQuery, "username", "u", "", "Filter by username")
+ dbCredsCmd.Flags().StringVarP(&emailDBQuery, "email", "e", "", "Filter by email")
+ dbCredsCmd.Flags().StringVarP(&passwordDBQuery, "password", "p", "", "Filter by password")
+ dbCredsCmd.Flags().IntVarP(&limitResultsDB, "limit", "l", 100, "Limit number of results")
+ dbCredsCmd.Flags().BoolVarP(&exactMatchDBQuery, "exact", "x", false, "Use exact matching instead of partial matching")
+ dbCredsCmd.Flags().StringVarP(&outputFormatDB, "format", "f", "table", "Output format (json, table, simple)")
+ dbCredsCmd.Flags().StringVar(&nonEmptyFieldsDBQuery, "non-empty", "", "Filter for non-empty fields (comma-separated list, e.g., 'password,email')")
+ dbCredsCmd.Flags().StringVar(&displayFieldsDBQuery, "display", "", "Fields to display in output (comma-separated list, e.g., 'username,email,password')")
+}
+
+// DB export command
+var dbExportCmd = &cobra.Command{
+ Use: "export",
+ Short: "Export database to file",
+ Run: func(cmd *cobra.Command, args []string) {
+ fmt.Println("Exporting database...")
+ // Create DBOptions with the provided parameters
+ options := &sqlite.DBOptions{
+ Username: usernameDBQuery,
+ Email: emailDBQuery,
+ IPAddress: ipDBQuery,
+ Password: passwordDBQuery,
+ HashedPassword: hashDBQuery,
+ Name: nameDBQuery,
+ Limit: limitResultsDB,
+ ExactMatch: exactMatchDBQuery,
+ }
+
+ // Parse non-empty fields if provided
+ if nonEmptyFieldsDBQuery != "" {
+ options.NonEmptyFields = strings.Split(nonEmptyFieldsDBQuery, ",")
+ }
+
+ // Parse display fields if provided
+ if displayFieldsDBQuery != "" {
+ options.DisplayFields = strings.Split(displayFieldsDBQuery, ",")
+ }
+
+ // Check if at least one search parameter is provided
+ if options.Username == "" && options.Email == "" && options.IPAddress == "" &&
+ options.Password == "" && options.HashedPassword == "" && options.Name == "" &&
+ len(options.NonEmptyFields) == 0 {
+ fmt.Println("Error: At least one search parameter is required.")
+ cmd.Help()
+ return
+ }
+
+ // Get the count of matching results
+ count, err := sqlite.GetResultsCount(options)
+ if err != nil {
+ fmt.Printf("Error counting results: %v\n", err)
+ return
+ }
+
+ // Query the database
+ results, err := sqlite.QueryResults(options)
+ if err != nil {
+ fmt.Printf("Error querying database: %v\n", err)
+ return
+ }
+ dhResults := sqlite.DehashedResults{Results: results}
+
+ fmt.Printf("Found %d results (showing %d):\n", count, len(results))
+
+ // Output results based on format
+ ft := files.GetFileType(outputFormatDB)
+ err = export.WriteToFile(dhResults, "dehasher_export", ft)
+ if err != nil {
+ zap.L().Error("write_to_file",
+ zap.String("message", "failed to write to file"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error writing to file: %v\n", err)
+ return
+ }
+ fmt.Printf("Exported successfully to file: dehasher_export%s\n", ft.Extension())
+ },
+}
+
+// DB query command
+var dbQueryCmd = &cobra.Command{
+ Use: "query",
+ Short: "Query local database",
+ Long: `Query the local database for previously run dehasher queries based on various parameters.`,
+ Run: func(cmd *cobra.Command, args []string) {
+ // Determine which table to query based on the tableTypeDBQuery parameter
+ switch tableTypeDBQuery {
+ case "results":
+ queryResultsTable(cmd)
+ case "runs":
+ queryRunsTable()
+ case "creds":
+ queryCredsTable(cmd)
+ default:
+ fmt.Printf("Error: Unknown table type '%s'. Valid options are: results, runs, creds\n", tableTypeDBQuery)
+ cmd.Help()
+ return
+ }
+ },
+}
+
+func queryRunsTable() {
+ // Parse date strings to time.Time
+ var startDate, endDate time.Time
+ var err error
+
+ if startDateDBRuns != "" {
+ startDate, err = time.Parse("2006-01-02", startDateDBRuns)
+ if err != nil {
+ fmt.Printf("Error parsing start date: %v\n", err)
+ return
+ }
+ }
+
+ if endDateDBRuns != "" {
+ endDate, err = time.Parse("2006-01-02", endDateDBRuns)
+ if err != nil {
+ fmt.Printf("Error parsing end date: %v\n", err)
+ return
+ }
+ // Set end date to end of day
+ endDate = endDate.Add(24*time.Hour - time.Second)
+ }
+
+ // Get the count of matching runs
+ count, err := sqlite.GetRunsCount(lastXRunsDBRuns, startDate, endDate, containsQueryDBRuns)
+ if err != nil {
+ fmt.Printf("Error counting runs: %v\n", err)
+ return
+ }
+
+ // Query the database
+ runs, err := sqlite.QueryRuns(limitResultsDB, lastXRunsDBRuns, startDate, endDate, containsQueryDBRuns)
+ if err != nil {
+ fmt.Printf("Error querying runs: %v\n", err)
+ return
+ }
+
+ displayRunsResults(count, runs)
+}
+
+func displayRunsResults(count int64, runs []sqlite.QueryOptions) {
+ // Display the results
+ fmt.Printf("Found %d runs (showing %d):\n", count, len(runs))
+
+ if len(runs) == 0 {
+ fmt.Println("No runs found.")
+ return
+ }
+
+ // Output results based on format
+ switch outputFormatDB {
+ case "json":
+ data, err := json.MarshalIndent(runs, "", " ")
+ if err != nil {
+ fmt.Printf("Error formatting results: %v\n", err)
+ return
+ }
+ fmt.Println(string(data))
+ case "table":
+ // Define headers and rows for the table
+ headers := []string{"ID", "Created At", "Max Records", "Username Query", "Email Query", "IP Query", "Password Query", "Hash Query", "Name Query", "Domain Query"}
+ rows := make([][]string, len(runs))
+
+ for i, run := range runs {
+ rows[i] = []string{
+ fmt.Sprintf("%d", run.ID),
+ run.CreatedAt.Format("2006-01-02 15:04:05"),
+ fmt.Sprintf("%d", run.MaxRecords),
+ truncate(run.UsernameQuery, 20),
+ truncate(run.EmailQuery, 20),
+ truncate(run.IpQuery, 20),
+ truncate(run.PassQuery, 20),
+ truncate(run.HashQuery, 20),
+ truncate(run.NameQuery, 20),
+ truncate(run.DomainQuery, 20),
+ }
+ }
+
+ pretty.Table(headers, rows)
+ default:
+ // Simple output
+ for _, run := range runs {
+ fmt.Printf("Run ID: %d\n", run.ID)
+ fmt.Printf(" Created At: %s\n", run.CreatedAt.Format("2006-01-02 15:04:05"))
+ fmt.Printf(" Max Records: %d\n", run.MaxRecords)
+ fmt.Printf(" Max Requests: %d\n", run.MaxRequests)
+ fmt.Printf(" Starting Page: %d\n", run.StartingPage)
+ fmt.Printf(" Output Format: %s\n", run.OutputFormat.String())
+ fmt.Printf(" Output File: %s\n", run.OutputFile)
+ fmt.Printf(" Regex Match: %t\n", run.RegexMatch)
+ fmt.Printf(" Wildcard Match: %t\n", run.WildcardMatch)
+ fmt.Printf(" Username Query: %s\n", run.UsernameQuery)
+ fmt.Printf(" Email Query: %s\n", run.EmailQuery)
+ fmt.Printf(" IP Query: %s\n", run.IpQuery)
+ fmt.Printf(" Password Query: %s\n", run.PassQuery)
+ fmt.Printf(" Hash Query: %s\n", run.HashQuery)
+ fmt.Printf(" Name Query: %s\n", run.NameQuery)
+ fmt.Printf(" Domain Query: %s\n", run.DomainQuery)
+ fmt.Printf(" VIN Query: %s\n", run.VinQuery)
+ fmt.Printf(" License Plate Query: %s\n", run.LicensePlateQuery)
+ fmt.Printf(" Address Query: %s\n", run.AddressQuery)
+ fmt.Printf(" Phone Query: %s\n", run.PhoneQuery)
+ fmt.Printf(" Social Query: %s\n", run.SocialQuery)
+ fmt.Printf(" Crypto Address Query: %s\n", run.CryptoAddressQuery)
+ fmt.Printf(" Print Balance: %t\n", run.PrintBalance)
+ fmt.Printf(" Creds Only: %t\n", run.CredsOnly)
+ fmt.Println()
+ }
+ }
+}
+
+// queryResultsTable queries the results table
+func queryResultsTable(cmd *cobra.Command) {
+ // Create DBOptions with the provided parameters
+ options := &sqlite.DBOptions{
+ Username: usernameDBQuery,
+ Email: emailDBQuery,
+ IPAddress: ipDBQuery,
+ Password: passwordDBQuery,
+ HashedPassword: hashDBQuery,
+ Name: nameDBQuery,
+ Vin: vinDBQuery,
+ LicensePlate: licensePlateDBQuery,
+ Address: addressDBQuery,
+ Phone: phoneDBQuery,
+ Social: socialDBQuery,
+ CryptoCurrencyAddress: cryptoCurrencyAddressDBQuery,
+ Domain: domainDBQuery,
+ Limit: limitResultsDB,
+ ExactMatch: exactMatchDBQuery,
+ }
+
+ // Parse non-empty fields if provided
+ if nonEmptyFieldsDBQuery != "" {
+ options.NonEmptyFields = strings.Split(nonEmptyFieldsDBQuery, ",")
+ }
+
+ // Parse display fields if provided
+ if displayFieldsDBQuery != "" {
+ options.DisplayFields = strings.Split(displayFieldsDBQuery, ",")
+ }
+
+ // Check if at least one search parameter is provided
+ if options.Empty() {
+ fmt.Println("Error: At least one search parameter is required.")
+ cmd.Help()
+ return
+ }
+
+ // Get the count of matching results
+ count, err := sqlite.GetResultsCount(options)
+ if err != nil {
+ fmt.Printf("Error counting results: %v\n", err)
+ return
+ }
+
+ // Query the database
+ results, err := sqlite.QueryResults(options)
+ if err != nil {
+ fmt.Printf("Error querying database: %v\n", err)
+ return
+ }
+
+ // Display the results
+ displayResultsTable(count, results, options)
+}
+
+// displayResultsTable displays the results from the results table
+func displayResultsTable(count int64, results []sqlite.Result, options *sqlite.DBOptions) {
+ // Display the results
+ fmt.Printf("Found %d results (showing %d):\n", count, len(results))
+
+ if len(results) == 0 {
+ fmt.Println("No results found.")
+ return
+ }
+
+ // Output results based on format
+ switch outputFormatDB {
+ case "json":
+ data, err := json.MarshalIndent(results, "", " ")
+ if err != nil {
+ fmt.Printf("Error formatting results: %v\n", err)
+ return
+ }
+ fmt.Println(string(data))
+ case "table":
+ // Determine which fields to display
+ type FieldInfo struct {
+ Name string
+ Width int
+ Getter func(result sqlite.Result) string
+ }
+
+ // Define all available fields
+ allFields := []FieldInfo{
+ {"Username", 20, func(r sqlite.Result) string { return truncate(arrayToString(r.Username), 20) }},
+ {"Email", 30, func(r sqlite.Result) string { return truncate(arrayToString(r.Email), 30) }},
+ {"IP Address", 15, func(r sqlite.Result) string { return truncate(arrayToString(r.IpAddress), 15) }},
+ {"Password", 20, func(r sqlite.Result) string { return truncate(arrayToString(r.Password), 20) }},
+ {"Hashed Password", 20, func(r sqlite.Result) string { return truncate(arrayToString(r.HashedPassword), 20) }},
+ {"Name", 20, func(r sqlite.Result) string { return truncate(arrayToString(r.Name), 20) }},
+ {"VIN", 20, func(r sqlite.Result) string { return truncate(arrayToString(r.Vin), 20) }},
+ {"License Plate", 15, func(r sqlite.Result) string { return truncate(arrayToString(r.LicensePlate), 15) }},
+ {"Address", 30, func(r sqlite.Result) string { return truncate(arrayToString(r.Address), 30) }},
+ {"Phone", 15, func(r sqlite.Result) string { return truncate(arrayToString(r.Phone), 15) }},
+ {"Social", 20, func(r sqlite.Result) string { return truncate(arrayToString(r.Social), 20) }},
+ {"Crypto Address", 20, func(r sqlite.Result) string { return truncate(arrayToString(r.CryptoCurrencyAddress), 20) }},
+ {"Domain/URL", 30, func(r sqlite.Result) string { return truncate(arrayToString(r.Url), 30) }},
+ }
+
+ // Select fields to display
+ var fieldsToDisplay []FieldInfo
+ var headers []string
+ if len(options.DisplayFields) > 0 {
+ // Use specified fields
+ for _, fieldName := range options.DisplayFields {
+ fieldName = strings.ToLower(strings.TrimSpace(fieldName))
+ for _, field := range allFields {
+ if strings.ToLower(field.Name) == fieldName ||
+ (fieldName == "ip" && strings.ToLower(field.Name) == "ip address") ||
+ (fieldName == "hash" && strings.ToLower(field.Name) == "hashed password") ||
+ (fieldName == "license" && strings.ToLower(field.Name) == "license plate") ||
+ (fieldName == "crypto" && strings.ToLower(field.Name) == "crypto address") ||
+ (fieldName == "url" && strings.ToLower(field.Name) == "domain/url") {
+ fieldsToDisplay = append(fieldsToDisplay, field)
+ headers = append(headers, field.Name)
+ break
+ }
+ }
+ }
+ } else {
+ // Default fields (first 6)
+ fieldsToDisplay = allFields[:6]
+ }
+
+ var rows [][]string
+ for _, result := range results {
+ rowValues := []string{}
+ for _, field := range fieldsToDisplay {
+ rowValues = append(rowValues, field.Getter(result))
+ }
+ rows = append(rows, rowValues)
+ }
+
+ pretty.Table(headers, rows)
+ default:
+ // Simple output
+ for i, result := range results {
+ fmt.Printf("Result %d:\n", i+1)
+
+ // Determine which fields to display
+ if len(options.DisplayFields) > 0 {
+ // Display only specified fields
+ for _, field := range options.DisplayFields {
+ field = strings.ToLower(strings.TrimSpace(field))
+ switch field {
+ case "username":
+ fmt.Printf(" Username: %s\n", result.Username)
+ case "email":
+ fmt.Printf(" Email: %s\n", result.Email)
+ case "ip", "ipaddress", "ip_address":
+ fmt.Printf(" IP Address: %s\n", result.IpAddress)
+ case "password":
+ fmt.Printf(" Password: %s\n", result.Password)
+ case "hash", "hashed_password":
+ fmt.Printf(" Hashed Password: %s\n", result.HashedPassword)
+ case "name":
+ fmt.Printf(" Name: %s\n", result.Name)
+ case "vin":
+ fmt.Printf(" VIN: %s\n", result.Vin)
+ case "license", "license_plate":
+ fmt.Printf(" License Plate: %s\n", result.LicensePlate)
+ case "address":
+ fmt.Printf(" Address: %s\n", result.Address)
+ case "phone":
+ fmt.Printf(" Phone: %s\n", result.Phone)
+ case "social":
+ fmt.Printf(" Social: %s\n", result.Social)
+ case "crypto", "cryptocurrency_address":
+ fmt.Printf(" Crypto Address: %s\n", result.CryptoCurrencyAddress)
+ case "domain", "url":
+ fmt.Printf(" Domain/URL: %s\n", result.Url)
+ }
+ }
+ } else {
+ // Display default fields
+ fmt.Printf(" Username: %s\n", result.Username)
+ fmt.Printf(" Email: %s\n", result.Email)
+ fmt.Printf(" IP Address: %s\n", result.IpAddress)
+ fmt.Printf(" Password: %s\n", result.Password)
+ fmt.Printf(" Hashed Password: %s\n", result.HashedPassword)
+ fmt.Printf(" Name: %s\n", result.Name)
+ }
+ fmt.Println()
+ }
+ }
+}
+
+// truncate truncates a string to the specified length and adds ellipsis if needed
+func truncate(s string, length int) string {
+ if len(s) <= length {
+ return s
+ }
+ return s[:length-3] + "..."
+}
+
+func arrayToString(a []string) string {
+ return strings.Join(a, ", ")
+}
+
+// DB runs command
+var dbRunsCmd = &cobra.Command{
+ Use: "runs",
+ Short: "Query previous query runs",
+ Long: `Query the database for previous query runs (QueryOptions) based on date range and query content.`,
+ Run: func(cmd *cobra.Command, args []string) {
+ // Call queryRunsTable directly
+ queryRunsTable()
+ },
+}
+
+// DB creds command
+var dbCredsCmd = &cobra.Command{
+ Use: "creds",
+ Short: "Query credentials",
+ Long: `Query the database for credentials based on username, email, and password.`,
+ Run: func(cmd *cobra.Command, args []string) {
+ // Call queryCredsTable directly
+ queryCredsTable(cmd)
+ },
+}
+
+// queryCredsTable queries the credentials table
+func queryCredsTable(cmd *cobra.Command) {
+ // Create DBOptions with the provided parameters
+ options := &sqlite.DBOptions{
+ Username: usernameDBQuery,
+ Email: emailDBQuery,
+ Password: passwordDBQuery,
+ Limit: limitResultsDB,
+ ExactMatch: exactMatchDBQuery,
+ }
+
+ // Parse non-empty fields if provided
+ if nonEmptyFieldsDBQuery != "" {
+ options.NonEmptyFields = strings.Split(nonEmptyFieldsDBQuery, ",")
+ }
+
+ // Parse display fields if provided
+ if displayFieldsDBQuery != "" {
+ options.DisplayFields = strings.Split(displayFieldsDBQuery, ",")
+ }
+
+ // Check if at least one search parameter is provided
+ if options.Username == "" && options.Email == "" && options.Password == "" && len(options.NonEmptyFields) == 0 {
+ fmt.Println("Error: At least one search parameter is required.")
+ cmd.Help()
+ return
+ }
+
+ // Get the count of matching credentials
+ count, err := sqlite.GetCredsCount(options)
+ if err != nil {
+ fmt.Printf("Error counting credentials: %v\n", err)
+ return
+ }
+
+ // Query the database
+ creds, err := sqlite.QueryCreds(options)
+ if err != nil {
+ fmt.Printf("Error querying credentials: %v\n", err)
+ return
+ }
+
+ // Display the results
+ displayCredsResults(count, creds)
+}
+
+// displayCredsResults displays the results from the creds table
+func displayCredsResults(count int64, creds []sqlite.Creds) {
+ // Display the results
+ fmt.Printf("Found %d credentials (showing %d):\n", count, len(creds))
+
+ if len(creds) == 0 {
+ fmt.Println("No credentials found.")
+ return
+ }
+
+ // Output results based on format
+ switch outputFormatDB {
+ case "json":
+ data, err := json.MarshalIndent(creds, "", " ")
+ if err != nil {
+ fmt.Printf("Error formatting results: %v\n", err)
+ return
+ }
+ fmt.Println(string(data))
+ case "table":
+ // Define all available fields
+ type FieldInfo struct {
+ Name string
+ Getter func(cred sqlite.Creds) string
+ }
+
+ allFields := []FieldInfo{
+ {"ID", func(c sqlite.Creds) string { return fmt.Sprintf("%d", c.ID) }},
+ {"Created At", func(c sqlite.Creds) string { return c.CreatedAt.Format("2006-01-02 15:04:05") }},
+ {"Email", func(c sqlite.Creds) string { return c.Email }},
+ {"Username", func(c sqlite.Creds) string { return c.Username }},
+ {"Password", func(c sqlite.Creds) string { return c.Password }},
+ }
+
+ // Select fields to display
+ var fieldsToDisplay []FieldInfo
+ var headers []string
+
+ if len(displayFieldsDBQuery) > 0 {
+ // Use specified display fields
+ displayFields := strings.Split(displayFieldsDBQuery, ",")
+ for _, fieldName := range displayFields {
+ fieldName = strings.ToLower(strings.TrimSpace(fieldName))
+ for _, field := range allFields {
+ if strings.ToLower(field.Name) == fieldName {
+ fieldsToDisplay = append(fieldsToDisplay, field)
+ headers = append(headers, field.Name)
+ break
+ }
+ }
+ }
+ } else {
+ // Default fields
+ fieldsToDisplay = allFields
+ for _, field := range fieldsToDisplay {
+ headers = append(headers, field.Name)
+ }
+ }
+
+ // Create rows
+ rows := make([][]string, len(creds))
+ for i, cred := range creds {
+ rowValues := []string{}
+ for _, field := range fieldsToDisplay {
+ rowValues = append(rowValues, field.Getter(cred))
+ }
+ rows[i] = rowValues
+ }
+
+ pretty.Table(headers, rows)
+ default:
+ // Simple output
+ for _, cred := range creds {
+ fmt.Printf("Credential ID: %d\n", cred.ID)
+ fmt.Printf(" Created At: %s\n", cred.CreatedAt.Format("2006-01-02 15:04:05"))
+ fmt.Printf(" Email: %s\n", cred.Email)
+ fmt.Printf(" Username: %s\n", cred.Username)
+ fmt.Printf(" Password: %s\n", cred.Password)
+ fmt.Println()
+ }
+ }
+}
diff --git a/cmd/query.go b/cmd/query.go
new file mode 100644
index 0000000..cd9860e
--- /dev/null
+++ b/cmd/query.go
@@ -0,0 +1,137 @@
+package cmd
+
+import (
+ "dehasher/internal/badger"
+ "dehasher/internal/query"
+ "dehasher/internal/sqlite"
+ "fmt"
+ "github.com/spf13/cobra"
+)
+
+var (
+ // Query command flags
+ maxRecords int
+ maxRequests int
+ startingPage int
+ credsOnly bool
+ printBalance bool
+ regexMatch bool
+ wildcardMatch bool
+ outputFormat string
+ outputFile string
+ usernameQuery string
+ emailQuery string
+ ipQuery string
+ passwordQuery string
+ hashQuery string
+ nameQuery string
+ domainQuery string
+ vinQuery string
+ licensePlateQuery string
+ addressQuery string
+ phoneQuery string
+ socialQuery string
+ cryptoCurrencyAddressQuery string
+
+ // Query command
+ queryCmd = &cobra.Command{
+ Use: "query",
+ Short: "Query the Dehashed API",
+ Long: `Query the Dehashed API for emails, usernames, passwords, hashes, IP addresses, and names.`,
+ Run: func(cmd *cobra.Command, args []string) {
+ // Check if API key and email are provided
+ key := apiKey
+ email := apiEmail
+
+ // If not provided as flags, try to get from stored values
+ if key == "" {
+ key = getStoredApiKey()
+ }
+ if email == "" {
+ email = getStoredApiEmail()
+ }
+
+ // Validate credentials
+ if key == "" || email == "" {
+ fmt.Println("API key and email are required. Use --key and --email flags or set them with set-key and set-email commands.")
+ return
+ }
+
+ // Create new QueryOptions
+ queryOptions := sqlite.NewQueryOptions(
+ maxRecords,
+ maxRequests,
+ startingPage,
+ outputFormat,
+ outputFile,
+ usernameQuery,
+ emailQuery,
+ ipQuery,
+ passwordQuery,
+ hashQuery,
+ nameQuery,
+ domainQuery,
+ vinQuery,
+ licensePlateQuery,
+ addressQuery,
+ phoneQuery,
+ socialQuery,
+ cryptoCurrencyAddressQuery,
+ regexMatch,
+ wildcardMatch,
+ printBalance,
+ credsOnly,
+ )
+
+ // Create new Dehasher
+ dehasher := query.NewDehasher(queryOptions)
+ dehasher.SetClientCredentials(
+ key,
+ )
+
+ // Start querying
+ dehasher.Start()
+ fmt.Println("\n[*] Completing Process")
+
+ sqlite.StoreQueryOptions(queryOptions)
+ },
+ }
+)
+
+func init() {
+ // Add flags specific to query command
+ queryCmd.Flags().IntVarP(&maxRecords, "max-records", "m", 30000, "Maximum amount of records to return")
+ queryCmd.Flags().IntVarP(&maxRequests, "max-requests", "r", -1, "Maximum number of requests to make")
+ queryCmd.Flags().IntVarP(&startingPage, "starting-page", "s", 1, "Starting page for requests")
+ queryCmd.Flags().BoolVarP(&printBalance, "print-balance", "b", false, "Print remaining balance after requests")
+ queryCmd.Flags().BoolVarP(®exMatch, "regex-match", "R", false, "Use regex matching on query fields")
+ queryCmd.Flags().BoolVarP(&wildcardMatch, "wildcard-match", "W", false, "Use wildcard matching on query fields (Use ? to replace a single character, and * for multiple characters)")
+ queryCmd.Flags().BoolVarP(&credsOnly, "creds-only", "C", false, "Return credentials only")
+ queryCmd.Flags().StringVarP(&outputFormat, "format", "f", "json", "Output format (json, yaml, xml, txt)")
+ queryCmd.Flags().StringVarP(&outputFile, "output", "o", "query", "File to output results to including extension")
+ queryCmd.Flags().StringVarP(&usernameQuery, "username", "U", "", "Username query")
+ queryCmd.Flags().StringVarP(&emailQuery, "email-query", "E", "", "Email query")
+ queryCmd.Flags().StringVarP(&ipQuery, "ip", "I", "", "IP address query")
+ queryCmd.Flags().StringVarP(&domainQuery, "domain", "D", "", "Domain query")
+ queryCmd.Flags().StringVarP(&passwordQuery, "password", "P", "", "Password query")
+ queryCmd.Flags().StringVarP(&vinQuery, "vin", "V", "", "VIN query")
+ queryCmd.Flags().StringVarP(&licensePlateQuery, "license", "L", "", "License plate query")
+ queryCmd.Flags().StringVarP(&addressQuery, "address", "A", "", "Address query")
+ queryCmd.Flags().StringVarP(&phoneQuery, "phone", "M", "", "Phone query")
+ queryCmd.Flags().StringVarP(&socialQuery, "social", "S", "", "Social query")
+ queryCmd.Flags().StringVarP(&cryptoCurrencyAddressQuery, "crypto", "B", "", "Crypto currency address query")
+ queryCmd.Flags().StringVarP(&hashQuery, "hash", "Q", "", "Hashed password query")
+ queryCmd.Flags().StringVarP(&nameQuery, "name", "N", "", "Name query")
+
+ // Add mutually exclusive flags to exact match and regex match
+ queryCmd.MarkFlagsMutuallyExclusive("regex-match", "wildcard-match")
+}
+
+// Helper functions to get stored API credentials
+func getStoredApiKey() string {
+ return badger.GetKey()
+}
+
+func getStoredApiEmail() string {
+ return badger.GetEmail()
+}
diff --git a/cmd/root.go b/cmd/root.go
new file mode 100644
index 0000000..e651346
--- /dev/null
+++ b/cmd/root.go
@@ -0,0 +1,126 @@
+package cmd
+
+import (
+ "dehasher/internal/badger"
+ "fmt"
+ "github.com/spf13/cobra"
+ "go.uber.org/zap"
+ "os"
+)
+
+var (
+ // Global Flags
+ apiKey string
+ apiEmail string
+
+ // rootCmd is the base command for the CLI.
+ rootCmd = &cobra.Command{
+ Use: "dehasher",
+ Short: `Dehasher is a cli tool for querying query.`,
+ Long: fmt.Sprintf(
+ "%s\n%s",
+ `
+ ______ _______ _______ _______ _______ _______
+( __ \ ( ____ \|\ /|( ___ )( ____ \|\ /|( ____ \( ____ )
+| ( \ )| ( \/| ) ( || ( ) || ( \/| ) ( || ( \/| ( )|
+| | ) || (__ | (___) || (___) || (_____ | (___) || (__ | (____)|
+| | | || __) | ___ || ___ |(_____ )| ___ || __) | __)
+| | ) || ( | ( ) || ( ) | ) || ( ) || ( | (\ (
+| (__/ )| (____/\| ) ( || ) ( |/\____) || ) ( || (____/\| ) \ \__
+(______/ (_______/|/ \||/ \|\_______)|/ \|(_______/|/ \__/
+ An Ar1ste1a Project
+`,
+ `––•–√\/––√\/––•––––•–√\/––√\/––•––––•–√\/––√\/––•––√\/––•––––•–√\/––√\/––•––
+ Dehasher can query the query API for:
+ - Emails - Usernames - Password
+ - Hashes - IP Addresses - Names
+ - VINs - License Plates - Addresses
+ - Phones - Social Media - Crypto Currency Addresses
+ Dehasher supports:
+ - Regex Matching
+ - Exact Matching
+––•–√\/––√\/––•––––•–√\/––√\/––•––––•–√\/––√\/––•––√\/––•––––•–√\/––√\/––•––
+`,
+ ),
+ Version: "v1.0",
+ }
+)
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+func Execute() {
+ if err := rootCmd.Execute(); err != nil {
+ zap.L().Fatal("execute_root_command",
+ zap.String("message", "failed to execute root command"),
+ zap.Error(err),
+ )
+ fmt.Printf("[!] %v", err)
+ os.Exit(1)
+ }
+}
+
+func init() {
+ // Hide the default help command
+ rootCmd.CompletionOptions.HiddenDefaultCmd = true
+
+ // Add global flags for API key and email
+ rootCmd.PersistentFlags().StringVarP(&apiKey, "key", "k", "", "API Key for authentication")
+
+ // Add subcommands
+ rootCmd.AddCommand(dbCmd)
+ rootCmd.AddCommand(queryCmd)
+ rootCmd.AddCommand(setKeyCmd)
+ rootCmd.AddCommand(setEmailCmd)
+}
+
+// Command to set API key
+var setKeyCmd = &cobra.Command{
+ Use: "set-key [key]",
+ Short: "Set and store API key",
+ Args: cobra.ExactArgs(1),
+ Run: func(cmd *cobra.Command, args []string) {
+ key := args[0]
+ // Store key in badger DB
+ err := storeApiKey(key)
+ if err != nil {
+ fmt.Printf("Error storing API key: %v\n", err)
+ return
+ }
+ fmt.Println("API key stored successfully")
+ },
+}
+
+// Command to set API email
+var setEmailCmd = &cobra.Command{
+ Use: "set-email [email]",
+ Short: "Set and store API email",
+ Args: cobra.ExactArgs(1),
+ Run: func(cmd *cobra.Command, args []string) {
+ email := args[0]
+ // Store email in badger DB
+ err := storeApiEmail(email)
+ if err != nil {
+ fmt.Printf("Error storing API email: %v\n", err)
+ return
+ }
+ fmt.Println("API email stored successfully")
+ },
+}
+
+// Helper functions to store API credentials
+func storeApiKey(key string) error {
+ err := badger.StoreKey(key)
+ if err != nil {
+ fmt.Printf("Error storing API key: %v\n", err)
+ return err
+ }
+ return nil
+}
+
+func storeApiEmail(email string) error {
+ err := badger.StoreEmail(email)
+ if err != nil {
+ fmt.Printf("Error storing API email: %v\n", err)
+ return err
+ }
+ return nil
+}
diff --git a/cmd/whois.go b/cmd/whois.go
new file mode 100644
index 0000000..ef58098
--- /dev/null
+++ b/cmd/whois.go
@@ -0,0 +1,303 @@
+package cmd
+
+import (
+ "dehasher/internal/sqlite"
+ "dehasher/internal/whois"
+ "fmt"
+ "github.com/spf13/cobra"
+ "go.uber.org/zap"
+ "strings"
+)
+
+var (
+ // WHOIS command flags
+ whoisDomain string
+ whoisIPAddress string
+ whoisMXAddress string
+ whoisNSAddress string
+ whoisInclude string
+ whoisExclude string
+ whoisReverseType string
+ whoisOutputFormat string
+ whoisShowCredits bool
+ whoisHistory bool
+ whoisSubdomainScan bool
+
+ // WHOIS command
+ whoisCmd = &cobra.Command{
+ Use: "whois",
+ Short: "Dehashed WHOIS lookups and reverse WHOIS searches",
+ Long: `Perform WHOIS lookups, history searches, reverse WHOIS searches, IP lookups, MX lookups, NS lookups, and subdomain scans.`,
+ Run: func(cmd *cobra.Command, args []string) {
+ // Check if API key is provided
+ key := apiKey
+
+ // If not provided as flag, try to get from stored value
+ if key == "" {
+ key = getStoredApiKey()
+ }
+
+ // Validate credentials
+ if key == "" {
+ fmt.Println("API key is required. Use --key flag or set it with set-key command.")
+ return
+ }
+
+ // Show credits if requested
+ if whoisShowCredits {
+ fmt.Println("[*] Getting WHOIS credits...")
+ credits, err := whois.GetWHOISCredits(key)
+ 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)
+ return
+ }
+ fmt.Printf("WHOIS Credits: %d\n", credits.WhoisCredits)
+ return
+ }
+
+ // Check if domain is provided for history and subdomain scan
+ if whoisHistory || whoisSubdomainScan {
+ if whoisDomain == "" {
+ fmt.Println("Domain is required for history and subdomain scan.")
+ return
+ }
+ }
+
+ // Determine which operation to perform based on flags
+ if whoisDomain != "" {
+ fmt.Println("[*] Performing WHOIS lookup...")
+ // Domain lookup
+ result, err := whois.WhoisSearch(whoisDomain, key)
+ if err != nil {
+ zap.L().Error("whois_search",
+ zap.String("message", "failed to perform whois search"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error performing WHOIS lookup: %v\n", err)
+ return
+ }
+
+ // Fix the output format to use proper formatting
+ fmt.Printf("WHOIS Lookup Result:\n%+v\n", result.Data.WhoisRecord)
+
+ // Store the record
+ err = sqlite.StoreWhoisRecord(result.Data.WhoisRecord)
+ if err != nil {
+ zap.L().Error("store_whois_record",
+ zap.String("message", "failed to store whois record"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error storing WHOIS record: %v\n", err)
+ // Continue execution even if storage fails
+ }
+
+ if whoisHistory {
+ fmt.Println("[*] Performing WHOIS history search...")
+ // Perform history search
+ history, err := whois.WhoisHistory(whoisDomain, key)
+ if err != nil {
+ zap.L().Error("whois_history",
+ zap.String("message", "failed to perform whois history lookup"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error performing WHOIS history lookup: %v\n", err)
+ } else {
+ fmt.Println("\nWHOIS History:")
+ fmt.Println(history)
+ }
+
+ err = sqlite.StoreHistoryRecord(history.Data.Records)
+ if err != nil {
+ zap.L().Error("store_history_record",
+ zap.String("message", "failed to store history record"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error storing WHOIS history record: %v\n", err)
+ }
+ }
+
+ // Perform subdomain scan
+ if whoisSubdomainScan {
+ fmt.Println("[*] Performing WHOIS subdomain scan...")
+ subdomains, err := whois.WhoisSubdomainScan(whoisDomain, key)
+ if err != nil {
+ zap.L().Error("whois_subdomain_scan",
+ zap.String("message", "failed to perform subdomain scan"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error performing subdomain scan: %v\n", err)
+ } else {
+ fmt.Println("\nSubdomain Scan:")
+ fmt.Println(subdomains)
+ }
+
+ err = sqlite.StoreSubdomainRecord(subdomains.Data.Result.Records)
+ if err != nil {
+ zap.L().Error("store_subdomain_record",
+ zap.String("message", "failed to store subdomain record"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error storing WHOIS subdomain record: %v\n", err)
+ }
+ }
+
+ // Get credits
+ credits, err := whois.GetWHOISCredits(key)
+ 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)
+ return
+ }
+ fmt.Printf("\nWHOIS Credits Remaining: %d\n", credits.WhoisCredits)
+ return
+ }
+
+ if whoisIPAddress != "" {
+ fmt.Println("[*] Performing reverse IP lookup...")
+ // IP lookup
+ result, err := whois.WhoisIP(whoisIPAddress, key)
+ if err != nil {
+ zap.L().Error("whois_ip",
+ zap.String("message", "failed to perform ip lookup"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error performing IP lookup: %v\n", err)
+ return
+ }
+ fmt.Println("IP Lookup Result:")
+ fmt.Println(string(result))
+
+ // Get credits
+ credits, err := whois.GetWHOISCredits(key)
+ 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)
+ return
+ }
+ fmt.Printf("\nWHOIS Credits Remaining: %d\n", credits.WhoisCredits)
+ return
+ }
+
+ if whoisMXAddress != "" {
+ fmt.Println("[*] Performing reverse MX lookup...")
+ // MX lookup
+ result, err := whois.WhoisMX(whoisMXAddress, key)
+ if err != nil {
+ zap.L().Error("whois_mx",
+ zap.String("message", "failed to perform mx lookup"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error performing MX lookup: %v\n", err)
+ return
+ }
+
+ // todo unmarshal mx lookup
+ fmt.Println("MX Lookup Result:")
+ fmt.Println(result)
+
+ // Get credits
+ credits, err := whois.GetWHOISCredits(key)
+ 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)
+ return
+ }
+ fmt.Printf("\nWHOIS Credits Remaining: %d\n", credits.WhoisCredits)
+ return
+ }
+
+ if whoisNSAddress != "" {
+ fmt.Println("[*] Performing reverse NS lookup...")
+ // NS lookup
+ result, err := whois.WhoisNS(whoisNSAddress, key)
+ if err != nil {
+ zap.L().Error("whois_ns",
+ zap.String("message", "failed to perform ns lookup"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error performing NS lookup: %v\n", err)
+ return
+ }
+ fmt.Println("NS Lookup Result:")
+ fmt.Println(result)
+
+ // Get credits
+ credits, err := whois.GetWHOISCredits(key)
+ 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)
+ return
+ }
+ fmt.Printf("\nWHOIS Credits Remaining: %d\n", credits.WhoisCredits)
+ return
+ }
+
+ if whoisInclude != "" || whoisExclude != "" {
+ // Reverse WHOIS
+ includeTerms := []string{}
+ if whoisInclude != "" {
+ includeTerms = strings.Split(whoisInclude, ",")
+ }
+
+ excludeTerms := []string{}
+ if whoisExclude != "" {
+ excludeTerms = strings.Split(whoisExclude, ",")
+ }
+
+ if whoisReverseType == "" {
+ whoisReverseType = "registrant"
+ }
+
+ fmt.Println("[*] Performing reverse WHOIS lookup...")
+ result, err := whois.ReverseWHOIS(includeTerms, excludeTerms, whoisReverseType, key)
+ if err != nil {
+ fmt.Printf("Error performing reverse WHOIS: %v\n", err)
+ return
+ }
+ fmt.Println("Reverse WHOIS Result:")
+ fmt.Println(result)
+ return
+ }
+
+ // If no specific operation was requested
+ cmd.Help()
+ },
+ }
+)
+
+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")
+
+ // Add API key flag
+ whoisCmd.Flags().StringVarP(&apiKey, "key", "k", "", "Dehashed API key")
+}
diff --git a/counseltrust.txt b/counseltrust.txt
new file mode 100644
index 0000000..6430328
--- /dev/null
+++ b/counseltrust.txt
@@ -0,0 +1,235 @@
+[*] Performing WHOIS lookup...
+WHOIS Lookup Result: %v
+ {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} {2025-03-10 15:29:09 UTC 2025-03-10 15:29:09 UTC} abuse@godaddy.com 2001-09-23T21:20:52Z 2001-09-23 21:20:52 UTC counseltrust.com .com 8633 2025-09-23T21:20:52Z 2025-09-23 21:20:52 UTC {[NS51.DOMAINCONTROL.COM NS52.DOMAINCONTROL.COM] [] NS51.DOMAINCONTROL.COM
+NS52.DOMAINCONTROL.COM
+} 3259 Domain Name: counseltrust.com
+Registry Domain ID: 77675771_DOMAIN_COM-VRSN
+Registrar WHOIS Server: whois.godaddy.com
+Registrar URL: https://www.godaddy.com
+Updated Date: 2024-09-24T12:17:56Z
+Creation Date: 2001-09-23T21:20:52Z
+Registrar Registration Expiration Date: 2025-09-23T21:20:52Z
+Registrar: GoDaddy.com, LLC
+Registrar IANA ID: 146
+Registrar Abuse Contact Email: abuse@godaddy.com
+Registrar Abuse Contact Phone: +1.4806242505
+Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
+Domain Status: clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited
+Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
+Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
+Registry Registrant ID: Not Available From Registry
+Registrant Name: Registration Private
+Registrant Organization: Domains By Proxy, LLC
+Registrant Street: DomainsByProxy.com
+Registrant Street: 100 S. Mill Ave, Suite 1600
+Registrant City: Tempe
+Registrant State/Province: Arizona
+Registrant Postal Code: 85281
+Registrant Country: US
+Registrant Phone: +1.4806242599
+Registrant Phone Ext:
+Registrant Fax:
+Registrant Fax Ext:
+Registrant Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com
+Registry Tech ID: Not Available From Registry
+Tech Name: Registration Private
+Tech Organization: Domains By Proxy, LLC
+Tech Street: DomainsByProxy.com
+Tech Street: 100 S. Mill Ave, Suite 1600
+Tech City: Tempe
+Tech State/Province: Arizona
+Tech Postal Code: 85281
+Tech Country: US
+Tech Phone: +1.4806242599
+Tech Phone Ext:
+Tech Fax:
+Tech Fax Ext:
+Tech Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com
+Name Server: NS51.DOMAINCONTROL.COM
+Name Server: NS52.DOMAINCONTROL.COM
+DNSSEC: unsigned
+URL of the ICANN WHOIS Data Problem Reporting System: http://wdprs.internic.net/
+>>> Last update of WHOIS database: 2025-03-10T15:29:08Z <<<
+For more information on Whois status codes, please visit https://icann.org/epp
+
+TERMS OF USE: The data contained in this registrar's Whois database, while believed by the
+registrar to be reliable, is provided "as is" with no guarantee or warranties regarding its
+accuracy. This information is provided for the sole purpose of assisting you in obtaining
+information about domain name registration records. Any use of this data for any other purpose
+is expressly forbidden without the prior written permission of this registrar. By submitting
+an inquiry, you agree to these terms and limitations of warranty. In particular, you agree not
+to use this data to allow, enable, or otherwise support the dissemination or collection of this
+data, in part or in its entirety, for any purpose, such as transmission by e-mail, telephone,
+postal mail, facsimile or other means of mass unsolicited, commercial advertising or solicitations
+of any kind, including spam. You further agree not to use this data to enable high volume, automated
+or robotic electronic processes designed to collect or compile this data for any purpose, including
+mining this data for your own personal or commercial purposes. Failure to comply with these terms
+may result in termination of access to the Whois database. These terms may be subject to modification
+at any time without notice.
+
+**NOTICE** This WHOIS server is being retired. Please use our RDAP service instead. {Tempe UNITED STATES US Registration Private Domains By Proxy, LLC 85281 Registrant Name: Registration Private
+Registrant Organization: Domains By Proxy, LLC
+Registrant Street: DomainsByProxy.com
+Registrant Street: 100 S. Mill Ave, Suite 1600
+Registrant City: Tempe
+Registrant State/Province: Arizona
+Registrant Postal Code: 85281
+Registrant Country: US
+Registrant Phone: +1.4806242599
+Registrant Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com Arizona DomainsByProxy.com
+100 S. Mill Ave, Suite 1600 14806242599} 146 GoDaddy.com, LLC {{2025-03-10 15:29:04 UTC 2025-03-10 15:29:04 UTC} 2001-09-24T02:20:52Z 2001-09-24 02:20:52 UTC counseltrust.com 2025-09-24T02:20:52Z 2025-09-24 02:20:52 UTC {[NS51.DOMAINCONTROL.COM NS52.DOMAINCONTROL.COM] [] NS51.DOMAINCONTROL.COM
+NS52.DOMAINCONTROL.COM
+} 251 Domain Name: COUNSELTRUST.COM
+ Registry Domain ID: 77675771_DOMAIN_COM-VRSN
+ Registrar WHOIS Server: whois.godaddy.com
+ Registrar URL: http://www.godaddy.com
+ Updated Date: 2024-09-24T17:17:57Z
+ Creation Date: 2001-09-24T02:20:52Z
+ Registry Expiry Date: 2025-09-24T02:20:52Z
+ Registrar: GoDaddy.com, LLC
+ Registrar IANA ID: 146
+ Registrar Abuse Contact Email: abuse@godaddy.com
+ Registrar Abuse Contact Phone: 480-624-2505
+ Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
+ Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
+ Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
+ Domain Status: clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited
+ Name Server: NS51.DOMAINCONTROL.COM
+ Name Server: NS52.DOMAINCONTROL.COM
+ DNSSEC: unsigned
+ URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
+>>> Last update of whois database: 2025-03-10T15:28:57Z <<<
+
+For more information on Whois status codes, please visit https://icann.org/epp
+
+NOTICE: The expiration date displayed in this record is the date the
+registrar's sponsorship of the domain name registration in the registry is
+currently set to expire. This date does not necessarily reflect the expiration
+date of the domain name registrant's agreement with the sponsoring
+registrar. Users may consult the sponsoring registrar's Whois database to
+view the registrar's reported date of expiration for this registration.
+
+TERMS OF USE: You are not authorized to access or query our Whois
+database through the use of electronic processes that are high-volume and
+automated except as reasonably necessary to register domain names or
+modify existing registrations; the Data in VeriSign Global Registry
+Services' ("VeriSign") Whois database is provided by VeriSign for
+information purposes only, and to assist persons in obtaining information
+about or related to a domain name registration record. VeriSign does not
+guarantee its accuracy. By submitting a Whois query, you agree to abide
+by the following terms of use: You agree that you may use this Data only
+for lawful purposes and that under no circumstances will you use this Data
+to: (1) allow, enable, or otherwise support the transmission of mass
+unsolicited, commercial advertising or solicitations via e-mail, telephone,
+or facsimile; or (2) enable high volume, automated, electronic processes
+that apply to VeriSign (or its computer systems). The compilation,
+repackaging, dissemination or other use of this Data is expressly
+prohibited without the prior written consent of VeriSign. You agree not to
+use electronic processes that are automated and high-volume to access or
+query the Whois database except as reasonably necessary to register
+domain names or modify existing registrations. VeriSign reserves the right
+to restrict your access to the Whois database in its sole discretion to ensure
+operational stability. VeriSign may restrict or terminate your access to the
+Whois database for failure to abide by these terms of use. VeriSign
+reserves the right to modify these terms at any time.
+
+The Registry database contains ONLY .COM, .NET, .EDU domains and
+Registrars. 146 GoDaddy.com, LLC clientDeleteProhibited clientRenewProhibited clientTransferProhibited clientUpdateProhibited Domain Name: COUNSELTRUST.COM
+Registry Domain ID: 77675771_DOMAIN_COM-VRSN
+Registrar WHOIS Server: whois.godaddy.com
+Registrar URL: http://www.godaddy.com
+Updated Date: 2024-09-24T17:17:57Z
+Creation Date: 2001-09-24T02:20:52Z
+Registry Expiry Date: 2025-09-24T02:20:52Z
+Registrar: GoDaddy.com, LLC
+Registrar IANA ID: 146
+Registrar Abuse Contact Email: abuse@godaddy.com
+Registrar Abuse Contact Phone: 480-624-2505
+Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
+Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
+Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
+Domain Status: clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited
+Name Server: NS51.DOMAINCONTROL.COM
+Name Server: NS52.DOMAINCONTROL.COM
+DNSSEC: unsigned
+URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
+>>> Last update of whois database: 2025-03-10T15:28:57Z <<<
+For more information on Whois status codes, please visit https://icann.org/epp
+NOTICE: The expiration date displayed in this record is the date the
+registrar's sponsorship of the domain name registration in the registry is
+currently set to expire. This date does not necessarily reflect the expiration
+date of the domain name registrant's agreement with the sponsoring
+registrar. Users may consult the sponsoring registrar's Whois database to
+view the registrar's reported date of expiration for this registration.
+TERMS OF USE: You are not authorized to access or query our Whois
+database through the use of electronic processes that are high-volume and
+automated except as reasonably necessary to register domain names or
+modify existing registrations; the Data in VeriSign Global Registry
+Services' ("VeriSign") Whois database is provided by VeriSign for
+information purposes only, and to assist persons in obtaining information
+about or related to a domain name registration record. VeriSign does not
+guarantee its accuracy. By submitting a Whois query, you agree to abide
+by the following terms of use: You agree that you may use this Data only
+for lawful purposes and that under no circumstances will you use this Data
+to: (1) allow, enable, or otherwise support the transmission of mass
+unsolicited, commercial advertising or solicitations via e-mail, telephone,
+or facsimile; or (2) enable high volume, automated, electronic processes
+that apply to VeriSign (or its computer systems). The compilation,
+repackaging, dissemination or other use of this Data is expressly
+prohibited without the prior written consent of VeriSign. You agree not to
+use electronic processes that are automated and high-volume to access or
+query the Whois database except as reasonably necessary to register
+domain names or modify existing registrations. VeriSign reserves the right
+to restrict your access to the Whois database in its sole discretion to ensure
+operational stability. VeriSign may restrict or terminate your access to the
+Whois database for failure to abide by these terms of use. VeriSign
+reserves the right to modify these terms at any time.
+The Registry database contains ONLY .COM, .NET, .EDU domains and
+Registrars.
+ 2024-09-24T17:17:57Z 2024-09-24 17:17:57 UTC whois.godaddy.com} clientTransferProhibited clientUpdateProhibited clientRenewProhibited clientDeleteProhibited Domain Name: counseltrust.com
+Registrar WHOIS Server: whois.godaddy.com
+Registrar URL: https://www.godaddy.com
+Updated Date: 2024-09-24T12:17:56Z
+Creation Date: 2001-09-23T21:20:52Z
+Registrar Registration Expiration Date: 2025-09-23T21:20:52Z
+Registrar: GoDaddy.com, LLC
+Registrar IANA ID: 146
+Registrar Abuse Contact Email: abuse@godaddy.com
+Registrar Abuse Contact Phone: +1.4806242505
+Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
+Domain Status: clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited
+Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
+Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
+Registrant Name: Registration Private
+Registrant Organization: Domains By Proxy, LLC
+Registrant Street: DomainsByProxy.com
+Registrant Street: 100 S. Mill Ave, Suite 1600
+Registrant City: Tempe
+Registrant State/Province: Arizona
+Registrant Postal Code: 85281
+Registrant Country: US
+Registrant Phone: +1.4806242599
+Registrant Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com
+Tech Name: Registration Private
+Tech Organization: Domains By Proxy, LLC
+Tech Street: DomainsByProxy.com
+Tech Street: 100 S. Mill Ave, Suite 1600
+Tech City: Tempe
+Tech State/Province: Arizona
+Tech Postal Code: 85281
+Tech Country: US
+Tech Phone: +1.4806242599
+Tech Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com
+Name Server: NS51.DOMAINCONTROL.COM
+Name Server: NS52.DOMAINCONTROL.COM
+ {Tempe UNITED STATES US Registration Private Domains By Proxy, LLC 85281 Tech Name: Registration Private
+Tech Organization: Domains By Proxy, LLC
+Tech Street: DomainsByProxy.com
+Tech Street: 100 S. Mill Ave, Suite 1600
+Tech City: Tempe
+Tech State/Province: Arizona
+Tech Postal Code: 85281
+Tech Country: US
+Tech Phone: +1.4806242599
+Tech Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com Arizona DomainsByProxy.com
+100 S. Mill Ave, Suite 1600 14806242599} 2024-09-24T12:17:56Z 2024-09-24 12:17:56 UTC}
diff --git a/counseltrust2.txt b/counseltrust2.txt
new file mode 100644
index 0000000..4efe188
--- /dev/null
+++ b/counseltrust2.txt
@@ -0,0 +1,241 @@
+[*] Performing WHOIS lookup...
+WHOIS Lookup Result:
+{Model:{ID:0 CreatedAt:0001-01-01 00:00:00 +0000 UTC UpdatedAt:0001-01-01 00:00:00 +0000 UTC DeletedAt:{Time:0001-01-01 00:00:00 +0000 UTC Valid:false}} Audit:{CreatedDate:2025-03-10 15:29:09 UTC UpdatedDate:2025-03-10 15:29:09 UTC} ContactEmail:abuse@godaddy.com CreatedDate:2001-09-23T21:20:52Z CreatedDateNormalized:2001-09-23 21:20:52 UTC DomainName:counseltrust.com DomainNameExt:.com EstimatedDomainAge:8633 ExpiresDate:2025-09-23T21:20:52Z ExpiresDateNormalized:2025-09-23 21:20:52 UTC Footer: Header: NameServers:{HostNames:[NS51.DOMAINCONTROL.COM NS52.DOMAINCONTROL.COM] IPs:[] RawText:NS51.DOMAINCONTROL.COM
+NS52.DOMAINCONTROL.COM
+} ParseCode:3259 RawText:Domain Name: counseltrust.com
+Registry Domain ID: 77675771_DOMAIN_COM-VRSN
+Registrar WHOIS Server: whois.godaddy.com
+Registrar URL: https://www.godaddy.com
+Updated Date: 2024-09-24T12:17:56Z
+Creation Date: 2001-09-23T21:20:52Z
+Registrar Registration Expiration Date: 2025-09-23T21:20:52Z
+Registrar: GoDaddy.com, LLC
+Registrar IANA ID: 146
+Registrar Abuse Contact Email: abuse@godaddy.com
+Registrar Abuse Contact Phone: +1.4806242505
+Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
+Domain Status: clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited
+Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
+Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
+Registry Registrant ID: Not Available From Registry
+Registrant Name: Registration Private
+Registrant Organization: Domains By Proxy, LLC
+Registrant Street: DomainsByProxy.com
+Registrant Street: 100 S. Mill Ave, Suite 1600
+Registrant City: Tempe
+Registrant State/Province: Arizona
+Registrant Postal Code: 85281
+Registrant Country: US
+Registrant Phone: +1.4806242599
+Registrant Phone Ext:
+Registrant Fax:
+Registrant Fax Ext:
+Registrant Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com
+Registry Tech ID: Not Available From Registry
+Tech Name: Registration Private
+Tech Organization: Domains By Proxy, LLC
+Tech Street: DomainsByProxy.com
+Tech Street: 100 S. Mill Ave, Suite 1600
+Tech City: Tempe
+Tech State/Province: Arizona
+Tech Postal Code: 85281
+Tech Country: US
+Tech Phone: +1.4806242599
+Tech Phone Ext:
+Tech Fax:
+Tech Fax Ext:
+Tech Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com
+Name Server: NS51.DOMAINCONTROL.COM
+Name Server: NS52.DOMAINCONTROL.COM
+DNSSEC: unsigned
+URL of the ICANN WHOIS Data Problem Reporting System: http://wdprs.internic.net/
+>>> Last update of WHOIS database: 2025-03-10T15:29:08Z <<<
+For more information on Whois status codes, please visit https://icann.org/epp
+
+TERMS OF USE: The data contained in this registrar's Whois database, while believed by the
+registrar to be reliable, is provided "as is" with no guarantee or warranties regarding its
+accuracy. This information is provided for the sole purpose of assisting you in obtaining
+information about domain name registration records. Any use of this data for any other purpose
+is expressly forbidden without the prior written permission of this registrar. By submitting
+an inquiry, you agree to these terms and limitations of warranty. In particular, you agree not
+to use this data to allow, enable, or otherwise support the dissemination or collection of this
+data, in part or in its entirety, for any purpose, such as transmission by e-mail, telephone,
+postal mail, facsimile or other means of mass unsolicited, commercial advertising or solicitations
+of any kind, including spam. You further agree not to use this data to enable high volume, automated
+or robotic electronic processes designed to collect or compile this data for any purpose, including
+mining this data for your own personal or commercial purposes. Failure to comply with these terms
+may result in termination of access to the Whois database. These terms may be subject to modification
+at any time without notice.
+
+**NOTICE** This WHOIS server is being retired. Please use our RDAP service instead. Registrant:{City:Tempe Country:UNITED STATES CountryCode:US Name:Registration Private Organization:Domains By Proxy, LLC PostalCode:85281 RawText:Registrant Name: Registration Private
+Registrant Organization: Domains By Proxy, LLC
+Registrant Street: DomainsByProxy.com
+Registrant Street: 100 S. Mill Ave, Suite 1600
+Registrant City: Tempe
+Registrant State/Province: Arizona
+Registrant Postal Code: 85281
+Registrant Country: US
+Registrant Phone: +1.4806242599
+Registrant Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com State:Arizona Street1:DomainsByProxy.com
+100 S. Mill Ave, Suite 1600 Telephone:14806242599} RegistrarIANAID:146 RegistrarName:GoDaddy.com, LLC RegistryData:{Audit:{CreatedDate:2025-03-10 15:29:04 UTC UpdatedDate:2025-03-10 15:29:04 UTC} CreatedDate:2001-09-24T02:20:52Z CreatedDateNormalized:2001-09-24 02:20:52 UTC DomainName:counseltrust.com ExpiresDate:2025-09-24T02:20:52Z ExpiresDateNormalized:2025-09-24 02:20:52 UTC Footer: Header: NameServers:{HostNames:[NS51.DOMAINCONTROL.COM NS52.DOMAINCONTROL.COM] IPs:[] RawText:NS51.DOMAINCONTROL.COM
+NS52.DOMAINCONTROL.COM
+} ParseCode:251 RawText:Domain Name: COUNSELTRUST.COM
+ Registry Domain ID: 77675771_DOMAIN_COM-VRSN
+ Registrar WHOIS Server: whois.godaddy.com
+ Registrar URL: http://www.godaddy.com
+ Updated Date: 2024-09-24T17:17:57Z
+ Creation Date: 2001-09-24T02:20:52Z
+ Registry Expiry Date: 2025-09-24T02:20:52Z
+ Registrar: GoDaddy.com, LLC
+ Registrar IANA ID: 146
+ Registrar Abuse Contact Email: abuse@godaddy.com
+ Registrar Abuse Contact Phone: 480-624-2505
+ Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
+ Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
+ Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
+ Domain Status: clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited
+ Name Server: NS51.DOMAINCONTROL.COM
+ Name Server: NS52.DOMAINCONTROL.COM
+ DNSSEC: unsigned
+ URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
+>>> Last update of whois database: 2025-03-10T15:28:57Z <<<
+
+For more information on Whois status codes, please visit https://icann.org/epp
+
+NOTICE: The expiration date displayed in this record is the date the
+registrar's sponsorship of the domain name registration in the registry is
+currently set to expire. This date does not necessarily reflect the expiration
+date of the domain name registrant's agreement with the sponsoring
+registrar. Users may consult the sponsoring registrar's Whois database to
+view the registrar's reported date of expiration for this registration.
+
+TERMS OF USE: You are not authorized to access or query our Whois
+database through the use of electronic processes that are high-volume and
+automated except as reasonably necessary to register domain names or
+modify existing registrations; the Data in VeriSign Global Registry
+Services' ("VeriSign") Whois database is provided by VeriSign for
+information purposes only, and to assist persons in obtaining information
+about or related to a domain name registration record. VeriSign does not
+guarantee its accuracy. By submitting a Whois query, you agree to abide
+by the following terms of use: You agree that you may use this Data only
+for lawful purposes and that under no circumstances will you use this Data
+to: (1) allow, enable, or otherwise support the transmission of mass
+unsolicited, commercial advertising or solicitations via e-mail, telephone,
+or facsimile; or (2) enable high volume, automated, electronic processes
+that apply to VeriSign (or its computer systems). The compilation,
+repackaging, dissemination or other use of this Data is expressly
+prohibited without the prior written consent of VeriSign. You agree not to
+use electronic processes that are automated and high-volume to access or
+query the Whois database except as reasonably necessary to register
+domain names or modify existing registrations. VeriSign reserves the right
+to restrict your access to the Whois database in its sole discretion to ensure
+operational stability. VeriSign may restrict or terminate your access to the
+Whois database for failure to abide by these terms of use. VeriSign
+reserves the right to modify these terms at any time.
+
+The Registry database contains ONLY .COM, .NET, .EDU domains and
+Registrars. RegistrarIANAID:146 RegistrarName:GoDaddy.com, LLC Status:clientDeleteProhibited clientRenewProhibited clientTransferProhibited clientUpdateProhibited StrippedText:Domain Name: COUNSELTRUST.COM
+Registry Domain ID: 77675771_DOMAIN_COM-VRSN
+Registrar WHOIS Server: whois.godaddy.com
+Registrar URL: http://www.godaddy.com
+Updated Date: 2024-09-24T17:17:57Z
+Creation Date: 2001-09-24T02:20:52Z
+Registry Expiry Date: 2025-09-24T02:20:52Z
+Registrar: GoDaddy.com, LLC
+Registrar IANA ID: 146
+Registrar Abuse Contact Email: abuse@godaddy.com
+Registrar Abuse Contact Phone: 480-624-2505
+Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
+Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
+Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
+Domain Status: clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited
+Name Server: NS51.DOMAINCONTROL.COM
+Name Server: NS52.DOMAINCONTROL.COM
+DNSSEC: unsigned
+URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
+>>> Last update of whois database: 2025-03-10T15:28:57Z <<<
+For more information on Whois status codes, please visit https://icann.org/epp
+NOTICE: The expiration date displayed in this record is the date the
+registrar's sponsorship of the domain name registration in the registry is
+currently set to expire. This date does not necessarily reflect the expiration
+date of the domain name registrant's agreement with the sponsoring
+registrar. Users may consult the sponsoring registrar's Whois database to
+view the registrar's reported date of expiration for this registration.
+TERMS OF USE: You are not authorized to access or query our Whois
+database through the use of electronic processes that are high-volume and
+automated except as reasonably necessary to register domain names or
+modify existing registrations; the Data in VeriSign Global Registry
+Services' ("VeriSign") Whois database is provided by VeriSign for
+information purposes only, and to assist persons in obtaining information
+about or related to a domain name registration record. VeriSign does not
+guarantee its accuracy. By submitting a Whois query, you agree to abide
+by the following terms of use: You agree that you may use this Data only
+for lawful purposes and that under no circumstances will you use this Data
+to: (1) allow, enable, or otherwise support the transmission of mass
+unsolicited, commercial advertising or solicitations via e-mail, telephone,
+or facsimile; or (2) enable high volume, automated, electronic processes
+that apply to VeriSign (or its computer systems). The compilation,
+repackaging, dissemination or other use of this Data is expressly
+prohibited without the prior written consent of VeriSign. You agree not to
+use electronic processes that are automated and high-volume to access or
+query the Whois database except as reasonably necessary to register
+domain names or modify existing registrations. VeriSign reserves the right
+to restrict your access to the Whois database in its sole discretion to ensure
+operational stability. VeriSign may restrict or terminate your access to the
+Whois database for failure to abide by these terms of use. VeriSign
+reserves the right to modify these terms at any time.
+The Registry database contains ONLY .COM, .NET, .EDU domains and
+Registrars.
+ UpdatedDate:2024-09-24T17:17:57Z UpdatedDateNormalized:2024-09-24 17:17:57 UTC WhoisServer:whois.godaddy.com} Status:clientTransferProhibited clientUpdateProhibited clientRenewProhibited clientDeleteProhibited StrippedText:Domain Name: counseltrust.com
+Registrar WHOIS Server: whois.godaddy.com
+Registrar URL: https://www.godaddy.com
+Updated Date: 2024-09-24T12:17:56Z
+Creation Date: 2001-09-23T21:20:52Z
+Registrar Registration Expiration Date: 2025-09-23T21:20:52Z
+Registrar: GoDaddy.com, LLC
+Registrar IANA ID: 146
+Registrar Abuse Contact Email: abuse@godaddy.com
+Registrar Abuse Contact Phone: +1.4806242505
+Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
+Domain Status: clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited
+Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
+Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
+Registrant Name: Registration Private
+Registrant Organization: Domains By Proxy, LLC
+Registrant Street: DomainsByProxy.com
+Registrant Street: 100 S. Mill Ave, Suite 1600
+Registrant City: Tempe
+Registrant State/Province: Arizona
+Registrant Postal Code: 85281
+Registrant Country: US
+Registrant Phone: +1.4806242599
+Registrant Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com
+Tech Name: Registration Private
+Tech Organization: Domains By Proxy, LLC
+Tech Street: DomainsByProxy.com
+Tech Street: 100 S. Mill Ave, Suite 1600
+Tech City: Tempe
+Tech State/Province: Arizona
+Tech Postal Code: 85281
+Tech Country: US
+Tech Phone: +1.4806242599
+Tech Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com
+Name Server: NS51.DOMAINCONTROL.COM
+Name Server: NS52.DOMAINCONTROL.COM
+ TechnicalContact:{City:Tempe Country:UNITED STATES CountryCode:US Name:Registration Private Organization:Domains By Proxy, LLC PostalCode:85281 RawText:Tech Name: Registration Private
+Tech Organization: Domains By Proxy, LLC
+Tech Street: DomainsByProxy.com
+Tech Street: 100 S. Mill Ave, Suite 1600
+Tech City: Tempe
+Tech State/Province: Arizona
+Tech Postal Code: 85281
+Tech Country: US
+Tech Phone: +1.4806242599
+Tech Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com State:Arizona Street1:DomainsByProxy.com
+100 S. Mill Ave, Suite 1600 Telephone:14806242599} UpdatedDate:2024-09-24T12:17:56Z UpdatedDateNormalized:2024-09-24 12:17:56 UTC}
+[*] Performing WHOIS subdomain scan...
+
+Subdomain Scan:
+{220 {{13 [{{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} cpanel.counseltrust.com 1621908519 1621908519} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} cpcalendars.counseltrust.com 1621908519 1621908519} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} sslvpn.counseltrust.com 1546654106 1546654106} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} stocks.counseltrust.com 1546654702 1546654702} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} cpcontacts.counseltrust.com 1621908519 1621908519} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} ftp.counseltrust.com 1737657659 1737657659} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} mail.counseltrust.com 1621908519 1621908519} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} webmail.counseltrust.com 1621908519 1621908519} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} webdisk.counseltrust.com 1621908519 1621908519} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} autodiscover.counseltrust.com 1743219849 1745735288} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} www.laserfiche.counseltrust.com 1649336639 1712556962} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} laserfiche.counseltrust.com 1565348884 1712556962} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} www.counseltrust.com 1517184000 1745798400}]} counseltrust.com}}
+
+WHOIS Credits Remaining: 220
diff --git a/dehasher b/dehasher
new file mode 100755
index 0000000..4490642
Binary files /dev/null and b/dehasher differ
diff --git a/dehasher.go b/dehasher.go
new file mode 100644
index 0000000..d9900fb
--- /dev/null
+++ b/dehasher.go
@@ -0,0 +1,94 @@
+package main
+
+import (
+ "dehasher/cmd"
+ "dehasher/internal/badger"
+ "dehasher/internal/sqlite"
+ "fmt"
+ "github.com/winking324/rzap"
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+ "gopkg.in/natefinch/lumberjack.v2"
+ "os"
+ "path/filepath"
+)
+
+var (
+ basePath string
+ logPath string
+ storePath string
+ dbPath string
+)
+
+func init() {
+ basePath = filepath.Join(os.Getenv("HOME"), ".local", "share", "Dehasher")
+ logPath = filepath.Join(basePath, "logs")
+ storePath = filepath.Join(basePath, "keystore")
+ dbPath = filepath.Join(basePath, "db")
+}
+
+func createDirectories() {
+ var err error
+
+ if _, err = os.Stat(basePath); os.IsNotExist(err) {
+ err = os.MkdirAll(basePath, 0755)
+ if err != nil {
+ zap.L().Error("Error creating directory", zap.Error(err))
+ fmt.Printf("[!] Error creating base directory: %v", err)
+ os.Exit(-1)
+ }
+ }
+
+ for _, dir := range []string{"logs", "keystore", "db"} {
+ if _, err := os.Stat(filepath.Join(basePath, dir)); os.IsNotExist(err) {
+ err = os.MkdirAll(filepath.Join(basePath, dir), 0755)
+ if err != nil {
+ zap.L().Error("Error creating directory", zap.Error(err), zap.String("directory", dir))
+ fmt.Printf("[!] Error creating directory: %v", err)
+ os.Exit(-1)
+ }
+ }
+ }
+}
+
+func initializeLogger() {
+ rzap.NewGlobalLogger([]zapcore.Core{
+ rzap.NewCore(&lumberjack.Logger{
+ Filename: filepath.Join(logPath, "info.log"),
+ }, zap.LevelEnablerFunc(func(level zapcore.Level) bool {
+ return level <= zap.InfoLevel
+ })),
+ rzap.NewCore(&lumberjack.Logger{
+ Filename: filepath.Join(logPath, "error.log"),
+ }, zap.LevelEnablerFunc(func(level zapcore.Level) bool {
+ return level > zap.InfoLevel
+ })),
+ })
+
+ zap.L().Info("some message", zap.Int("status", 0))
+}
+
+func main() {
+ initializeLogger()
+
+ zap.L().Info("creating_directories")
+ createDirectories()
+
+ zap.L().Info("initializing_database")
+ _, err := sqlite.InitDB(dbPath)
+ if err != nil {
+ zap.L().Error("init_db",
+ zap.String("message", "failed to initialize database"),
+ zap.Error(err),
+ )
+ fmt.Printf("[!] Error initializing database: %v", err)
+ os.Exit(1)
+ }
+
+ zap.L().Info("starting_badger")
+ db := badger.Start(storePath)
+ defer db.Close()
+
+ zap.L().Info("executing_command")
+ cmd.Execute()
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..fa96d9c
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,54 @@
+module dehasher
+
+go 1.23.0
+
+toolchain go1.24.3
+
+require (
+ github.com/charmbracelet/lipgloss v1.1.0
+ github.com/dgraph-io/badger/v4 v4.7.0
+ github.com/spf13/cobra v1.9.1
+ github.com/winking324/rzap v0.1.0
+ go.uber.org/zap v1.20.0
+ gopkg.in/natefinch/lumberjack.v2 v2.0.0
+ gopkg.in/yaml.v3 v3.0.1
+ gorm.io/driver/sqlite v1.5.7
+ gorm.io/gorm v1.26.1
+)
+
+require (
+ github.com/BurntSushi/toml v1.5.0 // indirect
+ github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
+ github.com/charmbracelet/x/ansi v0.8.0 // indirect
+ github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
+ github.com/charmbracelet/x/term v0.2.1 // indirect
+ github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect
+ github.com/dustin/go-humanize v1.0.1 // indirect
+ github.com/go-logr/logr v1.4.2 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/google/flatbuffers v25.2.10+incompatible // indirect
+ github.com/inconshreveable/mousetrap v1.1.0 // indirect
+ github.com/jinzhu/inflection v1.0.0 // indirect
+ github.com/jinzhu/now v1.1.5 // indirect
+ github.com/klauspost/compress v1.18.0 // indirect
+ github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
+ github.com/mattn/go-isatty v0.0.20 // 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/rivo/uniseg v0.4.7 // indirect
+ github.com/spf13/pflag v1.0.6 // indirect
+ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
+ go.opentelemetry.io/auto/sdk v1.1.0 // indirect
+ go.opentelemetry.io/otel v1.35.0 // indirect
+ go.opentelemetry.io/otel/metric v1.35.0 // indirect
+ go.opentelemetry.io/otel/trace v1.35.0 // indirect
+ go.uber.org/atomic v1.7.0 // indirect
+ go.uber.org/multierr v1.6.0 // indirect
+ golang.org/x/net v0.38.0 // indirect
+ golang.org/x/sys v0.31.0 // indirect
+ golang.org/x/text v0.23.0 // indirect
+ google.golang.org/protobuf v1.36.6 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..d1c0bf1
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,160 @@
+github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
+github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
+github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
+github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
+github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
+github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
+github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
+github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
+github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
+github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
+github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
+github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
+github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
+github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
+github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
+github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
+github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
+github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
+github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgraph-io/badger/v4 v4.7.0 h1:Q+J8HApYAY7UMpL8d9owqiB+odzEc0zn/aqOD9jhc6Y=
+github.com/dgraph-io/badger/v4 v4.7.0/go.mod h1:He7TzG3YBy3j4f5baj5B7Zl2XyfNe5bl4Udl0aPemVA=
+github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM=
+github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI=
+github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
+github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=
+github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
+github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
+github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
+github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+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/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/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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
+github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
+github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
+github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
+github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/winking324/rzap v0.1.0 h1:otHb5JwO2l9Alr1MPeww8FAzF+YcmiZMR0EvBZnmlqo=
+github.com/winking324/rzap v0.1.0/go.mod h1:C7Ui70QKYWiN5h4Qk2U0qaTN5yC3ujpKsRgHcXsFWhI=
+github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
+github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
+go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
+go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
+go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
+go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
+go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
+go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
+go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/zap v1.20.0 h1:N4oPlghZwYG55MlU6LXk/Zp00FVNE9X9wrYO8CEs4lc=
+go.uber.org/zap v1.20.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
+golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
+golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
+golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
+google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
+gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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/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/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
diff --git a/internal/badger/badger.go b/internal/badger/badger.go
new file mode 100644
index 0000000..8452fef
--- /dev/null
+++ b/internal/badger/badger.go
@@ -0,0 +1,174 @@
+package badger
+
+import (
+ "crypto/sha256"
+ "github.com/dgraph-io/badger/v4"
+ "go.uber.org/zap"
+ "log"
+ "os"
+ "os/user"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "sync"
+)
+
+var (
+ encryptionKey []byte // must be 32 bytes
+ db *badger.DB
+ rootDir string
+ once sync.Once
+)
+
+func GetHardwareEntropy() []byte {
+ // Get hostname
+ hostname, err := os.Hostname()
+ if err != nil {
+ hostname = "unknown-host"
+ log.Printf("Error getting hostname: %v", err)
+ }
+
+ // Get username
+ currentUser, err := user.Current()
+ username := "unknown-user"
+ if err == nil && currentUser != nil {
+ username = currentUser.Username
+ }
+
+ // Get OS and architecture info
+ osInfo := runtime.GOOS + "-" + runtime.GOARCH
+
+ // Combine all information for a unique but consistent fingerprint
+ fingerprint := strings.Join([]string{
+ hostname,
+ username,
+ osInfo,
+ // You could add a static salt here for additional security
+ "Dehasher-static-salt-value",
+ }, ":")
+
+ // Hash the fingerprint to get a 32-byte key
+ sum := sha256.Sum256([]byte(fingerprint))
+ return sum[:]
+}
+
+func Start(dirPath string) *badger.DB {
+ var err error
+
+ zap.L().Info("Starting Badger DB", zap.String("directory", dirPath))
+ zap.L().Info("Badger DB Directory Path", zap.String("directory", dirPath))
+
+ once.Do(func() {
+ if !strings.HasSuffix(dirPath, "db") {
+ dirPath = filepath.Join(dirPath, "db")
+ }
+ rootDir = dirPath
+
+ encryptionKey = GetHardwareEntropy()
+ if err != nil {
+ zap.L().Fatal("get_encryption_key",
+ zap.String("message", "failed to get encryption key"),
+ zap.Error(err),
+ )
+ }
+
+ badgerDB := filepath.Join(rootDir, "badger.db")
+ opts := badger.DefaultOptions(badgerDB).
+ WithEncryptionKey(encryptionKey).
+ WithIndexCacheSize(10 << 20). // 10MB
+ WithLoggingLevel(badger.ERROR)
+ db, err = badger.Open(opts)
+ if err != nil {
+ zap.L().Fatal("new_badger_db",
+ zap.String("message", "failed to open badger database"),
+ zap.Error(err),
+ )
+ }
+ })
+
+ return db
+}
+
+func Close() {
+ err := db.Close()
+ if err != nil {
+ zap.L().Fatal("new_badger_db",
+ zap.String("message", "failed to close badger database"),
+ zap.Error(err),
+ )
+ }
+}
+
+func GetKey() string {
+ var apiKey string
+
+ err := db.View(func(txn *badger.Txn) error {
+ item, err := txn.Get([]byte("cfg:api_key"))
+ if err != nil {
+ return err // could be ErrKeyNotFound
+ }
+ return item.Value(func(val []byte) error {
+ apiKey = string(val)
+ return nil
+ })
+ })
+
+ if err != nil {
+ zap.L().Error("get_api_key",
+ zap.String("message", "failed to get api_key"),
+ zap.Error(err),
+ )
+ }
+
+ return apiKey
+}
+
+func GetEmail() string {
+ var email string
+
+ err := db.View(func(txn *badger.Txn) error {
+ item, err := txn.Get([]byte("cfg:email"))
+ if err != nil {
+ return err // could be ErrKeyNotFound
+ }
+ return item.Value(func(val []byte) error {
+ email = string(val)
+ return nil
+ })
+ })
+
+ if err != nil {
+ zap.L().Error("get_email",
+ zap.String("message", "failed to get email"),
+ zap.Error(err),
+ )
+ }
+
+ return email
+}
+
+func StoreKey(apiKey string) error {
+ err := db.Update(func(txn *badger.Txn) error {
+ return txn.Set([]byte("cfg:api_key"), []byte(apiKey))
+ })
+ if err != nil {
+ zap.L().Error("set_api_key",
+ zap.String("message", "failed to set api_key"),
+ zap.Error(err),
+ )
+ }
+ return err
+}
+
+func StoreEmail(email string) error {
+ err := db.Update(func(txn *badger.Txn) error {
+ return txn.Set([]byte("cfg:email"), []byte(email))
+ })
+ if err != nil {
+ zap.L().Error("set_email",
+ zap.String("message", "failed to set email"),
+ zap.Error(err),
+ )
+ }
+ return err
+}
diff --git a/internal/export/export.go b/internal/export/export.go
new file mode 100644
index 0000000..17fb675
--- /dev/null
+++ b/internal/export/export.go
@@ -0,0 +1,77 @@
+package export
+
+import (
+ "dehasher/internal/files"
+ "dehasher/internal/sqlite"
+ "encoding/json"
+ "encoding/xml"
+ "errors"
+ "fmt"
+ "gopkg.in/yaml.v3"
+ "io/ioutil"
+ "os"
+ "strings"
+)
+
+func WriteCredsToFile(creds []sqlite.Creds, outputFile string, fileType files.FileType) error {
+ var data []byte
+ var err error
+
+ switch fileType {
+ case files.JSON:
+ data, err = json.MarshalIndent(creds, "", " ")
+ case files.XML:
+ data, err = xml.MarshalIndent(creds, "", " ")
+ case files.YAML:
+ data, err = yaml.Marshal(creds)
+ case files.TEXT:
+ var outStrings []string
+ for _, c := range creds {
+ outStrings = append(outStrings, c.ToString()+"\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 WriteToFile(results sqlite.DehashedResults, outputFile string, fileType files.FileType) error {
+ var data []byte
+ var err error
+
+ result := results.Results
+
+ switch fileType {
+ case files.JSON:
+ data, err = json.MarshalIndent(result, "", " ")
+ case files.XML:
+ data, err = xml.MarshalIndent(result, "", " ")
+ case files.YAML:
+ data, err = yaml.Marshal(result)
+ case files.TEXT:
+ var outStrings []string
+ for _, r := range result {
+ out := fmt.Sprintf(
+ "Id: %s\nEmail: %s\nIpAddress: %s\nUsername: %s\nPassword: %s\nHashedPassword: %s\nHashType: %s\nName: %s\nVin: %s\nAddress: %s\nPhone: %s\nDatabaseName: %s\n\n",
+ r.DehashedId, r.Email, r.IpAddress, r.Username, r.Password, r.HashedPassword, r.HashType, r.Name, r.Vin, r.Address, r.Phone, r.DatabaseName)
+ 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)
+ return ioutil.WriteFile(filePath, data, 0644)
+}
diff --git a/internal/files/filetype.go b/internal/files/filetype.go
new file mode 100644
index 0000000..11cecae
--- /dev/null
+++ b/internal/files/filetype.go
@@ -0,0 +1,44 @@
+package files
+
+type FileType int32
+
+const (
+ JSON FileType = iota
+ XML
+ YAML
+ TEXT
+)
+
+func GetFileType(filetype string) FileType {
+ switch filetype {
+ case "json":
+ return JSON
+ case "xml":
+ return XML
+ case "yaml":
+ return YAML
+ case "txt":
+ return TEXT
+ default:
+ return JSON
+ }
+}
+
+func (ft FileType) String() string {
+ switch ft {
+ case JSON:
+ return "json"
+ case XML:
+ return "xml"
+ case YAML:
+ return "yaml"
+ case TEXT:
+ return "txt"
+ default:
+ return "json"
+ }
+}
+
+func (ft FileType) Extension() string {
+ return "." + ft.String()
+}
diff --git a/internal/pretty/tables.go b/internal/pretty/tables.go
new file mode 100644
index 0000000..8ff571f
--- /dev/null
+++ b/internal/pretty/tables.go
@@ -0,0 +1,48 @@
+package pretty
+
+import (
+ "fmt"
+ "github.com/charmbracelet/lipgloss"
+ "github.com/charmbracelet/lipgloss/table"
+)
+
+var (
+ rows = [][]string{
+ {"Chinese", "您好", "你好"},
+ {"Japanese", "こんにちは", "やあ"},
+ {"Arabic", "أهلين", "أهلا"},
+ {"Russian", "Здравствуйте", "Привет"},
+ {"Spanish", "Hola", "¿Qué tal?"},
+ }
+
+ purple = lipgloss.Color("99")
+ gray = lipgloss.Color("245")
+ lightGray = lipgloss.Color("241")
+
+ headerStyle = lipgloss.NewStyle().Foreground(purple).Bold(true).Align(lipgloss.Center)
+ cellStyle = lipgloss.NewStyle().Padding(0, 1)
+ oddRowStyle = cellStyle.Foreground(gray)
+ evenRowStyle = cellStyle.Foreground(lightGray)
+)
+
+func Table(headers []string, rows [][]string) {
+ t := table.New().
+ Border(lipgloss.NormalBorder()).
+ BorderStyle(lipgloss.NewStyle().Foreground(purple)).
+ StyleFunc(func(row, col int) lipgloss.Style {
+ switch {
+ case row == table.HeaderRow:
+ return headerStyle
+ case row%2 == 0:
+ return evenRowStyle
+ default:
+ return oddRowStyle
+ }
+ }).
+ Headers(headers...).
+ Rows(rows...)
+
+ // You can also add tables row-by-row
+ //t.Row("English", "You look absolutely fabulous.", "How's it going?")
+ fmt.Println(t)
+}
diff --git a/internal/query/client.go b/internal/query/client.go
new file mode 100644
index 0000000..d31e940
--- /dev/null
+++ b/internal/query/client.go
@@ -0,0 +1,111 @@
+package query
+
+import (
+ "dehasher/internal/sqlite"
+ "fmt"
+ "log"
+ "net/http"
+ "net/url"
+ "os"
+ "strings"
+)
+
+type DehashedClient struct {
+ key string
+ email string
+ results []sqlite.Result
+ client *http.Client
+ query string
+ params string
+ printBal bool
+ total int
+ balance int
+}
+
+var baseUrl = "https://api.dehashed.com/v2/search"
+
+func NewDehashedClient(key, email string, printBal bool) *DehashedClient {
+ return &DehashedClient{key: key, email: email, results: make([]sqlite.Result, 0), client: &http.Client{}, printBal: printBal}
+}
+
+func (dc *DehashedClient) getKey() string {
+ return dc.key
+}
+
+func (dc *DehashedClient) getEmail() string {
+ return dc.email
+}
+
+func (dc *DehashedClient) GetResults() sqlite.DehashedResults {
+ return sqlite.DehashedResults{Results: dc.results}
+}
+
+func (dc *DehashedClient) buildQuery(params map[string]string) {
+ urlParams := url.Values{}
+ urlString := baseUrl
+
+ if len(params) > 0 {
+ urlString += "?query="
+
+ for k, v := range params {
+ if len(v) > 0 {
+ urlParams.Add(k, v)
+ }
+ }
+ }
+
+ tmp, _ := url.QueryUnescape(urlParams.Encode())
+ tmp2 := strings.Replace(tmp, "=", ":", -1)
+ dc.params = tmp2
+ urlString += dc.params
+ dc.query = urlString
+}
+
+func (dc *DehashedClient) setResults(results int) {
+ dc.query = fmt.Sprintf("%s?query=%s&size=%d", baseUrl, dc.params, results)
+}
+
+func (dc *DehashedClient) setPage(page int) {
+ dc.query = fmt.Sprintf("%s&nextPage=%d", dc.query, page)
+}
+
+func (dc *DehashedClient) Do() int {
+ fmt.Printf("\n\t[*] Performing Request...")
+ req, err := http.NewRequest("GET", dc.query, nil)
+ if err != nil {
+ fmt.Printf("[!] Error constructing request: %v", err)
+ os.Exit(-1)
+ }
+
+ dc.setAuth(req)
+ req.Header.Add("Dehashed-Api-Key", dc.getKey())
+ req.Header.Add("Accept", "application/json")
+ resp, err := dc.client.Do(req)
+ if err != nil {
+ fmt.Printf("[!] Error performing request: %s\n%v", dc.query, err)
+ os.Exit(-1)
+ }
+
+ if resp.StatusCode != 200 {
+ dhErr := GetDehashedError(resp.StatusCode)
+ fmt.Println()
+ log.Fatal(dhErr.Error())
+ }
+
+ entries, balance, total := sqlite.NewDehashedResults(resp.Body)
+ dc.results = append(dc.results, entries...)
+ dc.balance = balance
+ dc.total += total
+ if dc.printBal {
+ fmt.Printf("\n\t\t[*] Balance Remaining: %d", balance)
+ }
+ return total
+}
+
+func (dc *DehashedClient) setAuth(r *http.Request) {
+ r.SetBasicAuth(dc.email, dc.key)
+}
+
+func (dc *DehashedClient) GetDomains() int {
+ return dc.balance
+}
diff --git a/internal/query/clientv2.go b/internal/query/clientv2.go
new file mode 100644
index 0000000..3412cba
--- /dev/null
+++ b/internal/query/clientv2.go
@@ -0,0 +1,193 @@
+package query
+
+import (
+ "bytes"
+ "crypto/sha256"
+ "dehasher/internal/sqlite"
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "go.uber.org/zap"
+ "io"
+ "net/http"
+ "strings"
+)
+
+type DehashedParameter string
+
+const (
+ Username DehashedParameter = "username"
+ Email DehashedParameter = "email"
+ Password DehashedParameter = "password"
+ HashedPassword DehashedParameter = "hashed_password"
+ Name DehashedParameter = "name"
+ IpAddress DehashedParameter = "ip_address"
+ Domain DehashedParameter = "domain"
+ Vin DehashedParameter = "vin"
+ LicensePlate DehashedParameter = "license_plate"
+ Address DehashedParameter = "address"
+ Phone DehashedParameter = "phone"
+ Social DehashedParameter = "social"
+ CryptoAddress DehashedParameter = "cryptocurrency_address"
+)
+
+func (dp DehashedParameter) GetArgumentString(arg string) string {
+ return fmt.Sprintf("%s:%s", string(dp), arg)
+}
+
+type DehashedSearchRequest struct {
+ ForcePlaintext bool `json:"-"`
+ Page int `json:"page"`
+ Query string `json:"query"`
+ Size int `json:"size"`
+ Wildcard bool `json:"wildcard"`
+ Regex bool `json:"regex"`
+ DeDupe bool `json:"de_dupe"`
+}
+
+func NewDehashedSearchRequest(page, size int, wildcard, regex, forcePlaintext bool) *DehashedSearchRequest {
+ return &DehashedSearchRequest{Page: page, Query: "", Size: size, Wildcard: wildcard, Regex: regex, DeDupe: true, ForcePlaintext: forcePlaintext}
+}
+
+func (dsr *DehashedSearchRequest) buildQuery(query string, param DehashedParameter) {
+ if len(dsr.Query) > 0 {
+ dsr.Query = fmt.Sprintf("%s&%s", strings.TrimSpace(dsr.Query), strings.TrimSpace(query))
+ } else {
+ dsr.Query = query
+ }
+}
+
+func (dsr *DehashedSearchRequest) AddUsernameQuery(query string) {
+ query = strings.TrimSpace(query)
+ dsr.buildQuery(Username.GetArgumentString(query), Username)
+}
+
+func (dsr *DehashedSearchRequest) AddEmailQuery(query string) {
+ query = strings.TrimSpace(query)
+ dsr.buildQuery(Email.GetArgumentString(query), Email)
+}
+
+func (dsr *DehashedSearchRequest) AddIpAddressQuery(query string) {
+ query = strings.TrimSpace(query)
+ dsr.buildQuery(IpAddress.GetArgumentString(query), IpAddress)
+}
+
+func (dsr *DehashedSearchRequest) AddDomainQuery(query string) {
+ query = strings.TrimSpace(query)
+ dsr.buildQuery(Domain.GetArgumentString(query), Domain)
+}
+
+func (dsr *DehashedSearchRequest) AddPasswordQuery(query string) {
+ if dsr.ForcePlaintext {
+ dsr.buildQuery(Password.GetArgumentString(query), Password)
+ return
+ }
+ hash := sha256.Sum256([]byte(query))
+ query = hex.EncodeToString(hash[:])
+ dsr.AddHashedPasswordQuery(query)
+}
+
+func (dsr *DehashedSearchRequest) AddVinQuery(query string) {
+ query = strings.TrimSpace(query)
+ dsr.buildQuery(Vin.GetArgumentString(query), Vin)
+}
+
+func (dsr *DehashedSearchRequest) AddLicensePlateQuery(query string) {
+ query = strings.TrimSpace(query)
+ dsr.buildQuery(LicensePlate.GetArgumentString(query), LicensePlate)
+}
+
+func (dsr *DehashedSearchRequest) AddAddressQuery(query string) {
+ query = strings.TrimSpace(query)
+ dsr.buildQuery(Address.GetArgumentString(query), Address)
+}
+
+func (dsr *DehashedSearchRequest) AddPhoneQuery(query string) {
+ query = strings.TrimSpace(query)
+ dsr.buildQuery(Phone.GetArgumentString(query), Phone)
+}
+
+func (dsr *DehashedSearchRequest) AddSocialQuery(query string) {
+ query = strings.TrimSpace(query)
+ dsr.buildQuery(Social.GetArgumentString(query), Social)
+}
+
+func (dsr *DehashedSearchRequest) AddCryptoAddressQuery(query string) {
+ query = strings.TrimSpace(query)
+ dsr.buildQuery(CryptoAddress.GetArgumentString(query), CryptoAddress)
+}
+
+func (dsr *DehashedSearchRequest) AddHashedPasswordQuery(query string) {
+ dsr.buildQuery(HashedPassword.GetArgumentString(query), HashedPassword)
+}
+
+func (dsr *DehashedSearchRequest) AddNameQuery(query string) {
+ query = strings.TrimSpace(query)
+ dsr.buildQuery(Name.GetArgumentString(query), Name)
+}
+
+type DehashedClientV2 struct {
+ apiKey string
+ results []sqlite.Result
+}
+
+func NewDehashedClientV2(apiKey string) *DehashedClientV2 {
+ return &DehashedClientV2{apiKey: apiKey}
+}
+
+func (dcv2 *DehashedClientV2) Search(searchRequest DehashedSearchRequest) (int, error) {
+ reqBody, _ := json.Marshal(searchRequest)
+ req, err := http.NewRequest("POST", "https://api.dehashed.com/v2/search", bytes.NewReader(reqBody))
+ if err != nil {
+ return -1, err
+ }
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Dehashed-Api-Key", dcv2.apiKey)
+ res, err := http.DefaultClient.Do(req)
+ if res != nil {
+ defer res.Body.Close()
+ }
+ if err != nil {
+ zap.L().Error("v2_search",
+ zap.String("message", "failed to perform request"),
+ zap.Error(err),
+ )
+ return -1, err
+ }
+ if res == nil {
+ zap.L().Error("v2_search",
+ zap.String("message", "response was nil"),
+ )
+ return -1, errors.New("response was nil")
+ }
+ b, err := io.ReadAll(res.Body)
+ if err != nil {
+ zap.L().Error("v2_search",
+ zap.String("message", "failed to read response body"),
+ zap.Error(err),
+ )
+ return -1, err
+ }
+
+ var responseResults sqlite.DehashedResponse
+ err = json.Unmarshal(b, &responseResults)
+ if err != nil {
+ zap.L().Error("v2_search",
+ zap.String("message", "failed to unmarshal response body"),
+ zap.Error(err),
+ )
+ return -1, err
+ }
+
+ dcv2.results = append(dcv2.results, responseResults.Entries...)
+ return responseResults.TotalResults, nil
+}
+
+func (dcv2 *DehashedClientV2) GetResults() sqlite.DehashedResults {
+ return sqlite.DehashedResults{Results: dcv2.results}
+}
+
+func (dcv2 *DehashedClientV2) GetTotalResults() int {
+ return len(dcv2.results)
+}
diff --git a/internal/query/dehashed.go b/internal/query/dehashed.go
new file mode 100644
index 0000000..a14949f
--- /dev/null
+++ b/internal/query/dehashed.go
@@ -0,0 +1,206 @@
+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.Println("[*] Querying Dehashed API...")
+ for i := 0; i < dh.options.MaxRequests; i++ {
+ fmt.Printf("\n\t[*] Performing Request...")
+ count, err := dh.client.Search(*dh.request)
+ if err != nil {
+ fmt.Printf("[!] Error performing request: %v", err)
+ os.Exit(-1)
+ }
+
+ if count < dh.options.MaxRecords {
+ fmt.Printf("\n\t\t[+] Retrieved %d Records", count)
+ fmt.Printf("\n[-] Not Enough Entries, ending queries")
+ break
+ } else {
+ fmt.Printf("\n\t\t[+] Retrieved %d Records", 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")
+ }
+ }
+ }
+}
diff --git a/internal/query/errors.go b/internal/query/errors.go
new file mode 100644
index 0000000..12027e1
--- /dev/null
+++ b/internal/query/errors.go
@@ -0,0 +1,33 @@
+package query
+
+type DehashError struct {
+ Message string
+ Code int
+}
+
+type DehashResponseError struct {
+ HttpResponse int `json:"HTTP Response Code"`
+}
+
+func (de *DehashError) Error() string {
+ return de.Message
+}
+
+func GetDehashedError(c int) DehashError {
+ switch c {
+ case 400:
+ return DehashError{Code: 400, Message: "There is an issue with authentication. Please check your API key and email. If you haven't, refresh your API Key "}
+ case 401:
+ return DehashError{Code: 401, Message: "You need a search subscription and API credits to use the API, please purchase a search subscription and add credits to your account."}
+ case 403:
+ return DehashError{Code: 403, Message: "Insufficient Credits"}
+ case 404:
+ return DehashError{Code: 404, Message: "Method not permitted"}
+ case 429:
+ return DehashError{Code: 420, Message: "Rate Limited"}
+ case 302:
+ return DehashError{Code: 302, Message: "Invalid/Missing Query"}
+ default:
+ return DehashError{Code: -1, Message: "An unknown error has occurred"}
+ }
+}
diff --git a/internal/sqlite/db.go b/internal/sqlite/db.go
new file mode 100644
index 0000000..bcc6d71
--- /dev/null
+++ b/internal/sqlite/db.go
@@ -0,0 +1,365 @@
+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
+}
diff --git a/internal/sqlite/gorm.go b/internal/sqlite/gorm.go
new file mode 100644
index 0000000..c8a6240
--- /dev/null
+++ b/internal/sqlite/gorm.go
@@ -0,0 +1,209 @@
+package sqlite
+
+import (
+ "fmt"
+ "go.uber.org/zap"
+ "gorm.io/gorm/clause"
+ "os"
+ "path/filepath"
+
+ "gorm.io/driver/sqlite"
+ "gorm.io/gorm"
+ "gorm.io/gorm/logger"
+)
+
+var DB *gorm.DB
+
+// InitDB initializes the database connection
+func InitDB(dbDir string) (*gorm.DB, error) {
+ zap.L().Info("Initializing database")
+
+ // Create directory if it doesn't exist
+ if err := os.MkdirAll(dbDir, 0755); err != nil {
+ zap.L().Error("Failed to create database directory", zap.Error(err))
+ return nil, fmt.Errorf("failed to create database directory: %w", err)
+ }
+
+ dbPath := filepath.Join(dbDir, "dehashed.sqlite")
+ db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{
+ Logger: logger.Default.LogMode(logger.Silent),
+ })
+ if err != nil {
+ zap.L().Error("Failed to connect to database", zap.Error(err))
+ return nil, fmt.Errorf("failed to connect to database: %w", err)
+ }
+
+ // Auto migrate your models
+ err = db.AutoMigrate(&Result{}, &Creds{}, QueryOptions{}, Creds{}, WhoisRecord{}, SubdomainRecord{}, HistoryRecord{})
+ if err != nil {
+ zap.L().Error("Failed to migrate database", zap.Error(err))
+ return nil, fmt.Errorf("failed to migrate database: %w", err)
+ }
+
+ DB = db
+ return db, nil
+}
+
+// GetDB returns the database connection
+func GetDB() *gorm.DB {
+ if DB == nil {
+ zap.L().Error("database not initialized")
+ fmt.Println("sqlite database not initialized")
+ os.Exit(1)
+ }
+ return DB
+}
+
+func StoreResults(results DehashedResults) error {
+ if len(results.Results) == 0 {
+ return nil
+ }
+
+ zap.L().Info("Storing results", zap.Int("count", len(results.Results)))
+ db := GetDB()
+
+ // Use batch insert with conflict handling
+ const batchSize = 100
+ var lastErr error
+
+ // Extract the slice of results
+ resultSlice := results.Results
+
+ for i := 0; i < len(resultSlice); i += batchSize {
+ end := i + batchSize
+ if end > len(resultSlice) {
+ end = len(resultSlice)
+ }
+
+ batch := resultSlice[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 results", zap.Error(err))
+ lastErr = err
+ // Continue with next batch despite error
+ }
+ }
+
+ return lastErr
+}
+
+func StoreCreds(creds []Creds) error {
+ if len(creds) == 0 {
+ return nil
+ }
+
+ zap.L().Info("Storing credentials", zap.Int("count", len(creds)))
+ db := GetDB()
+
+ // Use batch insert with conflict handling
+ // This will insert records in batches and continue even if some fail
+ const batchSize = 100
+ var lastErr error
+
+ for i := 0; i < len(creds); i += batchSize {
+ end := i + batchSize
+ if end > len(creds) {
+ end = len(creds)
+ }
+
+ batch := creds[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 credentials", zap.Error(err))
+ lastErr = err
+ // Continue with next batch despite error
+ }
+ }
+
+ return lastErr
+}
+
+func StoreQueryOptions(queryOptions *QueryOptions) error {
+ db := GetDB()
+ return db.Create(queryOptions).Error
+}
+
+func StoreWhoisRecord(whoisRecord WhoisRecord) error {
+ // Create a pointer to the record to make it addressable
+ recordPtr := &whoisRecord
+
+ zap.L().Info("Storing WHOIS record",
+ zap.String("domain", whoisRecord.DomainName))
+
+ db := GetDB()
+
+ // Use OnConflict clause to handle duplicates
+ err := db.Clauses(clause.OnConflict{DoNothing: true}).Create(recordPtr).Error
+ if err != nil {
+ zap.L().Error("store_whois_record",
+ zap.String("message", "failed to store whois record"),
+ zap.Error(err))
+ return err
+ }
+
+ return nil
+}
+
+func StoreSubdomainRecord(subdomainRecords []SubdomainRecord) error {
+ if len(subdomainRecords) == 0 {
+ return nil
+ }
+
+ zap.L().Info("Storing subdomain records", zap.Int("count", len(subdomainRecords)))
+ db := GetDB()
+
+ // Use batch insert with conflict handling
+ const batchSize = 100
+ var lastErr error
+
+ for i := 0; i < len(subdomainRecords); i += batchSize {
+ end := i + batchSize
+ if end > len(subdomainRecords) {
+ end = len(subdomainRecords)
+ }
+
+ batch := subdomainRecords[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 subdomain records", zap.Error(err))
+ lastErr = err
+ // Continue with next batch despite error
+ }
+ }
+
+ return lastErr
+}
+
+func StoreHistoryRecord(historyRecords []HistoryRecord) error {
+ if len(historyRecords) == 0 {
+ return nil
+ }
+
+ zap.L().Info("Storing history records", zap.Int("count", len(historyRecords)))
+ db := GetDB()
+
+ // Use batch insert with conflict handling
+ const batchSize = 100
+ var lastErr error
+
+ for i := 0; i < len(historyRecords); i += batchSize {
+ end := i + batchSize
+ if end > len(historyRecords) {
+ end = len(historyRecords)
+ }
+
+ batch := historyRecords[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 history records", zap.Error(err))
+ lastErr = err
+ // Continue with next batch despite error
+ }
+ }
+
+ return lastErr
+}
diff --git a/internal/sqlite/query.go b/internal/sqlite/query.go
new file mode 100644
index 0000000..1b6382c
--- /dev/null
+++ b/internal/sqlite/query.go
@@ -0,0 +1,20 @@
+package sqlite
+
+type DehashedSearchRequest struct {
+ Page int `json:"page"`
+ Query string `json:"query"`
+ Size int `json:"size"`
+ Wildcard bool `json:"wildcard"`
+ Regex bool `json:"regex"`
+ DeDupe bool `json:"de_dupe"`
+}
+
+func NewDehashedSearchRequest(size int, wildcard, regex bool) *DehashedSearchRequest {
+ return &DehashedSearchRequest{
+ Page: 0,
+ Size: size,
+ Wildcard: false,
+ Regex: false,
+ DeDupe: true,
+ }
+}
diff --git a/internal/sqlite/result.go b/internal/sqlite/result.go
new file mode 100644
index 0000000..d31817b
--- /dev/null
+++ b/internal/sqlite/result.go
@@ -0,0 +1,90 @@
+package sqlite
+
+import (
+ "encoding/json"
+ "fmt"
+ "go.uber.org/zap"
+ "gorm.io/gorm"
+ "io"
+ "os"
+)
+
+type DehashedResponse struct {
+ Balance int `json:"balance"`
+ Entries []Result `json:"entries"`
+ Success bool `json:"success"`
+ Took string `json:"took"`
+ TotalResults int `json:"total"`
+}
+
+type Result struct {
+ gorm.Model
+ DehashedId string `json:"id" xml:"id" yaml:"id" gorm:"uniqueIndex"`
+ Email []string `json:"email,omitempty" xml:"email,omitempty" yaml:"email,omitempty" gorm:"serializer:json"`
+ IpAddress []string `json:"ip_address,omitempty" xml:"ip_address,omitempty" yaml:"ip_address,omitempty" gorm:"serializer:json"`
+ Username []string `json:"username,omitempty" xml:"username,omitempty" yaml:"username,omitempty" gorm:"serializer:json"`
+ Password []string `json:"password,omitempty" xml:"password,omitempty" yaml:"password,omitempty" gorm:"serializer:json"`
+ HashedPassword []string `json:"hashed_password,omitempty" xml:"hashed_password,omitempty" yaml:"hashed_password,omitempty" gorm:"serializer:json"`
+ HashType string `json:"hash_type,omitempty" xml:"hash_type,omitempty" yaml:"hash_type,omitempty"`
+ Name []string `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty" gorm:"serializer:json"`
+ Vin []string `json:"vin,omitempty" xml:"vin,omitempty" yaml:"vin,omitempty" gorm:"serializer:json"`
+ LicensePlate []string `json:"license_plate,omitempty" xml:"license_plate,omitempty" yaml:"license_plate,omitempty" gorm:"serializer:json"`
+ Url []string `json:"url,omitempty" xml:"url,omitempty" yaml:"url,omitempty" gorm:"serializer:json"`
+ Social []string `json:"social,omitempty" xml:"social,omitempty" yaml:"social,omitempty" gorm:"serializer:json"`
+ CryptoCurrencyAddress []string `json:"cryptocurrency_address,omitempty" xml:"cryptocurrency_address,omitempty" yaml:"cryptocurrency_address,omitempty" gorm:"serializer:json"`
+ Address []string `json:"address,omitempty" xml:"address,omitempty" yaml:"address,omitempty" gorm:"serializer:json"`
+ Phone []string `json:"phone,omitempty" xml:"phone,omitempty" yaml:"phone,omitempty" gorm:"serializer:json"`
+ Company []string `json:"company,omitempty" xml:"company,omitempty" yaml:"company,omitempty" gorm:"serializer:json"`
+ DatabaseName string `json:"database_name,omitempty" xml:"database_name,omitempty" yaml:"database_name,omitempty"`
+}
+
+type DehashedResults struct {
+ Results []Result `json:"results"`
+}
+
+func (dr *DehashedResults) ExtractCredentials() []Creds {
+ var creds []Creds
+
+ results := dr.Results
+
+ for _, r := range results {
+ if len(r.Password) > 0 {
+ // Get first email if available
+ email := ""
+ if len(r.Email) > 0 {
+ email = r.Email[0]
+ }
+
+ // Get first password
+ password := r.Password[0]
+
+ cred := Creds{Email: email, Password: password}
+ creds = append(creds, cred)
+ }
+ }
+
+ go func() {
+ err := StoreCreds(creds)
+ if err != nil {
+ zap.L().Error("store_creds",
+ zap.String("message", "failed to store creds"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error Storing Results: %v", err)
+ }
+ }()
+
+ return creds
+}
+
+func NewDehashedResults(body io.Reader) ([]Result, int, int) {
+ var response DehashedResponse
+
+ err := json.NewDecoder(body).Decode(&response)
+ if err != nil {
+ fmt.Printf("Error Parsing Response Body: %v", err)
+ os.Exit(-1)
+ }
+
+ return response.Entries, response.Balance, response.TotalResults
+}
diff --git a/internal/sqlite/structs.go b/internal/sqlite/structs.go
new file mode 100644
index 0000000..4650da2
--- /dev/null
+++ b/internal/sqlite/structs.go
@@ -0,0 +1,108 @@
+package sqlite
+
+import (
+ "dehasher/internal/files"
+ "fmt"
+ "gorm.io/gorm"
+)
+
+type DBOptions struct {
+ Username string
+ Email string
+ IPAddress string
+ Password string
+ HashedPassword string
+ Name string
+ Vin string
+ LicensePlate string
+ Address string
+ Phone string
+ Social string
+ CryptoCurrencyAddress string
+ Domain string
+ Limit int
+ ExactMatch bool
+ NonEmptyFields []string // Fields that should not be empty
+ DisplayFields []string // Fields to display in output
+}
+
+func NewDBOptions() *DBOptions {
+ return &DBOptions{
+ Limit: 100, // Default limit
+ ExactMatch: false,
+ NonEmptyFields: []string{},
+ DisplayFields: []string{},
+ }
+}
+
+func (o *DBOptions) Empty() bool {
+ return o.Username == "" && o.Email == "" && o.IPAddress == "" &&
+ o.Password == "" && o.HashedPassword == "" && o.Name == "" &&
+ o.Vin == "" && o.LicensePlate == "" && o.Address == "" &&
+ o.Phone == "" && o.Social == "" && o.CryptoCurrencyAddress == "" && o.Domain == "" &&
+ len(o.NonEmptyFields) == 0
+}
+
+type QueryOptions struct {
+ gorm.Model
+ MaxRecords int `json:"max_records"`
+ MaxRequests int `json:"max_requests"`
+ StartingPage int `json:"starting_page"`
+ OutputFormat files.FileType `json:"output_format"`
+ OutputFile string `json:"output_file"`
+ RegexMatch bool `json:"regex_match"`
+ WildcardMatch bool `json:"wildcard_match"`
+ UsernameQuery string `json:"username_query"`
+ EmailQuery string `json:"email_query"`
+ IpQuery string `json:"ip_query"`
+ PassQuery string `json:"pass_query"`
+ HashQuery string `json:"hash_query"`
+ NameQuery string `json:"name_query"`
+ DomainQuery string `json:"domain_query"`
+ VinQuery string `json:"vin_query"`
+ LicensePlateQuery string `json:"license_plate_query"`
+ AddressQuery string `json:"address_query"`
+ PhoneQuery string `json:"phone_query"`
+ SocialQuery string `json:"social_query"`
+ CryptoAddressQuery string `json:"crypto_address_query"`
+ PrintBalance bool `json:"print_balance"`
+ CredsOnly bool `json:"creds_only"`
+}
+
+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 {
+ return &QueryOptions{
+ MaxRecords: maxRecords,
+ MaxRequests: maxRequests,
+ StartingPage: startingPage,
+ OutputFormat: files.GetFileType(outputFormat),
+ OutputFile: outputFile,
+ PrintBalance: printBalance,
+ CredsOnly: credsOnly,
+ RegexMatch: regexMatch,
+ WildcardMatch: wildcardMatch,
+ UsernameQuery: usernameQuery,
+ EmailQuery: emailQuery,
+ IpQuery: ipQuery,
+ PassQuery: passQuery,
+ HashQuery: hashQuery,
+ NameQuery: nameQuery,
+ DomainQuery: domainQuery,
+ VinQuery: vinQuery,
+ LicensePlateQuery: licensePlateQuery,
+ AddressQuery: addressQuery,
+ PhoneQuery: phoneQuery,
+ SocialQuery: socialQuery,
+ CryptoAddressQuery: cryptoAddressQuery,
+ }
+}
+
+type Creds struct {
+ gorm.Model
+ Email string `json:"email" yaml:"email" xml:"email"`
+ Username string `json:"username" yaml:"username" xml:"username"`
+ Password string `json:"password" yaml:"password" xml:"password"`
+}
+
+func (c Creds) ToString() string {
+ return fmt.Sprintf("%s%s%s", c.Username, "%", c.Password)
+}
diff --git a/internal/sqlite/whois.go b/internal/sqlite/whois.go
new file mode 100644
index 0000000..84d6afa
--- /dev/null
+++ b/internal/sqlite/whois.go
@@ -0,0 +1,160 @@
+package sqlite
+
+import "gorm.io/gorm"
+
+type WhoIsLookupResult struct {
+ RemainingCredits int `json:"remaining_credits"`
+ Data Data `json:"data"`
+}
+
+type Data struct {
+ WhoisRecord WhoisRecord `json:"WhoisRecord"`
+}
+
+type WhoisRecord struct {
+ gorm.Model
+ Audit Audit `json:"audit" gorm:"serializer:json"`
+ ContactEmail string `json:"contactEmail"`
+ CreatedDate string `json:"createdDate"`
+ CreatedDateNormalized string `json:"createdDateNormalized"`
+ DomainName string `json:"domainName"`
+ DomainNameExt string `json:"domainNameExt"`
+ EstimatedDomainAge int `json:"estimatedDomainAge"`
+ ExpiresDate string `json:"expiresDate"`
+ ExpiresDateNormalized string `json:"expiresDateNormalized"`
+ Footer string `json:"footer"`
+ Header string `json:"header"`
+ NameServers NameServers `json:"nameServers" gorm:"serializer:json"`
+ ParseCode int `json:"parseCode"`
+ RawText string `json:"rawText"`
+ Registrant Contact `json:"registrant" gorm:"serializer:json"`
+ RegistrarIANAID string `json:"registrarIANAID"`
+ RegistrarName string `json:"registrarName"`
+ RegistryData RegistryData `json:"registryData" gorm:"serializer:json"`
+ Status string `json:"status"`
+ StrippedText string `json:"strippedText"`
+ TechnicalContact Contact `json:"technicalContact" gorm:"serializer:json"`
+ UpdatedDate string `json:"updatedDate"`
+ UpdatedDateNormalized string `json:"updatedDateNormalized"`
+}
+
+type Audit struct {
+ CreatedDate string `json:"createdDate"`
+ UpdatedDate string `json:"updatedDate"`
+}
+
+type NameServers struct {
+ HostNames []string `json:"hostNames"`
+ IPs []string `json:"ips"`
+ RawText string `json:"rawText"`
+}
+
+type Contact struct {
+ City string `json:"city"`
+ Country string `json:"country"`
+ CountryCode string `json:"countryCode"`
+ Name string `json:"name"`
+ Organization string `json:"organization"`
+ PostalCode string `json:"postalCode"`
+ RawText string `json:"rawText"`
+ State string `json:"state"`
+ Street1 string `json:"street1"`
+ Telephone string `json:"telephone"`
+}
+
+type RegistryData struct {
+ Audit Audit `json:"audit"`
+ CreatedDate string `json:"createdDate"`
+ CreatedDateNormalized string `json:"createdDateNormalized"`
+ DomainName string `json:"domainName"`
+ ExpiresDate string `json:"expiresDate"`
+ ExpiresDateNormalized string `json:"expiresDateNormalized"`
+ Footer string `json:"footer"`
+ Header string `json:"header"`
+ NameServers NameServers `json:"nameServers"`
+ ParseCode int `json:"parseCode"`
+ RawText string `json:"rawText"`
+ RegistrarIANAID string `json:"registrarIANAID"`
+ RegistrarName string `json:"registrarName"`
+ Status string `json:"status"`
+ StrippedText string `json:"strippedText"`
+ UpdatedDate string `json:"updatedDate"`
+ UpdatedDateNormalized string `json:"updatedDateNormalized"`
+ WhoisServer string `json:"whoisServer"`
+}
+
+type WhoIsSubdomainScan struct {
+ RemainingCredits int `json:"remaining_credits"`
+ Data ScanData `json:"data"`
+}
+
+type ScanData struct {
+ Result ScanResult `json:"result"`
+ Search string `json:"search"`
+}
+
+type ScanResult struct {
+ Count int `json:"count"`
+ Records []SubdomainRecord `json:"records"`
+}
+
+type SubdomainRecord struct {
+ gorm.Model
+ Domain string `json:"domain"`
+ FirstSeen int64 `json:"firstSeen"`
+ LastSeen int64 `json:"lastSeen"`
+}
+
+type WhoIsHistory struct {
+ RemainingCredits int `json:"remaining_credits"`
+ Data HistoryData `json:"data"`
+}
+
+type HistoryData struct {
+ Records []HistoryRecord `json:"records"`
+ RecordsCount int `json:"recordsCount"`
+}
+
+type HistoryRecord struct {
+ gorm.Model
+ AdministrativeContact ContactInfo `json:"administrativeContact" gorm:"serializer:json"`
+ Audit Audit `json:"audit" gorm:"serializer:json"`
+ BillingContact ContactInfo `json:"billingContact" gorm:"serializer:json"`
+ CleanText string `json:"cleanText"`
+ CreatedDateISO8601 string `json:"createdDateISO8601"`
+ CreatedDateRaw string `json:"createdDateRaw"`
+ DomainName string `json:"domainName"`
+ DomainType string `json:"domainType"`
+ ExpiresDateISO8601 string `json:"expiresDateISO8601"`
+ ExpiresDateRaw string `json:"expiresDateRaw"`
+ NameServers []string `json:"nameServers" gorm:"serializer:json"`
+ RawText string `json:"rawText"`
+ RegistrantContact ContactInfo `json:"registrantContact" gorm:"serializer:json"`
+ RegistrarName string `json:"registrarName"`
+ Status []string `json:"status" gorm:"serializer:json"`
+ TechnicalContact ContactInfo `json:"technicalContact" gorm:"serializer:json"`
+ UpdatedDateISO8601 string `json:"updatedDateISO8601"`
+ UpdatedDateRaw string `json:"updatedDateRaw"`
+ WhoisServer string `json:"whoisServer"`
+ ZoneContact ContactInfo `json:"zoneContact" gorm:"serializer:json"`
+}
+
+type ContactInfo struct {
+ City string `json:"city"`
+ Country string `json:"country"`
+ Email string `json:"email"`
+ Fax string `json:"fax"`
+ FaxExt string `json:"faxExt"`
+ Name string `json:"name"`
+ Organization string `json:"organization"`
+ PostalCode string `json:"postalCode"`
+ RawText string `json:"rawText"`
+ State string `json:"state"`
+ Street string `json:"street"`
+ Telephone string `json:"telephone"`
+ TelephoneExt string `json:"telephoneExt"`
+}
+
+type WhoIsCredits struct {
+ WhoisCredits int `json:"whois_credits"`
+}
diff --git a/internal/whois/whois.go b/internal/whois/whois.go
new file mode 100644
index 0000000..93dfef5
--- /dev/null
+++ b/internal/whois/whois.go
@@ -0,0 +1,319 @@
+package whois
+
+import (
+ "bytes"
+ "dehasher/internal/sqlite"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "go.uber.org/zap"
+ "io"
+ "net/http"
+)
+
+type DehashedWHOISSearchRequest struct {
+ Include []string `json:"include,omitempty"`
+ Exclude []string `json:"exclude,omitempty"`
+ IPAddress string `json:"ip_address,omitempty"`
+ ReverseType string `json:"reverse_type,omitempty"`
+ Domain string `json:"domain,omitempty"`
+ MXAddress string `json:"mx_address,omitempty"`
+ NSAddress string `json:"ns_address,omitempty"`
+ SearchType string `json:"search_type,omitempty"`
+}
+
+func WhoisSearch(domain, apiKey string) (sqlite.WhoIsLookupResult, error) {
+ var whois sqlite.WhoIsLookupResult
+ whoisSearchRequest := DehashedWHOISSearchRequest{
+ Domain: domain,
+ SearchType: "whois",
+ }
+ reqBody, _ := json.Marshal(whoisSearchRequest)
+ req, err := http.NewRequest("POST", "https://api.dehashed.com/v2/whois/search", bytes.NewReader(reqBody))
+ if err != nil {
+ return whois, err
+ }
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Dehashed-Api-Key", apiKey)
+ res, err := http.DefaultClient.Do(req)
+ if res != nil {
+ defer res.Body.Close()
+ }
+ if err != nil {
+ return whois, err
+ }
+ if res == nil {
+ return whois, errors.New("response was nil")
+ }
+ b, err := io.ReadAll(res.Body)
+ if err != nil {
+ return whois, err
+ }
+
+ err = json.Unmarshal(b, &whois)
+ if err != nil {
+ zap.L().Error("whois_search",
+ zap.String("message", "failed to unmarshal response body"),
+ zap.Error(err),
+ )
+ fmt.Println("Error unmarshalling response body:", err)
+ fmt.Println("Response body:", string(b))
+ return whois, err
+ }
+
+ return whois, nil
+}
+
+func WhoisHistory(domain, apiKey string) (sqlite.WhoIsHistory, error) {
+ var whois sqlite.WhoIsHistory
+ whoisSearchRequest := DehashedWHOISSearchRequest{
+ Domain: domain,
+ SearchType: "whois-history",
+ }
+ reqBody, _ := json.Marshal(whoisSearchRequest)
+ req, err := http.NewRequest("POST", "https://api.dehashed.com/v2/whois/search", bytes.NewReader(reqBody))
+ if err != nil {
+ return whois, err
+ }
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Dehashed-Api-Key", apiKey)
+ res, err := http.DefaultClient.Do(req)
+ if res != nil {
+ zap.L().Info("whois_history",
+ zap.String("message", "response was not nil"),
+ )
+ defer res.Body.Close()
+ }
+ if err != nil {
+ zap.L().Error("whois_history",
+ zap.String("message", "failed to perform request"),
+ zap.Error(err),
+ )
+ return whois, err
+ }
+ if res == nil {
+ zap.L().Error("whois_history",
+ zap.String("message", "response was nil"),
+ )
+ return whois, errors.New("response was nil")
+ }
+ b, err := io.ReadAll(res.Body)
+ if err != nil {
+ zap.L().Error("whois_history",
+ zap.String("message", "failed to read response body"),
+ zap.Error(err),
+ )
+ return whois, err
+ }
+
+ err = json.Unmarshal(b, &whois)
+ if err != nil {
+ zap.L().Error("whois_history",
+ zap.String("message", "failed to unmarshal response body"),
+ zap.Error(err),
+ )
+ fmt.Println("Error unmarshalling response body:", err)
+ fmt.Println("Response body:", string(b))
+ return whois, err
+ }
+
+ return whois, nil
+}
+
+func ReverseWHOIS(include []string, exclude []string, reverseType, apiKey string) (string, error) {
+ whoisSearchRequest := DehashedWHOISSearchRequest{
+ Include: include,
+ Exclude: exclude,
+ ReverseType: reverseType,
+ SearchType: "reverse-whois",
+ }
+ reqBody, _ := json.Marshal(whoisSearchRequest)
+ req, err := http.NewRequest("POST", "https://api.dehashed.com/v2/whois/search", bytes.NewReader(reqBody))
+ if err != nil {
+ return "", err
+ }
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Dehashed-Api-Key", apiKey)
+ res, err := http.DefaultClient.Do(req)
+ if res != nil {
+ defer res.Body.Close()
+ }
+ if err != nil {
+ return "", err
+ }
+ if res == nil {
+ return "", errors.New("response was nil")
+ }
+ b, err := io.ReadAll(res.Body)
+ if err != nil {
+ return "", err
+ }
+ return string(b), nil
+}
+
+func WhoisIP(ipAddress, apiKey string) ([]byte, error) {
+ whoisSearchRequest := DehashedWHOISSearchRequest{
+ IPAddress: ipAddress,
+ SearchType: "reverse-ip",
+ }
+ reqBody, _ := json.Marshal(whoisSearchRequest)
+ req, err := http.NewRequest("POST", "https://api.dehashed.com/v2/whois/search", bytes.NewReader(reqBody))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Dehashed-Api-Key", apiKey)
+ res, err := http.DefaultClient.Do(req)
+ if res != nil {
+ defer res.Body.Close()
+ }
+ if err != nil {
+ return nil, err
+ }
+ if res == nil {
+ return nil, errors.New("response was nil")
+ }
+ b, err := io.ReadAll(res.Body)
+ if err != nil {
+ return nil, err
+ }
+ return b, nil
+}
+
+func WhoisMX(mxAddress, apiKey string) (string, error) {
+ whoisSearchRequest := DehashedWHOISSearchRequest{
+ MXAddress: mxAddress,
+ SearchType: "reverse-mx",
+ }
+ reqBody, _ := json.Marshal(whoisSearchRequest)
+ req, err := http.NewRequest("POST", "https://api.dehashed.com/v2/whois/search", bytes.NewReader(reqBody))
+ if err != nil {
+ return "", err
+ }
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Dehashed-Api-Key", apiKey)
+ res, err := http.DefaultClient.Do(req)
+ if res != nil {
+ defer res.Body.Close()
+ }
+ if err != nil {
+ return "", err
+ }
+ if res == nil {
+ return "", errors.New("response was nil")
+ }
+ b, err := io.ReadAll(res.Body)
+ if err != nil {
+ return "", err
+ }
+ return string(b), nil
+}
+
+func WhoisNS(nsAddress, apiKey string) (string, error) {
+ whoisSearchRequest := DehashedWHOISSearchRequest{
+ NSAddress: nsAddress,
+ SearchType: "reverse-ns",
+ }
+ reqBody, _ := json.Marshal(whoisSearchRequest)
+ req, err := http.NewRequest("POST", "https://api.dehashed.com/v2/whois/search", bytes.NewReader(reqBody))
+ if err != nil {
+ return "", err
+ }
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Dehashed-Api-Key", apiKey)
+ res, err := http.DefaultClient.Do(req)
+ if res != nil {
+ defer res.Body.Close()
+ }
+ if err != nil {
+ return "", err
+ }
+ if res == nil {
+ return "", errors.New("response was nil")
+ }
+ b, err := io.ReadAll(res.Body)
+ if err != nil {
+ return "", err
+ }
+ return string(b), nil
+}
+
+func WhoisSubdomainScan(domain, apiKey string) (sqlite.WhoIsSubdomainScan, error) {
+ var whois sqlite.WhoIsSubdomainScan
+ whoisSearchRequest := DehashedWHOISSearchRequest{
+ Domain: domain,
+ SearchType: "subdomain-scan",
+ }
+ reqBody, _ := json.Marshal(whoisSearchRequest)
+ req, err := http.NewRequest("POST", "https://api.dehashed.com/v2/whois/search", bytes.NewReader(reqBody))
+ if err != nil {
+ return whois, err
+ }
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Dehashed-Api-Key", apiKey)
+ res, err := http.DefaultClient.Do(req)
+ if res != nil {
+ defer res.Body.Close()
+ }
+ if err != nil {
+ return whois, err
+ }
+ if res == nil {
+ return whois, errors.New("response was nil")
+ }
+ b, err := io.ReadAll(res.Body)
+ if err != nil {
+ return whois, err
+ }
+
+ err = json.Unmarshal(b, &whois)
+ if err != nil {
+ zap.L().Error("whois_subdomain_scan",
+ zap.String("message", "failed to unmarshal response body"),
+ zap.Error(err),
+ )
+ fmt.Println("Error unmarshalling response body:", err)
+ fmt.Println("Response body:", string(b))
+ return whois, err
+ }
+
+ return whois, nil
+}
+
+func GetWHOISCredits(apiKey string) (sqlite.WhoIsCredits, error) {
+ var whoisCredits sqlite.WhoIsCredits
+
+ req, err := http.NewRequest("GET", "https://api.dehashed.com/v2/whois/credits", nil)
+ if err != nil {
+ return whoisCredits, err
+ }
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Dehashed-Api-Key", apiKey)
+ res, err := http.DefaultClient.Do(req)
+ if res != nil {
+ defer res.Body.Close()
+ }
+ if err != nil {
+ return whoisCredits, err
+ }
+ if res == nil {
+ return whoisCredits, errors.New("response was nil")
+ }
+ b, err := io.ReadAll(res.Body)
+ if err != nil {
+ return whoisCredits, err
+ }
+
+ err = json.Unmarshal(b, &whoisCredits)
+ if err != nil {
+ zap.L().Error("get_whois_credits",
+ zap.String("message", "failed to unmarshal response body"),
+ zap.Error(err),
+ )
+ fmt.Println("Error unmarshalling response body:", err)
+ fmt.Println("Response body:", string(b))
+ return whoisCredits, err
+ }
+
+ return whoisCredits, nil
+}
diff --git a/ondefend.txt b/ondefend.txt
new file mode 100644
index 0000000..b065cfe
--- /dev/null
+++ b/ondefend.txt
@@ -0,0 +1,455 @@
+Id: Uhs6wdMRv-jq3VAHW_Wc
+Email: [selby@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: []
+HashType:
+Name: [BRIAN SELBY]
+Vin: []
+Address: [224 SAINT CHARLES WAY STE 100, YORK, PA, 17402, US]
+Phone: [7177181611]
+DatabaseName: DemandScience by Pure Incubation
+
+Id: nWaSwdMRbgUelnkGA_uI
+Email: [kriddle@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: [counseltrust.com:None||Cisco-PIX(MD5) COUNSELTRUST.COM:None||Cisco-PIX(MD5)]
+HashType:
+Name: [KATHY RIDDLE]
+Vin: []
+Address: [224 ST CHARLES WAY STE 100, YORK, PA, 17402, US]
+Phone: [7177181600]
+DatabaseName: DemandScience by Pure Incubation
+
+Id: kRypwYMRZd5-FWZi9grp
+Email: [david@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: [COUNSELTRUST.COM:None||Cisco-PIX(MD5)]
+HashType:
+Name: [DAVID DOLAN]
+Vin: []
+Address: [224 ST CHARLES WAY SUITE 100, YORK, PA, 17402, US]
+Phone: [7177181600]
+DatabaseName: DemandScience by Pure Incubation
+
+Id: AqCvwYMRs35batudGTqf
+Email: [jpaff@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: [COUNSELTRUST.COM:None||Cisco-PIX(MD5)]
+HashType:
+Name: []
+Vin: []
+Address: []
+Phone: [7177181600]
+DatabaseName: DemandScience by Pure Incubation
+
+Id: IxLfwdMRkvXBjMmbeqpV
+Email: [gplatt@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: []
+HashType:
+Name: []
+Vin: []
+Address: []
+Phone: []
+DatabaseName: Evite
+
+Id: zBbozYMRs35batudWm6j
+Email: [stiles@counseltrust.com]
+IpAddress: []
+Username: [df34221016bc31f1b86c14519]
+Password: []
+HashedPassword: []
+HashType:
+Name: [stiles@counseltrustcom]
+Vin: []
+Address: []
+Phone: []
+DatabaseName: ShareThis.com
+
+Id: 37u0wdMRZd5-FWZiMghr
+Email: [jyonkovitch@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: []
+HashType:
+Name: [JASON YONKOVITCH]
+Vin: []
+Address: [224 SAINT CHARLES WAY STE 100, YORK, PA, 17402, US]
+Phone: [7177181611]
+DatabaseName: DemandScience by Pure Incubation
+
+Id: N0VExYMRkvXBjMmbnULI
+Email: [stiles@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: []
+HashType:
+Name: []
+Vin: []
+Address: []
+Phone: []
+DatabaseName: Adobe
+
+Id: 1_I0wNMRv-jq3VAHQBKZ
+Email: [dwells@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: []
+HashType:
+Name: [David Wells]
+Vin: []
+Address: [PO BOX 993, MADISON, AL]
+Phone: [7174656685]
+DatabaseName: Luxottica
+
+Id: TF69z5MRkvXBjMmbM3Mt
+Email: [WCHILCOAT@COUNSELTRUST.COM]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: [*16CpSaOtR9Ew=:None||UNKNOWN]
+HashType:
+Name: [WANDA CHILCOAT]
+Vin: []
+Address: [2740 CLEARVIEW WAY, YORK, PA, 17403 2740 CLEARVIEW WAY YORK, PA, 17403]
+Phone: [7177414611]
+DatabaseName: AT&T
+
+Id: uxOYwdMRZd5-FWZiD3PA
+Email: [paul.stevenson@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: [COUNSELTRUST.COM:None||Cisco-PIX(MD5)]
+HashType:
+Name: [PAUL STEVENSON]
+Vin: []
+Address: [224 ST CHARLES WAY SUITE 100, YORK, PA, 17402, US]
+Phone: [7177181600]
+DatabaseName: DemandScience by Pure Incubation
+
+Id: SGGjwNMRbgUelnkGHZSP
+Email: [david.dolan@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: [counseltrust.com:None||Cisco-PIX(MD5) COUNSELTRUST.COM:None||Cisco-PIX(MD5)]
+HashType:
+Name: [DAVID DOLAN]
+Vin: []
+Address: [224 SAINT CHARLES WAY STE 100, YORK, PA, 17402-4665, US 224 St. Charles Way Suite 100, York, PA, 17402, US]
+Phone: [7177181601]
+DatabaseName: DemandScience by Pure Incubation
+
+Id: o5ixwYMRv-jq3VAHfzI5
+Email: [stiles@counseltrust.com]
+IpAddress: []
+Username: [http://www.linkedin.com/in/bonnie-stiles-6a75b69]
+Password: []
+HashedPassword: [counseltrust.com:None||Cisco-PIX(MD5)]
+HashType:
+Name: [Bonnie M. Stiles]
+Vin: []
+Address: [224 Saint Charles Way, York, PA, 17402, US]
+Phone: [7177181601]
+DatabaseName: DemandScience by Pure Incubation
+
+Id: S37Qz5MRkvXBjMmbhwiO
+Email: [stiles@counseltrust.com]
+IpAddress: []
+Username: []
+Password: [stryker]
+HashedPassword: [ebe73849058b47ae5eb163ecb134a4c4:||MD5 2cbf1f833918150abc54fa50c53dc8dbd0266a7e:||SHA1 09792abb14956e93ecf2a709affde76344ca3ed396e57ffcbf65436cce83fe3c:||SHA256 49301fa88e4fa3d5547c0074fdfd8f56c7e4468222fcfe181882a0c97970b9e79b8598ec13d7437791b5dfb201a1f9188a25dffeaf3ccb7342364cdc29f9317d:||SHA512 c3RyeWtlcg==:||BASE64]
+HashType:
+Name: []
+Vin: []
+Address: []
+Phone: []
+DatabaseName: trustupdates.com (Cit0day)
+
+Id: Z8kqQdURkHaJUned-bBv
+Email: [pstevenson@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: [counseltrust.com:None||Cisco-PIX(MD5) COUNSELTRUST.COM:None||Cisco-PIX(MD5)]
+HashType:
+Name: [PAUL STEVENSON]
+Vin: []
+Address: [224 SAINT CHARLES WAY STE 100, YORK, PA, 17402-4665, US]
+Phone: [7177181601]
+DatabaseName: DemandScience by Pure Incubation
+
+Id: YrsEwdMRs35batudzJVH
+Email: [lkauffman@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: []
+HashType:
+Name: []
+Vin: []
+Address: []
+Phone: []
+DatabaseName: Evite
+
+Id: QRFKzdMRs35batud9GZ5
+Email: [DDOLAN@COUNSELTRUST.COM]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: [*0x7gMMV53xScLt+SqJEA1hw==:None||UNKNOWN *0+HSfRH0LZIc=:None||UNKNOWN]
+HashType:
+Name: [DAVID DOLAN]
+Vin: []
+Address: [3023 S ATLANTIC AV, DAYB SHR, FL, 32118 1330 HICKORY RUN DR, elizabethtown pa 17022-9202 ELIZABETHTOWN, PA, 17022]
+Phone: [7178584702]
+DatabaseName: AT&T
+
+Id: AapPzdMRbgUelnkGzAZi
+Email: [ecrooks@counseltrust.com]
+IpAddress: []
+Username: [edcrooks]
+Password: []
+HashedPassword: []
+HashType:
+Name: []
+Vin: []
+Address: []
+Phone: []
+DatabaseName: MyFitnessPal
+
+Id: iPykdNMRZd5-FWZi6NTb
+Email: [ddolan@counseltrust.com]
+IpAddress: []
+Username: []
+Password: [5c480f68b]
+HashedPassword: [35452b0e9763678ce20606aff52e399c:||MD5 ff7f60425e953d03e14024a7ed0a9f90bf2ea90e:||SHA1 0ba12cc8988083b4b1db7cecdb319bb6e6a01f19c383cfc01194ac79356e96ee:||SHA256 001ec68783cb044cc83a8d5f396916ff94a4550c37c056dbb0ee9dceede14736fccd39102575cf2f92d69c23822164b5644df77939b5364bae2e0f303c0ca5ce:||SHA512 NWM0ODBmNjhi:||BASE64]
+HashType:
+Name: []
+Vin: []
+Address: []
+Phone: []
+DatabaseName: Collections
+
+Id: y6tqwdMRZd5-FWZiayk2
+Email: [crooks@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: [counseltrust.com:None||Cisco-PIX(MD5) COUNSELTRUST.COM:None||Cisco-PIX(MD5)]
+HashType:
+Name: [Brandon Crooks]
+Vin: []
+Address: [224 Saint Charles Way Ste 100, York, PA, 17402, US]
+Phone: [7177181611]
+DatabaseName: DemandScience by Pure Incubation
+
+Id: iLTwwYMRbgUelnkGiiW6
+Email: [paul@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: [COUNSELTRUST.COM:None||Cisco-PIX(MD5)]
+HashType:
+Name: [PAUL STEVENSON]
+Vin: []
+Address: [224 ST CHARLES WAY SUITE 100, YORK, PA, 17402, US]
+Phone: [7177181600]
+DatabaseName: DemandScience by Pure Incubation
+
+Id: mqOPwYMRv-jq3VAHGN4o
+Email: [crooks@counseltrust.com]
+IpAddress: []
+Username: [https://www.linkedin.com/in/brandon-crooks-733a315/]
+Password: []
+HashedPassword: [counseltrust.com:None||Cisco-PIX(MD5)]
+HashType:
+Name: [Brandon Crooks]
+Vin: []
+Address: [224 Saint Charles Way Ste 100, York, PA, 17402, US]
+Phone: [7177181611]
+DatabaseName: DemandScience by Pure Incubation
+
+Id: lZ0LwdMRbgUelnkGVn_G
+Email: [ecrooks@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: [counseltrust.com:None||Cisco-PIX(MD5)]
+HashType:
+Name: [EDWARD CROOKS]
+Vin: []
+Address: [224 SAINT CHARLES WAY, YORK, PA, 17402-4664, US]
+Phone: [7177181601]
+DatabaseName: DemandScience by Pure Incubation
+
+Id: 5t5mwYMRiA0xW47jCGZa
+Email: [ddolan@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: [counseltrust.com:None||Cisco-PIX(MD5) COUNSELTRUST.COM:None||Cisco-PIX(MD5)]
+HashType:
+Name: [David Dolan]
+Vin: []
+Address: [224 Saint Charles Way, York, PA, 17402-4644, US]
+Phone: [7177181601]
+DatabaseName: DemandScience by Pure Incubation
+
+Id: 877zwNMRbgUelnkG9SWf
+Email: [pstevenson@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: [$2y$10$/UdtWJ1fbpOa0yJMAyDzkeuBHktRBahxlNkwZ2zW11DQiawcc3O8G:None||Blowfish(OpenBSD)]
+HashType:
+Name: [PAUL STEVENSON]
+Vin: []
+Address: [224 SAINT CHARLES WAY STE 100, YORK, PA, 17402-4665, US]
+Phone: [7177181601]
+DatabaseName: DemandScience by Pure Incubation
+
+Id: PF7owNMRs35batud-AC5
+Email: [stiles@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: [counseltrust.com:None||Cisco-PIX(MD5) COUNSELTRUST.COM:None||Cisco-PIX(MD5)]
+HashType:
+Name: [Bonnie M. Stiles]
+Vin: []
+Address: [224 Saint Charles Way, York, PA, 17402, US]
+Phone: [7177181601]
+DatabaseName: DemandScience by Pure Incubation
+
+Id: g1K4wdMRbgUelnkGiwtt
+Email: [dolan@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: [COUNSELTRUST.COM:None||Cisco-PIX(MD5)]
+HashType:
+Name: [DAVID DOLAN]
+Vin: []
+Address: [224 SCHARLES WY, YORK, PA, 17402, US]
+Phone: [7177181601]
+DatabaseName: DemandScience by Pure Incubation
+
+Id: 5uGAzYMRiA0xW47jZ0SG
+Email: [gplatt@counseltrust.com]
+IpAddress: []
+Username: [871fc67187061029d70c29640]
+Password: []
+HashedPassword: []
+HashType:
+Name: [gplatt@counseltrustcom]
+Vin: []
+Address: []
+Phone: []
+DatabaseName: ShareThis.com
+
+Id: vfJvwYMRiA0xW47jrHLh
+Email: [onnie@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: [COUNSELTRUST.COM:None||Cisco-PIX(MD5)]
+HashType:
+Name: [BONNIE STILES]
+Vin: []
+Address: [224 ST CHARLES WAY SUITE 100, YORK, PA, 17402, US]
+Phone: [7177181600]
+DatabaseName: DemandScience by Pure Incubation
+
+Id: GHKcwdMRbgUelnkGrEIP
+Email: [onnie.stiles@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: [COUNSELTRUST.COM:None||Cisco-PIX(MD5)]
+HashType:
+Name: [BONNIE STILES]
+Vin: []
+Address: [224 ST CHARLES WAY SUITE 100, YORK, PA, 17402, US]
+Phone: [7177181600]
+DatabaseName: DemandScience by Pure Incubation
+
+Id: W56-x5MRv-jq3VAHVOq9
+Email: [stiles@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: []
+HashType:
+Name: []
+Vin: []
+Address: []
+Phone: []
+DatabaseName: LinkedIn
+
+Id: DuS0wYMRbgUelnkG2EyB
+Email: [gplatt@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: [$2a$05$b8axmc6a1T7pXoZXGq/KR.OGSSM.swfjqCMNRbGsIjL5is6stNX5S:None||Blowfish(OpenBSD)]
+HashType:
+Name: []
+Vin: []
+Address: []
+Phone: []
+DatabaseName: ParkMobile
+
+Id: 11M_wYMRs35batud_mIn
+Email: [pstevenson@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: []
+HashType:
+Name: []
+Vin: []
+Address: []
+Phone: []
+DatabaseName: LinkedIn
+
+Id: QooWwdMRv-jq3VAH6-uY
+Email: [gplatt@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: []
+HashType:
+Name: [GEOFFREY PLATT]
+Vin: []
+Address: [224 SAINT CHARLES WAY STE 100, YORK, PA, 17402, US]
+Phone: [7177181611]
+DatabaseName: DemandScience by Pure Incubation
+
+Id: AItfnYMRv-jq3VAHEtYB
+Email: [dwells@counseltrust.com]
+IpAddress: []
+Username: []
+Password: []
+HashedPassword: [19a673bd64ef80608f70626c1441d578:None||MD5]
+HashType:
+Name: []
+Vin: []
+Address: [NU]
+Phone: []
+DatabaseName: Chegg
+