18 Commits

Author SHA1 Message Date
KrakenTech 375aac0fca Merge pull request #2 from Kraken-OffSec/fixes-for-accidental-find-and-replace
Updated README.md
2025-05-16 20:22:27 -04:00
Ar1ste1a 32150ce6ee Updated README.md
updated typos

added 'easy time' upgrade to log filtering
2025-05-16 20:21:36 -04:00
Evan Hosinski 91fd75abe2 Updated table to include lookup 2025-05-16 18:46:16 -04:00
Evan Hosinski cc3016c5fb Updated table to include lookup 2025-05-16 18:45:24 -04:00
KrakenTech 86ceeba6d4 Merge pull request #1 from Kraken-OffSec/expand-database-to-include-whois
Added whois and lookups to queryable columnns and tables.
2025-05-16 16:30:07 -04:00
Evan Hosinski e8e8cede33 Added whois and lookups to queryable columnns and tables.
Removed unused db.go file.
2025-05-16 16:29:33 -04:00
KrakenTech f5a5f07997 Update Makefile 2025-05-16 15:57:38 -04:00
Evan Hosinski 8dbd83f233 Added photos to README.md 2025-05-16 15:47:11 -04:00
Evan Hosinski 8246253738 Updates to allow for enhanced debugging.
Added structs for whois calls.

Added ability to write WhoIs to file.

Added structured output for Whois Records.

Added String Method for WhoIsRecord and WhoIsHistory Records.
2025-05-16 15:33:43 -04:00
Evan Hosinski 65c4ea6a15 Updates to allow for enhanced debugging.
Added structs for whois calls.

Added ability to write WhoIs to file.

Added structured output for Whois Records.

Added String Method for WhoIsRecord and WhoIsHistory Records.
2025-05-16 15:33:29 -04:00
KrakenTech ef5a8149e1 Update README.md 2025-05-15 21:28:30 -04:00
KrakenTech 21bf091f0e Update README.md 2025-05-15 21:27:31 -04:00
Evan Hosinski 93a5ce069d Update filenames 2025-05-15 21:22:21 -04:00
Evan Hosinski 85f315dafe Updated photos
blurred content
2025-05-15 21:19:32 -04:00
Ar1ste1a e05b54fb29 Updated README.md added readme photos 2025-05-15 21:14:57 -04:00
Ar1ste1a 7a6ae01254 Updated README.md 2025-05-15 21:11:24 -04:00
Ar1ste1a 02ef2f78fb Added Makefile 2025-05-15 17:24:45 -04:00
Evan Hosinski 473bdbff0b Added enhanced logging for specific errors related to dehash tied into the apiu calls. Added logs subcommand. 2025-05-15 16:38:14 -04:00
34 changed files with 2807 additions and 867 deletions
+2
View File
@@ -1 +1,3 @@
.idea/* .idea/*
build/*
.DS_Store
Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

-1
View File
@@ -1 +0,0 @@
txt
Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

+53
View File
@@ -0,0 +1,53 @@
# Makefile for Dehasher
# Go command
GO=go
# Binary name
BINARY_NAME=dehasher
# Build directory
BUILD_DIR=build/bin
# Platforms to build for
PLATFORMS=linux darwin windows
# Architecture to build for
ARCHS=amd64 arm64
# Version info from git tag or default
VERSION=$(shell git describe --tags 2>/dev/null || echo "v1.2.0")
.PHONY: all clean build build-all
# Default target
all: clean build-all
# Clean build artifacts
clean:
rm -rf $(BUILD_DIR)
mkdir -p $(BUILD_DIR)
# Build for current platform
build:
$(GO) build -o $(BUILD_DIR)/$(BINARY_NAME) -ldflags "-X main.version=$(VERSION)" dehasher.go
# Build for all platforms
build-all: clean
@for platform in $(PLATFORMS); do \
for arch in $(ARCHS); do \
echo "Building for $$platform/$$arch..."; \
GOOS=$$platform GOARCH=$$arch $(GO) build -o $(BUILD_DIR)/$(BINARY_NAME)-$$platform-$$arch -ldflags "-X main.version=$(VERSION)" dehasher.go; \
if [ "$$platform" = "windows" ]; then \
mv $(BUILD_DIR)/$(BINARY_NAME)-$$platform-$$arch $(BUILD_DIR)/$(BINARY_NAME)-$$platform-$$arch.exe; \
fi; \
done; \
done
# Install locally
install: build
cp $(BUILD_DIR)/$(BINARY_NAME) /usr/local/bin/
# Run tests
test:
$(GO) test ./...
+264 -93
View File
@@ -1,69 +1,36 @@
# Dehasher # 🚀 Dehasher
## A cli tool built for interaction with the Dehash API ### A CLI tool for seamless interaction with the Dehashed API
<div align="center"> ---
<img src="https://img.wanman.io/fUSu0/SaCUyEMe87.png/raw" style="width: 350px; height: auto" alt="Ar1ste1a" title="Ar1ste1a Offensive Security">
</div>
# Features ## 🌟 Features
- Output Format Control - **Output Format Control**: JSON, YAML, XML, and TEXT support.
- Request Limiting - **Regex & Wildcard Matching**: Flexible query options.
- Record Limiting - **Local Database Storage**: Default or custom paths.
- Regular Expression Handling - **Database Querying**: Raw SQL and filtered queries.
- Exact Match Handling - **Enhanced Logging**: Easy log parsing and rotation.
- Error Handling - **Error Handling**: Intelligent API error management.
- Credential Dumping - **WhoIs Lookups**: Domain, IP, MX, NS, and more.
- Intelligent Token Usage - **Subdomain Scanning**: Identify subdomains.
- Database Path Configuration - **Robust Logging**: Detailed logs for debugging.
# Options - **API Key Management**: Securely store and manage API keys.
- **Formatted Output**: Easy to read and understand.
- **Intuitive Database Querying**: Query for specific information.
```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 ## 📦 Installation
options: Clone the repository and build the tool:
-h --help show this help message and exit ```bash
-m --max-records Maximum amount of records to return git clone https://github.com/Ar1ste1a/Dehasher.git
-r --max-requests Maximum number of requests to make cd Dehasher
-B --print-balance Print remaining balance after requests go build dehasher.go
-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
--local-db Use local database in current directory
v1.0
``` ```
# Sample Run <hr></hr>
```bash-session
-k ddq<redacted> -a ar1ste1a@<redacted> -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
# Getting Started
To begin, clone the repository To begin, clone the repository
``` bash-session ``` bash-session
@@ -72,13 +39,26 @@ cd Dehasher
go build dehasher.go go build dehasher.go
``` ```
# Database Configuration <hr></hr>
## 🛠️ Initial Setup
Dehasher requires an API key from Dehashed. Set it up with:
```bash
ar1ste1a@kali:~$ dehasher set-key <redacted>
```
<hr></hr>
## 🗄️ Database Configuration
Dehasher supports two database storage options: Dehasher supports two database storage options:
1. **Default Path** (default): Stores the database at `~/.local/share/Dehasher/db/dehashed.sqlite` 1. **Default Path** (default): Stores the database at `~/.local/share/Dehasher/db/dehashed.sqlite`
2. **Local Path**: Stores the database in the current directory as `./dehasher.sqlite` 2. **Local Path**: Stores the database in the current directory as `./dehasher.sqlite`
The **Local Path** option allows for separate databases for different projects or engagements.
To configure the database location: To configure the database location:
```bash ```bash
@@ -89,61 +69,252 @@ To configure the database location:
./dehasher set-local-db false ./dehasher set-local-db false
``` ```
You can also specify the database location when running commands: <hr></hr>
```bash ## 🔍 Crafting Queries
# Use local database for this command only
./dehasher -k YOUR_API_KEY -a YOUR_EMAIL -E @example.com --local-db
```
# Crafting a query ### Simple Query
Dehasher can be used simply for example to query for credentials matching a given email domain.
## Simple Query
``` go ``` go
# Provide credentials for emails matching @target.com # Provide credentials for emails matching @target.com
dehasher -k ddq<redacted> -a ar1ste1a@domain.tld -E @target.com dehasher api -D @target.com -C
``` ```
## Simple Credentials Query ### Simple Credentials Query
Dehasher can also be used to return only credentials for a given query.
``` go ``` go
# Provide credentials for emails matching @target.com # Provide credentials for emails matching @target.com
dehasher -k ddq<redacted> -a ar1ste1a@domain.tld -E @target.com -C dehasher api -E @target.com -C
``` ```
## Simple Query Returning Balance ### Multiple Match Query
Dehasher is capable of handling multiple queries for the same field.
This is useful for when you want to search for multiple domains, or multiple usernames.
``` go ``` go
# Provide credentials for emails matching @target.com # Provide credentials for emails matching @target.com and @target2.com
dehasher -k ddq<redacted> -a ar1ste1a@domain.tld -E @target.com -C -B dehasher api -E @target.com,@target2.com -C
``` ```
## Regex Query ### Wildcard Query
Dehasher is capable of handling wildcard queries.
A wildcard query cannot begin with a wildcard.
This is a limitation of the Dehashed API.
An asterisk can be used to denote multiple characters, and a question mark can be used to denote a single character.
![Alt text](.img/wildcard_sample.png "Wildcard Query")
``` go
# Provide credentials for emails matching @target.com and @target2.com
dehasher api -E @target?.com -C -W
```
### Regex Query
Dehasher is capable of handling regex queries.
Simply denote regex queries with the `-R` flag.
Place all regex queries in quotes with the corresponding query flag in single quotes.
``` go ``` go
# Return matches for emails matching this given regex query # Return matches for emails matching this given regex query
# -R e: Specify the '-E' field as a regex entry dehasher api -R -e '[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)?@target.com'
dehasher -k ddq<redacted> -a ar1ste1a@domain.tld -E '[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)?@target.com' -C -B -R e
``` ```
## Exact Match Query ### Output Text (default JSON)
``` go Dehasher is capable of handling output formats.
# Return matches for usernames exactly matching "admin" The default output format is JSON.
# -X u: Specify the '-U' field as an exact match entry To change the output format, use the `-f` flag.
dehasher -k ddq<redacted> -a ar1ste1a@domain.tld -C -B -U admin -X u Dehasher currently supports JSON, YAML, XML, and TEXT output formats.
```
## Output Text (default JSON)
``` go ``` go
# Return matches for usernames exactly matching "admin" and write to text file 'admins_file.txt' # Return matches for usernames exactly matching "admin" and write to text file 'admins_file.txt'
dehasher -k ddq<redacted> -a ar1ste1a@domain.tld -C -B -U admin -X u -T -o admins_file dehasher api -U admin -o admins_file -f txt
``` ```
## Output YAML <hr></hr>
``` go
# Return matches for usernames exactly matching "admin" and write to yaml file 'admins_file.yaml' ## 🌐 WhoIs Lookups
dehasher -k ddq<redacted> -a ar1ste1a@domain.tld -C -B -U admin -X u -Y -o admins_file Dehasher supports WHOIS lookups, history searches, reverse WHOIS searches, IP lookups, MX lookups, NS lookups, and subdomain scans.
The WhoIs Lookups require a separate API Credit from the Dehashed API.
### Domain Lookup
Dehasher can perform a domain lookup for a given domain.
This provides a tree view of the domain's WHOIS information.
![Alt text](.img/tree_whois_lookup.png "WhoIs Tree View")
```bash
# Perform a WHOIS lookup for example.com
dehasher whois -d example.com
``` ```
## Output XML ### History Lookup
``` go History Lookups require 25 credits.
# Return matches for usernames exactly matching "admin" and write to xml file 'admins_file.xml' This is a Dehashed API limitation.
dehasher -k ddq<redacted> -a ar1ste1a@domain.tld -C -B -U admin -X u -x -o admins_file The history lookup is immediately written to file and not displayed in the terminal or stored in the database.
```bash
# Perform a WHOIS history search for example.com
dehasher whois -d example.com -H
``` ```
### Reverse WHOIS Lookup
Dehasher can perform a reverse WHOIS lookup for given criteria.
This provides a list of all domains that match the given query.
The reverse WHOIS lookup is immediately written to file and not displayed in the terminal or stored in the database.
```bash
# Perform a reverse WHOIS lookup for example.com
dehasher whois -I example.com
```
### IP Lookup
Dehasher can perform a reverse IP lookup for a given IP address.
This provides a list of all domains that match the given query.
![Alt text](.img/reverse_ip_lookup.png "WhoIs Tree View")
```bash
# Perform a reverse IP lookup for 8.8.8.8
dehasher whois -i 8.8.8.8
```
### MX Lookup
Dehasher can perform an MX lookup for a given MX hostname.
This provides a list of all domains that match the given query.
![Alt text](.img/mx_lookup.png "WhoIs Tree View")
```bash
# Perform a reverse MX lookup for google.com
dehasher whois -m google.com
```
### NS Lookup
Dehasher can perform an NS lookup for a given NS hostname.
This provides a list of all domains that match the given query.
The picture below also includes the --debug global flag.
![Alt text](.img/debug_ns_search.png "WhoIs Tree View")
```bash
# Perform a reverse NS lookup for google.com
dehasher whois -n google.com
```
### Subdomain Scan
Dehasher can perform a subdomain scan for a given domain.
This provides a list of all subdomains that match the given query.
![Alt text](.img/subdomains_lookup.png "WhoIs Tree View")
```bash
# Perform a WHOIS subdomain scan for google.com
dehasher whois -d google.com -s
```
---
## 📊 Database Querying
Dehasher stores query results in a local database.
This database can be queried for previous results.
This database also includes WhoIs Information and Subdomain Scan results, but does **not** include historical lookups.
## Simple Query
![Alt text](.img/simple_query_db.png "Simple Query")
Dehasher supports querying the database for previous results.
This is useful for when you want to query for specific information.
```bash
# Query the database for all results containing the word 'admin' in the username
dehasher query -t results -q "username LIKE '%admin%'"
```
## Raw SQL Queries
![Alt text](.img/raw_query_db.png "Raw Query")
Dehasher also supports raw SQL queries. This is useful for when you want to query for specific information.
```bash
# Query the database for all results containing the word 'admin' in the username
dehasher query -r "SELECT * FROM results WHERE username LIKE '%admin%'"
```
## Query Options
Dehasher supports a number of query options. These options can be used to filter the results of a query.
```bash
# Query the database for all results containing the word 'admin' in the username
dehasher query -t results -q "username LIKE '%admin%'" -n username,email,password
```
## Listing Tables and Columns
Dehasher supports listing all available tables and columns.
This is useful for when you want to query for specific information.
```bash
# List all available tables and columns
dehasher query -a
```
The current tables available for query are:
- results
- Results from a dehashed query
- creds
- Credentials parsed from dehashed results
- whois
- Results from a whois record lookup
- subdomains
- Subdomains discovered in a whois subdomain scan
- runs
- Previous query runs to the dehashed API
- lookup
- Results of any Whois NS, MX, or IP lookup
---
# Exporting Results
Dehasher supports exporting results to a file.
This is useful for when you want to requery for specific information without touching the Dehashed API.
The export subcommand supports all the same options as the query subcommand.
The export subcommand also supports file naming and output format control.
```bash
# Export all results containing the word 'admin' in the username to a text file
dehasher export -t results -q "username LIKE '%admin%'" -o admins_file -f txt
```
## 🐛 Debugging
Dehasher uses the `zap` logging library for logging. The logs are stored in `~/.local/share/Dehasher/logs`.
The logs can be easily queried from the Dehasher CLI.
```bash
# Show the last 10 logs
dehasher logs -l 10
# Show logs from the last 24 hours
dehasher logs -s "last 24 hours"
# Show logs from the last 24 hours with a severity of error or fatal
dehasher logs -s "05-01-2025" -v error,fatal
```
### Logs Dates
#### Dehasher utilized 'easy time' to determine the appropriate time for a given query.
![Alt text](.img/easy_time_parsing.png "Easy Time")
#### You may also used dates mixed with easy time to perform queries.
![Alt text](.img/easy_time_query_2.png "Mixed Time")
#### The following formats are supported:
- `last 24 hours`
- `last 2 days`
- `30 minutes ago`
- `45 seconds ago`
- `1 week ago`
- `05-01-2025`
- `05/01/2025`
- `05/01/25`
- `05-01-25`
- `May 01, 2025`
## 🎉 Sample Run
```bash
ar1ste1a@kali:~$ dehasher api -D <redacted>.com -o <redacted> -f json
Making 3 Requests for 10000 Records (30000 Total)
[*] Querying Dehashed API...
[*] Performing Request...
[+] Retrieved 2740 Records
[-] Not Enough Entries, ending queries
[+] Discovered 10 Credentials
[*] Writing entries to file: <redacted>.json
[*] Success
[*] Completing Process
```
## 🤝 Contributing
Contributions are welcome! Submit a pull request to help improve Dehasher.
<div align="center">
<img src="https://img.wanman.io/fUSu0/jUtovIFE52.png/raw" style="width: 350px; height: auto" alt="Ar1ste1a" title="Ar1ste1a Offensive Security">
</div>
## **Release The Kraken**
+19 -5
View File
@@ -2,17 +2,19 @@ package cmd
import ( import (
"dehasher/internal/badger" "dehasher/internal/badger"
"dehasher/internal/query" "dehasher/internal/debug"
"dehasher/internal/dehashed"
"dehasher/internal/sqlite" "dehasher/internal/sqlite"
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.uber.org/zap"
) )
func init() { func init() {
// Add query command to root command // Add query command to root command
rootCmd.AddCommand(apiCmd) rootCmd.AddCommand(apiCmd)
// Add flags specific to query command // Add flags specific to api command
apiCmd.Flags().IntVarP(&maxRecords, "max-records", "m", 30000, "Maximum amount of records to return") apiCmd.Flags().IntVarP(&maxRecords, "max-records", "m", 30000, "Maximum amount of records to return")
apiCmd.Flags().IntVarP(&maxRequests, "max-requests", "r", -1, "Maximum number of requests to make") apiCmd.Flags().IntVarP(&maxRequests, "max-requests", "r", -1, "Maximum number of requests to make")
apiCmd.Flags().IntVarP(&startingPage, "starting-page", "s", 1, "Starting page for requests") apiCmd.Flags().IntVarP(&startingPage, "starting-page", "s", 1, "Starting page for requests")
@@ -36,7 +38,7 @@ func init() {
apiCmd.Flags().StringVarP(&hashQuery, "hash", "Q", "", "Hashed password query") apiCmd.Flags().StringVarP(&hashQuery, "hash", "Q", "", "Hashed password query")
apiCmd.Flags().StringVarP(&nameQuery, "name", "N", "", "Name query") apiCmd.Flags().StringVarP(&nameQuery, "name", "N", "", "Name query")
// Add mutually exclusive flags to exact match and regex match // Add mutually exclusive flags to wildcard match and regex match
apiCmd.MarkFlagsMutuallyExclusive("regex-match", "wildcard-match") apiCmd.MarkFlagsMutuallyExclusive("regex-match", "wildcard-match")
} }
@@ -103,10 +105,11 @@ var (
wildcardMatch, wildcardMatch,
printBalance, printBalance,
credsOnly, credsOnly,
debugGlobal,
) )
// Create new Dehasher // Create new Dehasher
dehasher := query.NewDehasher(queryOptions) dehasher := dehashed.NewDehasher(queryOptions)
dehasher.SetClientCredentials( dehasher.SetClientCredentials(
key, key,
) )
@@ -115,7 +118,18 @@ var (
dehasher.Start() dehasher.Start()
fmt.Println("\n[*] Completing Process") fmt.Println("\n[*] Completing Process")
sqlite.StoreQueryOptions(queryOptions) err := sqlite.StoreQueryOptions(queryOptions)
if err != nil {
if debugGlobal {
debug.PrintInfo("failed to store query options")
debug.PrintError(err)
}
zap.L().Error("store_query_options",
zap.String("message", "failed to store query options"),
zap.Error(err),
)
fmt.Printf("Error storing query options: %v\n", err)
}
}, },
} }
) )
+70 -4
View File
@@ -16,8 +16,8 @@ func init() {
// Add flags specific to export command // Add flags specific to export command
exportCmd.Flags().IntVarP(&exportLimitRows, "limit", "l", 100, "Limit number of results") exportCmd.Flags().IntVarP(&exportLimitRows, "limit", "l", 100, "Limit number of results")
exportCmd.Flags().BoolVarP(&exportListAll, "list-all", "a", false, "List all columns") exportCmd.Flags().BoolVarP(&exportListAll, "list-all", "a", false, "List all tables and their columns")
exportCmd.Flags().StringVarP(&exportTableName, "table", "t", "", "Table to export") exportCmd.Flags().StringVarP(&exportTableName, "table", "t", "", "Table to export (results, creds, whois, subdomains, history, runs)")
exportCmd.Flags().StringVarP(&exportNotNull, "not-null", "n", "", "Filter for non-null values (comma-separated list, e.g., 'password,email')") exportCmd.Flags().StringVarP(&exportNotNull, "not-null", "n", "", "Filter for non-null values (comma-separated list, e.g., 'password,email')")
exportCmd.Flags().StringVarP(&exportColumns, "columns", "c", "", "Columns to display in output (comma-separated list, e.g., 'username,email,password')") exportCmd.Flags().StringVarP(&exportColumns, "columns", "c", "", "Columns to display in output (comma-separated list, e.g., 'username,email,password')")
exportCmd.Flags().StringVarP(&exportUserQuery, "user-query", "q", "", "User query to execute") exportCmd.Flags().StringVarP(&exportUserQuery, "user-query", "q", "", "User query to execute")
@@ -50,6 +50,12 @@ var (
Use: "export", Use: "export",
Short: "Export database to file", Short: "Export database to file",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// If list-all flag is set, list all tables and columns
if exportListAll {
listAvailableTables()
return
}
fmt.Println("[*] Exporting database...") fmt.Println("[*] Exporting database...")
// If Raw Query is set, execute it and export // If Raw Query is set, execute it and export
@@ -59,11 +65,65 @@ var (
return return
} }
// Validate table name
if exportTableName == "" {
fmt.Println("[!] Error: Table name is required. Use -t or --table to specify a table.")
fmt.Println("[*] Available tables: results, creds, whois, subdomains, history, runs")
fmt.Println("[*] Use --list-all to see all tables and their columns.")
return
}
if !isValidTable(exportTableName) {
fmt.Printf("[!] Error: Unknown table '%s'.\n", exportTableName)
fmt.Println("[*] Available tables: results, creds, whois, subdomains, history, runs")
fmt.Println("[*] Use --list-all to see all tables and their columns.")
return
}
// Validate columns if specified
if exportColumns != "" {
columns := strings.Split(exportColumns, ",")
invalidColumns := validateColumns(exportTableName, columns)
if len(invalidColumns) > 0 {
fmt.Printf("[!] Error: Invalid column(s) for table '%s': %s\n",
exportTableName, strings.Join(invalidColumns, ", "))
fmt.Println("[*] Available columns for this table:")
for i := 0; i < len(availableTables[exportTableName]); i += 5 {
end := i + 5
if end > len(availableTables[exportTableName]) {
end = len(availableTables[exportTableName])
}
fmt.Printf(" %s\n", strings.Join(availableTables[exportTableName][i:end], ", "))
}
return
}
}
// Validate not-null fields if specified
if exportNotNull != "" {
notNullFields := strings.Split(exportNotNull, ",")
invalidFields := validateColumns(exportTableName, notNullFields)
if len(invalidFields) > 0 {
fmt.Printf("[!] Error: Invalid not-null field(s) for table '%s': %s\n",
exportTableName, strings.Join(invalidFields, ", "))
fmt.Println("[*] Available columns for this table:")
for i := 0; i < len(availableTables[exportTableName]); i += 5 {
end := i + 5
if end > len(availableTables[exportTableName]) {
end = len(availableTables[exportTableName])
}
fmt.Printf(" %s\n", strings.Join(availableTables[exportTableName][i:end], ", "))
}
return
}
}
// Determine which table to query based on the tableTypeDBQuery parameter // Determine which table to query based on the tableTypeDBQuery parameter
table := sqlite.GetTable(exportTableName) table := sqlite.GetTable(exportTableName)
if table == sqlite.UnknownTable { if table == sqlite.UnknownTable {
fmt.Printf("Error: Unknown table type '%s'.\n", exportTableName) fmt.Printf("[!] Error: Unknown table type '%s'.\n", exportTableName)
cmd.Help() fmt.Println("[*] Available tables: results, creds, whois, subdomains, history, runs")
fmt.Println("[*] Use --list-all to see all tables and their columns.")
return return
} }
@@ -99,6 +159,12 @@ func exportTableQuery(table sqlite.Table) {
// Get the object for the table // Get the object for the table
object := table.Object() object := table.Object()
// Check if object is nil (invalid table)
if object == nil {
fmt.Printf("[!] Error: Table '%s' is not valid or does not exist.\n", exportTableName)
return
}
// Query the database // Query the database
db := sqlite.GetDB() db := sqlite.GetDB()
query := db.Model(object).Select(columns) query := db.Model(object).Select(columns)
+11 -32
View File
@@ -1,6 +1,7 @@
package cmd package cmd
import ( import (
"dehasher/internal/easyTime"
"dehasher/internal/pretty" "dehasher/internal/pretty"
"encoding/json" "encoding/json"
"fmt" "fmt"
@@ -73,6 +74,11 @@ var (
allLogs = append(allLogs, filepath.Join(logsPath, "info.log"), filepath.Join(logsPath, "error.log")) allLogs = append(allLogs, filepath.Join(logsPath, "info.log"), filepath.Join(logsPath, "error.log"))
} }
var timeChunk easyTime.TimeChunk
if logStartDate != "" {
timeChunk = easyTime.NewTimeChunk(logStartDate, logEndDate, debugGlobal)
}
var parsedLogs []LogEntry var parsedLogs []LogEntry
for _, logFile := range allLogs { for _, logFile := range allLogs {
// Read the log file // Read the log file
@@ -97,7 +103,7 @@ var (
continue continue
} }
// Also unmarshal to get additional fields // Unmarshal to get additional fields
if err := json.Unmarshal([]byte(line), &rawEntry); err != nil { if err := json.Unmarshal([]byte(line), &rawEntry); err != nil {
fmt.Printf("Error parsing raw log entry: %v\n", err) fmt.Printf("Error parsing raw log entry: %v\n", err)
continue continue
@@ -106,10 +112,10 @@ var (
// Parse the timestamp // Parse the timestamp
parsedTime, err := time.Parse("2006-01-02T15:04:05.999-0700", entry.Timestamp) parsedTime, err := time.Parse("2006-01-02T15:04:05.999-0700", entry.Timestamp)
if err != nil { if err != nil {
// Try alternative formats // Try RFC3339
parsedTime, err = time.Parse(time.RFC3339, entry.Timestamp) parsedTime, err = time.Parse(time.RFC3339, entry.Timestamp)
if err != nil { if err != nil {
// Try another format // Try RFC3339Nano
parsedTime, err = time.Parse(time.RFC3339Nano, entry.Timestamp) parsedTime, err = time.Parse(time.RFC3339Nano, entry.Timestamp)
if err != nil { if err != nil {
fmt.Printf("Error parsing timestamp '%s': %v\n", entry.Timestamp, err) fmt.Printf("Error parsing timestamp '%s': %v\n", entry.Timestamp, err)
@@ -133,22 +139,8 @@ var (
(logFatal && strings.EqualFold(entry.Level, "FATAL")) { (logFatal && strings.EqualFold(entry.Level, "FATAL")) {
// Filter by date range if specified // Filter by date range if specified
if logStartDate != "" { if timeChunk.IsSet() {
startDate, err := time.Parse("2006-01-02", logStartDate) if entry.ParsedTime.Before(timeChunk.StartTime) || entry.ParsedTime.After(timeChunk.EndTime) {
if err != nil {
fmt.Printf("Error parsing start date: %v\n", err)
} else if entry.ParsedTime.Before(startDate) {
continue
}
}
if logEndDate != "" {
endDate, err := time.Parse("2006-01-02", logEndDate)
// Add one day to include the end date
endDate = endDate.Add(24 * time.Hour)
if err != nil {
fmt.Printf("Error parsing end date: %v\n", err)
} else if entry.ParsedTime.After(endDate) {
continue continue
} }
} }
@@ -211,16 +203,3 @@ const (
FATAL FATAL
UNKNOWN Severity = -1 UNKNOWN Severity = -1
) )
func getSeverity(logLevel string) Severity {
switch logLevel {
case "INFO":
return INFO
case "ERROR":
return ERROR
case "FATAL":
return FATAL
default:
return UNKNOWN
}
}
+196 -7
View File
@@ -7,28 +7,144 @@ import (
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.uber.org/zap" "go.uber.org/zap"
"os"
"strings" "strings"
) )
// Map of available tables and their columns
var availableTables = map[string][]string{
"creds": {
"id", "created_at", "updated_at", "deleted_at", "email", "username", "password",
},
//"history": {
// "id", "created_at", "updated_at", "deleted_at", "domain_name", "domain_type",
// "registrar_name", "whois_server", "created_date_iso8601", "updated_date_iso8601", "expires_date_iso8601",
//},
"lookup": {
"id", "created_at", "updated_at", "deleted_at", "search_term", "type", "first_seen", "last_visit",
"name",
},
// Query Options
"runs": {
"id", "created_at", "updated_at", "deleted_at", "max_records", "max_requests", "starting_page",
"output_format", "output_file", "regex_match", "wildcard_match", "username_query", "email_query",
"ip_query", "pass_query", "hash_query", "name_query", "domain_query", "vin_query", "license_plate_query",
"address_query", "phone_query", "social_query", "crypto_address_query", "print_balance", "creds_only",
},
"results": {
"id", "created_at", "updated_at", "deleted_at", "dehashed_id", "email", "ip_address", "username",
"password", "hashed_password", "hash_type", "name", "vin", "license_plate", "url", "social",
"cryptocurrency_address", "address", "phone", "company", "database_name",
},
"subdomains": {
"id", "created_at", "updated_at", "deleted_at", "domain", "first_seen", "last_seen",
},
"whois": {
"id", "created_at", "updated_at", "deleted_at", "audit", "contact_email", "created_date", "created_date_normalized",
"domain_name", "domain_name_ext", "estimated_domain_age", "expires_date", "expires_date_normalized", "footer", "header",
"name_servers", "parse_code", "raw_text", "registrant", "registrar_iana_id", "registrar_name", "registry_data",
"status", "stripped_text", "updated_date", "updated_date_normalized",
},
}
// Function to list available tables and their columns
func listAvailableTables() {
fmt.Println("Available tables and columns:")
// Prepare data for pretty.Table
headers := []string{"Table", "Columns"}
var tableRows [][]string
// Sort tables alphabetically for consistent output
var tableNames []string
for tableName := range availableTables {
tableNames = append(tableNames, tableName)
}
// Simple bubble sort for table names
for i := 0; i < len(tableNames)-1; i++ {
for j := 0; j < len(tableNames)-i-1; j++ {
if tableNames[j] > tableNames[j+1] {
tableNames[j], tableNames[j+1] = tableNames[j+1], tableNames[j]
}
}
}
// Create rows for the table
for _, tableName := range tableNames {
columns := availableTables[tableName]
// Format columns with line breaks for better readability
var formattedColumns string
for i := 0; i < len(columns); i += 5 {
end := i + 5
if end > len(columns) {
end = len(columns)
}
if i > 0 {
formattedColumns += "\n"
}
formattedColumns += strings.Join(columns[i:end], ", ")
}
tableRows = append(tableRows, []string{tableName, formattedColumns})
}
// Display the table
pretty.Table(headers, tableRows)
}
// Function to validate table name
func isValidTable(tableName string) bool {
_, exists := availableTables[tableName]
return exists
}
// Function to validate column names for a specific table
func validateColumns(tableName string, columns []string) []string {
if tableName == "" || columns == nil || len(columns) == 0 || columns[0] == "*" {
return nil
}
tableColumns, exists := availableTables[tableName]
if !exists {
return []string{fmt.Sprintf("Table '%s' does not exist", tableName)}
}
var invalidColumns []string
for _, col := range columns {
valid := false
for _, tableCol := range tableColumns {
if col == tableCol {
valid = true
break
}
}
if !valid {
invalidColumns = append(invalidColumns, col)
}
}
return invalidColumns
}
func init() { func init() {
// Add whois command to root command // Add whois command to root command
rootCmd.AddCommand(queryCmd) rootCmd.AddCommand(queryCmd)
// Add flags specific to whois command // Add flags specific to whois command
queryCmd.Flags().StringVarP(&dbQueryTableName, "table", "t", "", "Table to query (results, creds, whois, subdomains, history, query_options)") queryCmd.Flags().StringVarP(&dbQueryTableName, "table", "t", "", "Table to query (results, creds, whois, subdomains, history, runs)")
queryCmd.Flags().IntVarP(&dbQueryLimitRows, "limit", "l", 100, "Limit number of results") queryCmd.Flags().IntVarP(&dbQueryLimitRows, "limit", "l", 100, "Limit number of results")
queryCmd.Flags().StringVarP(&dbQueryNotNull, "not-null", "n", "", "Filter for non-null values (comma-separated list, e.g., 'password,email')") queryCmd.Flags().StringVarP(&dbQueryNotNull, "not-null", "n", "", "Filter for non-null values (comma-separated list, e.g., 'password,email')")
queryCmd.Flags().StringVarP(&dbQueryColumns, "columns", "c", "", "Columns to display in output (comma-separated list, e.g., 'username,email,password')") queryCmd.Flags().StringVarP(&dbQueryColumns, "columns", "c", "", "Columns to display in output (comma-separated list, e.g., 'username,email,password')")
queryCmd.Flags().StringVarP(&dbQueryUserQuery, "user-query", "q", "", "User query to execute") queryCmd.Flags().StringVarP(&dbQueryUserQuery, "user-query", "q", "", "User query to execute")
queryCmd.Flags().StringVarP(&dbQueryRawQuery, "raw-query", "r", "", "Raw SQL query to execute") queryCmd.Flags().StringVarP(&dbQueryRawQuery, "raw-query", "r", "", "Raw SQL query to execute")
queryCmd.Flags().BoolVarP(&dbQueryListAll, "list-all", "a", false, "List all columns") queryCmd.Flags().BoolVarP(&dbQueryListAll, "list-all", "a", false, "List all tables and their columns")
// Add mutually exclusive flags to query and raw-query // Add mutually exclusive flags to query and raw-query
// Cannot use query and raw-query at the same time // Cannot use query and raw-query at the same time
queryCmd.MarkFlagsMutuallyExclusive("user-query", "raw-query") queryCmd.MarkFlagsMutuallyExclusive("user-query", "raw-query")
// Raw query does not require a table // Raw query does not require a table
queryCmd.MarkFlagsMutuallyExclusive("user-query", "table") queryCmd.MarkFlagsMutuallyExclusive("raw-query", "table")
// List all columns does not require a query or raw-query // List all columns does not require a query or raw-query
queryCmd.MarkFlagsMutuallyExclusive("raw-query", "list-all") queryCmd.MarkFlagsMutuallyExclusive("raw-query", "list-all")
} }
@@ -47,20 +163,81 @@ var (
Short: "Query the database", Short: "Query the database",
Long: `Query the database for various information.`, Long: `Query the database for various information.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// If list-all flag is set, list all tables and columns
if dbQueryListAll {
listAvailableTables()
return
}
// If Raw Query is set, execute it and return // If Raw Query is set, execute it and return
if dbQueryRawQuery != "" { if dbQueryRawQuery != "" {
fmt.Println("[*] Executing Raw Query...") fmt.Println("[*] Executing Raw Query...")
rawDBQuery() rawDBQuery()
os.Exit(1) return
}
// Validate table name
if dbQueryTableName == "" {
fmt.Println("[!] Error: Table name is required. Use -t or --table to specify a table.")
fmt.Println("[*] Available tables: results, creds, whois, subdomains, history, runs")
fmt.Println("[*] Use --list-all to see all tables and their columns.")
return
}
if !isValidTable(dbQueryTableName) {
fmt.Printf("[!] Error: Unknown table '%s'.\n", dbQueryTableName)
fmt.Println("[*] Available tables: results, creds, whois, subdomains, history, runs")
fmt.Println("[*] Use --list-all to see all tables and their columns.")
return
}
// Validate columns if specified
if dbQueryColumns != "" {
columns := strings.Split(dbQueryColumns, ",")
invalidColumns := validateColumns(dbQueryTableName, columns)
if len(invalidColumns) > 0 {
fmt.Printf("[!] Error: Invalid column(s) for table '%s': %s\n",
dbQueryTableName, strings.Join(invalidColumns, ", "))
fmt.Println("[*] Available columns for this table:")
for i := 0; i < len(availableTables[dbQueryTableName]); i += 5 {
end := i + 5
if end > len(availableTables[dbQueryTableName]) {
end = len(availableTables[dbQueryTableName])
}
fmt.Printf(" %s\n", strings.Join(availableTables[dbQueryTableName][i:end], ", "))
}
return
}
}
// Validate not-null fields if specified
if dbQueryNotNull != "" {
notNullFields := strings.Split(dbQueryNotNull, ",")
invalidFields := validateColumns(dbQueryTableName, notNullFields)
if len(invalidFields) > 0 {
fmt.Printf("[!] Error: Invalid not-null field(s) for table '%s': %s\n",
dbQueryTableName, strings.Join(invalidFields, ", "))
fmt.Println("[*] Available columns for this table:")
for i := 0; i < len(availableTables[dbQueryTableName]); i += 5 {
end := i + 5
if end > len(availableTables[dbQueryTableName]) {
end = len(availableTables[dbQueryTableName])
}
fmt.Printf(" %s\n", strings.Join(availableTables[dbQueryTableName][i:end], ", "))
}
return
}
} }
// Determine which table to query based on the tableTypeDBQuery parameter // Determine which table to query based on the tableTypeDBQuery parameter
table := sqlite.GetTable(dbQueryTableName) table := sqlite.GetTable(dbQueryTableName)
if table == sqlite.UnknownTable { if table == sqlite.UnknownTable {
fmt.Printf("Error: Unknown table type '%s'.\n", dbQueryTableName) fmt.Printf("[!] Error: Unknown table type '%s'.\n", dbQueryTableName)
cmd.Help() fmt.Println("[*] Available tables: results, creds, whois, subdomains, history, runs")
fmt.Println("[*] Use --list-all to see all tables and their columns.")
return return
} }
fmt.Println("[*] Querying Database...") fmt.Println("[*] Querying Database...")
tableQuery(table) tableQuery(table)
}, },
@@ -93,6 +270,12 @@ func tableQuery(table sqlite.Table) {
// Get the object for the table // Get the object for the table
object := table.Object() object := table.Object()
// Check if object is nil (invalid table)
if object == nil {
fmt.Printf("[!] Error: Table '%s' is not valid or does not exist.\n", dbQueryTableName)
return
}
// Query the database // Query the database
db := sqlite.GetDB() db := sqlite.GetDB()
query := db.Model(object).Select(columns) query := db.Model(object).Select(columns)
@@ -114,6 +297,7 @@ func tableQuery(table sqlite.Table) {
zap.Error(err), zap.Error(err),
) )
fmt.Printf("[!] Error executing query: %v\n", err) fmt.Printf("[!] Error executing query: %v\n", err)
return
} }
defer rows.Close() defer rows.Close()
@@ -125,6 +309,7 @@ func tableQuery(table sqlite.Table) {
zap.Error(err), zap.Error(err),
) )
fmt.Printf("[!] Error getting columns from query: %v\n", err) fmt.Printf("[!] Error getting columns from query: %v\n", err)
return
} }
// Prepare data for pretty.Table // Prepare data for pretty.Table
@@ -144,6 +329,7 @@ func tableQuery(table sqlite.Table) {
zap.Error(err), zap.Error(err),
) )
fmt.Printf("[!] Error scanning row from query: %v\n", err) fmt.Printf("[!] Error scanning row from query: %v\n", err)
continue
} }
// Convert row values to strings // Convert row values to strings
@@ -202,6 +388,7 @@ func rawDBQuery() {
zap.Error(err), zap.Error(err),
) )
fmt.Printf("[!] Error executing raw query: %v\n", err) fmt.Printf("[!] Error executing raw query: %v\n", err)
return
} }
defer rows.Close() defer rows.Close()
@@ -212,6 +399,7 @@ func rawDBQuery() {
zap.Error(err), zap.Error(err),
) )
fmt.Printf("[!] Error getting columns from raw query: %v\n", err) fmt.Printf("[!] Error getting columns from raw query: %v\n", err)
return
} }
// Prepare data for pretty.Table // Prepare data for pretty.Table
@@ -231,6 +419,7 @@ func rawDBQuery() {
zap.Error(err), zap.Error(err),
) )
fmt.Printf("[!] Error scanning row from raw query: %v\n", err) fmt.Printf("[!] Error scanning row from raw query: %v\n", err)
continue
} }
// Convert row values to strings // Convert row values to strings
+5 -7
View File
@@ -11,11 +11,12 @@ import (
var ( var (
// Global Flags // Global Flags
debugGlobal bool
// rootCmd is the base command for the CLI. // rootCmd is the base command for the CLI.
rootCmd = &cobra.Command{ rootCmd = &cobra.Command{
Use: "dehasher", Use: "dehasher",
Short: `Dehasher is a cli tool for querying query.`, Short: `Dehasher is a cli tool for querying the dehashed api.`,
Long: fmt.Sprintf( Long: fmt.Sprintf(
"%s\n%s", "%s\n%s",
` `
@@ -41,7 +42,7 @@ var (
––•–√\/––√\/––•––––•–√\/––√\/––•––––•–√\/––√\/––•––√\/––•––––•–√\/––√\/––•–– ––•–√\/––√\/––•––––•–√\/––√\/––•––––•–√\/––√\/––•––√\/––•––––•–√\/––√\/––•––
`, `,
), ),
Version: "v1.0", Version: "v1.2.1",
} }
) )
@@ -58,14 +59,11 @@ func Execute() {
} }
func init() { func init() {
//// Attempt to retrieve the useLocalDB
//useLocalDatabase := badger.GetUseLocalDB()
// Hide the default help command // Hide the default help command
rootCmd.CompletionOptions.HiddenDefaultCmd = true rootCmd.CompletionOptions.HiddenDefaultCmd = true
//// Add global flags // Add global flags
//rootCmd.PersistentFlags().BoolVar(&useLocalDatabase, "local-db", useLocalDatabase, "Use local database in current directory instead of default path") rootCmd.PersistentFlags().BoolVar(&debugGlobal, "debug", false, "Show debug information")
// Add subcommands // Add subcommands
rootCmd.AddCommand(setKeyCmd) rootCmd.AddCommand(setKeyCmd)
+361 -83
View File
@@ -1,14 +1,39 @@
package cmd package cmd
import ( import (
"dehasher/internal/debug"
"dehasher/internal/export"
"dehasher/internal/files"
"dehasher/internal/pretty"
"dehasher/internal/sqlite" "dehasher/internal/sqlite"
"dehasher/internal/whois" "dehasher/internal/whois"
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.uber.org/zap" "go.uber.org/zap"
"os"
"strings" "strings"
"time"
) )
func init() {
// Add whois subcommand to root command
rootCmd.AddCommand(whoisCmd)
// Add flags specific to whois command
whoisCmd.Flags().StringVarP(&whoisDomain, "domain", "d", "", "Domain for WHOIS lookup, history search, and subdomain scan")
whoisCmd.Flags().StringVarP(&whoisIPAddress, "ip", "i", "", "IP address for reverse IP lookup")
whoisCmd.Flags().StringVarP(&whoisMXAddress, "mx", "m", "", "MX hostname for reverse MX lookup")
whoisCmd.Flags().StringVarP(&whoisNSAddress, "ns", "n", "", "NS hostname for reverse NS lookup")
whoisCmd.Flags().StringVarP(&whoisInclude, "include", "I", "", "Up to 4 Terms to include in reverse WHOIS search (comma-separated)")
whoisCmd.Flags().StringVarP(&whoisExclude, "exclude", "E", "", "Up to 4 Terms to exclude in reverse WHOIS search (comma-separated)")
whoisCmd.Flags().StringVarP(&whoisReverseType, "type", "t", "registrant", "Type of reverse WHOIS search ([default] current or historic)")
whoisCmd.Flags().StringVarP(&whoisOutputFormat, "format", "f", "text", "Output format (text, json)")
whoisCmd.Flags().StringVarP(&whoisOutputFile, "output", "o", "whois", "File to output results to including extension")
whoisCmd.Flags().BoolVarP(&whoisShowCredits, "credits", "c", false, "Show remaining WHOIS credits")
whoisCmd.Flags().BoolVarP(&whoisHistory, "history", "H", false, "Perform WHOIS history search [25 Credits]")
whoisCmd.Flags().BoolVarP(&whoisSubdomainScan, "subdomains", "s", false, "Perform WHOIS subdomain scan")
}
var ( var (
// WHOIS command flags // WHOIS command flags
whoisDomain string whoisDomain string
@@ -19,6 +44,7 @@ var (
whoisExclude string whoisExclude string
whoisReverseType string whoisReverseType string
whoisOutputFormat string whoisOutputFormat string
whoisOutputFile string
whoisShowCredits bool whoisShowCredits bool
whoisHistory bool whoisHistory bool
whoisSubdomainScan bool whoisSubdomainScan bool
@@ -37,20 +63,44 @@ var (
return return
} }
// Show credits if requested if debugGlobal {
if whoisShowCredits { debug.PrintInfo("debug mode enabled")
fmt.Println("[*] Getting WHOIS credits...") zap.L().Info("whois_debug",
credits, err := whois.GetWHOISCredits(key) zap.String("message", "debug mode enabled"),
if err != nil {
zap.L().Error("get_whois_credits",
zap.String("message", "failed to get whois credits"),
zap.Error(err),
) )
fmt.Printf("Error getting WHOIS credits: %v\n", err) }
if whoisOutputFile == "" {
if debugGlobal {
debug.PrintInfo("output file not specified, using default")
}
whoisOutputFile = "whois_" + time.Now().Format("05_04_05")
}
if whoisOutputFormat == "" {
if debugGlobal {
debug.PrintInfo("output format not specified, using default")
}
whoisOutputFormat = "json"
}
fType := files.GetFileType(whoisOutputFormat)
if fType == files.UNKNOWN {
fmt.Println("[!] Error: Invalid output format. Must be 'json', 'xml', 'yaml', or 'txt'.")
return return
} }
fmt.Printf("WHOIS Credits: %d\n", credits.WhoisCredits) if debugGlobal {
return debug.PrintInfo("using output format: " + whoisOutputFormat)
}
w := whois.NewWhoIs(key, debugGlobal)
// Show credits if requested
if whoisShowCredits {
fmt.Println("[*] Getting WHOIS balance...")
if whoisShowCredits {
checkBalance(w)
}
} }
// Check if domain is provided for history and subdomain scan // Check if domain is provided for history and subdomain scan
@@ -59,14 +109,22 @@ var (
fmt.Println("Domain is required for history and subdomain scan.") fmt.Println("Domain is required for history and subdomain scan.")
return return
} }
if whoisShowCredits {
checkBalance(w)
}
} }
// Determine which operation to perform based on flags // Determine which operation to perform based on flags
if whoisDomain != "" { if whoisDomain != "" {
fmt.Println("[*] Performing WHOIS lookup...") fmt.Println("[*] Performing WHOIS lookup...")
// Domain lookup // Domain lookup
result, err := whois.WhoisSearch(whoisDomain, key) result, err := w.WhoisSearch(whoisDomain)
if err != nil { if err != nil {
if debugGlobal {
debug.PrintInfo("failed to perform whois search")
debug.PrintError(err)
}
zap.L().Error("whois_search", zap.L().Error("whois_search",
zap.String("message", "failed to perform whois search"), zap.String("message", "failed to perform whois search"),
zap.Error(err), zap.Error(err),
@@ -75,12 +133,20 @@ var (
return return
} }
if whoisShowCredits {
checkBalance(w)
}
// Fix the output format to use proper formatting // Fix the output format to use proper formatting
fmt.Printf("WHOIS Lookup Result:\n%+v\n", result.Data.WhoisRecord) fmt.Println("WHOIS Lookup Result:")
// Store the record // Store the record
err = sqlite.StoreWhoisRecord(result.Data.WhoisRecord) err = sqlite.StoreWhoisRecord(result)
if err != nil { if err != nil {
if debugGlobal {
debug.PrintInfo("failed to store whois record")
debug.PrintError(err)
}
zap.L().Error("store_whois_record", zap.L().Error("store_whois_record",
zap.String("message", "failed to store whois record"), zap.String("message", "failed to store whois record"),
zap.Error(err), zap.Error(err),
@@ -89,75 +155,163 @@ var (
// Continue execution even if storage fails // Continue execution even if storage fails
} }
// Pretty Print WhoIs Record
pretty.WhoIsTree(whoisDomain, result)
// Write WhoIs Record to file
if len(result.DomainName) != 0 {
fmt.Printf("[*] Writing WHOIS record to file: %s%s\n", whoisOutputFile, fType.Extension())
err = export.WriteWhoIsRecordToFile(result, whoisOutputFile, fType)
} else {
if debugGlobal {
debug.PrintInfo("no whois record to write to file")
}
zap.L().Info("write_whois_record",
zap.String("message", "no whois record to write to file"),
)
}
if whoisHistory { if whoisHistory {
fmt.Println("[*] Performing WHOIS history search...") fmt.Println("[*] Performing WHOIS history search...")
// Perform history search // Perform history search
history, err := whois.WhoisHistory(whoisDomain, key) historyRecords, err := w.WhoisHistory(whoisDomain)
if err != nil { if err != nil {
if debugGlobal {
debug.PrintInfo("failed to perform whois history lookup")
debug.PrintError(err)
}
zap.L().Error("whois_history", zap.L().Error("whois_history",
zap.String("message", "failed to perform whois history lookup"), zap.String("message", "failed to perform whois history lookup"),
zap.Error(err), zap.Error(err),
) )
fmt.Printf("Error performing WHOIS history lookup: %v\n", err) fmt.Printf("[!] Error performing WHOIS history lookup: %v\n", err)
} else { } else {
fmt.Println("\nWHOIS History:") if whoisShowCredits {
fmt.Println(history) checkBalance(w)
} }
err = sqlite.StoreHistoryRecord(history.Data.Records) // Write history records to file if any
if len(historyRecords) > 0 {
fmt.Println("[*] Records Found: %d\n", len(historyRecords))
fmt.Println("[*] WHOIS History being written to file: %s%s\n", whoisOutputFile, fType.Extension())
writeErr := export.WriteWhoIsHistoryToFile(historyRecords, whoisOutputFile, fType)
if writeErr != nil {
if debugGlobal {
debug.PrintInfo("failed to write whois history to file")
debug.PrintError(writeErr)
}
zap.L().Error("write_whois_history",
zap.String("message", "failed to write whois history to file"),
zap.Error(writeErr),
)
fmt.Printf("[!] Error writing WHOIS history to file: %v\n", writeErr)
}
err = sqlite.StoreHistoryRecord(historyRecords)
if err != nil { if err != nil {
if debugGlobal {
debug.PrintInfo("failed to store history record")
debug.PrintError(err)
}
zap.L().Error("store_history_record", zap.L().Error("store_history_record",
zap.String("message", "failed to store history record"), zap.String("message", "failed to store history record"),
zap.Error(err), zap.Error(err),
) )
fmt.Printf("Error storing WHOIS history record: %v\n", err) fmt.Printf("Error storing WHOIS history record: %v\n", err)
} }
} else {
if debugGlobal {
debug.PrintInfo("no whois history records to write to file")
}
zap.L().Info("write_whois_history",
zap.String("message", "no whois history records to write to file"),
)
}
}
} }
// Perform subdomain scan // Perform subdomain scan
if whoisSubdomainScan { if whoisSubdomainScan {
fmt.Println("[*] Performing WHOIS subdomain scan...") fmt.Println("[*] Performing WHOIS subdomain scan...")
subdomains, err := whois.WhoisSubdomainScan(whoisDomain, key) subdomains, err := w.WhoisSubdomainScan(whoisDomain)
if whoisShowCredits {
checkBalance(w)
}
if err != nil { if err != nil {
if debugGlobal {
debug.PrintInfo("failed to perform subdomain scan")
debug.PrintError(err)
}
zap.L().Error("whois_subdomain_scan", zap.L().Error("whois_subdomain_scan",
zap.String("message", "failed to perform subdomain scan"), zap.String("message", "failed to perform subdomain scan"),
zap.Error(err), zap.Error(err),
) )
fmt.Printf("Error performing subdomain scan: %v\n", err) fmt.Printf("Error performing subdomain scan: %v\n", err)
} else { } else {
fmt.Println("\nSubdomain Scan:") fmt.Println("Subdomain Scan:")
fmt.Println(subdomains) err = sqlite.StoreSubdomainRecords(subdomains)
}
err = sqlite.StoreSubdomainRecord(subdomains.Data.Result.Records)
if err != nil { if err != nil {
if debugGlobal {
debug.PrintInfo("failed to store subdomain record")
debug.PrintError(err)
}
zap.L().Error("store_subdomain_record", zap.L().Error("store_subdomain_record",
zap.String("message", "failed to store subdomain record"), zap.String("message", "failed to store subdomain record"),
zap.Error(err), zap.Error(err),
) )
fmt.Printf("Error storing WHOIS subdomain record: %v\n", err) fmt.Printf("Error storing WHOIS subdomain record: %v\n", err)
} }
}
// Get credits // Write the subdomains to file if any
credits, err := whois.GetWHOISCredits(key) if len(subdomains) > 0 {
fmt.Printf("[*] Writing subdomains to file: %s%s\n", whoisOutputFile, fType.Extension())
err = export.WriteSubdomainsToFile(subdomains, whoisOutputFile, fType)
if err != nil { if err != nil {
zap.L().Error("get_whois_credits", zap.L().Error("write_whois_subdomain",
zap.String("message", "failed to get whois credits"), zap.String("message", "failed to write whois subdomain to file"),
zap.Error(err), zap.Error(err),
) )
fmt.Printf("Error getting WHOIS credits: %v\n", err) fmt.Printf("Error writing WHOIS subdomain to file: %v\n", err)
return
} }
fmt.Printf("\nWHOIS Credits Remaining: %d\n", credits.WhoisCredits)
var (
headers = []string{"Domain", "First Seen", "Last Seen"}
rows [][]string
)
// Create the rows
for _, r := range subdomains {
rows = append(rows, []string{r.Domain, time.Unix(r.FirstSeen, 0).String(), time.Unix(r.LastSeen, 0).String()})
}
// Store the subdomains
pretty.Table(headers, rows)
} else {
fmt.Println("[!] No subdomains found")
zap.L().Info("write_whois_subdomain",
zap.String("message", "no whois subdomain records to write to file"),
zap.String("domain", whoisDomain),
)
}
}
}
return return
} }
if whoisIPAddress != "" { if whoisIPAddress != "" {
fmt.Println("[*] Performing reverse IP lookup...") fmt.Println("[*] Performing reverse IP lookup...")
// IP lookup // IP lookup
result, err := whois.WhoisIP(whoisIPAddress, key) result, err := w.WhoisIP(whoisIPAddress)
if err != nil { if err != nil {
if debugGlobal {
debug.PrintInfo("failed to perform ip lookup")
debug.PrintError(err)
}
zap.L().Error("whois_ip", zap.L().Error("whois_ip",
zap.String("message", "failed to perform ip lookup"), zap.String("message", "failed to perform ip lookup"),
zap.Error(err), zap.Error(err),
@@ -165,28 +319,61 @@ var (
fmt.Printf("Error performing IP lookup: %v\n", err) fmt.Printf("Error performing IP lookup: %v\n", err)
return return
} }
fmt.Println("IP Lookup Result:")
fmt.Println(string(result))
// Get credits // Get credits
credits, err := whois.GetWHOISCredits(key) if whoisShowCredits {
if err != nil { checkBalance(w)
zap.L().Error("get_whois_credits", }
zap.String("message", "failed to get whois credits"),
zap.Error(err), if len(result) == 0 {
fmt.Println("[!] No results found")
zap.L().Info("whois_ip",
zap.String("message", "no results found"),
zap.String("ip", whoisIPAddress),
) )
fmt.Printf("Error getting WHOIS credits: %v\n", err)
return return
} }
fmt.Printf("\nWHOIS Credits Remaining: %d\n", credits.WhoisCredits)
// Write the results to file
fmt.Printf("[*] Writing IP lookup results to file: %s%s\n", whoisOutputFile, fType.Extension())
err = export.WriteIPLookupToFile(result, whoisOutputFile, fType)
if err != nil {
if debugGlobal {
debug.PrintInfo("failed to write ip lookup to file")
debug.PrintError(err)
}
zap.L().Error("write_ip_lookup",
zap.String("message", "failed to write ip lookup to file"),
zap.Error(err),
)
fmt.Printf("Error writing IP lookup to file: %v\n", err)
}
// Pretty Print the JSON
var (
headers = []string{"Name", "Search Term", "First Seen", "Last Visit", "Type"}
rows [][]string
)
fmt.Println("IP Lookup Result:")
for _, r := range result {
rows = append(rows, []string{r.Name, r.SearchTerm, time.Unix(r.FirstSeen, 0).String(), time.Unix(r.LastVisit, 0).String(), r.Type})
}
pretty.Table(headers, rows)
return return
} }
if whoisMXAddress != "" { if whoisMXAddress != "" {
fmt.Println("[*] Performing reverse MX lookup...") fmt.Println("[*] Performing reverse MX lookup...")
// MX lookup // MX lookup
result, err := whois.WhoisMX(whoisMXAddress, key) result, err := w.WhoisMX(whoisMXAddress)
if err != nil { if err != nil {
if debugGlobal {
debug.PrintInfo("failed to perform mx lookup")
debug.PrintError(err)
}
zap.L().Error("whois_mx", zap.L().Error("whois_mx",
zap.String("message", "failed to perform mx lookup"), zap.String("message", "failed to perform mx lookup"),
zap.Error(err), zap.Error(err),
@@ -195,29 +382,60 @@ var (
return return
} }
// todo unmarshal mx lookup
fmt.Println("MX Lookup Result:")
fmt.Println(result)
// Get credits // Get credits
credits, err := whois.GetWHOISCredits(key) if whoisShowCredits {
if err != nil { checkBalance(w)
zap.L().Error("get_whois_credits", }
zap.String("message", "failed to get whois credits"),
zap.Error(err), if len(result) == 0 {
fmt.Println("[!] No results found")
zap.L().Info("whois_mx",
zap.String("message", "no results found"),
zap.String("mx", whoisMXAddress),
) )
fmt.Printf("Error getting WHOIS credits: %v\n", err)
return return
} }
fmt.Printf("\nWHOIS Credits Remaining: %d\n", credits.WhoisCredits)
// Write the results to file
fmt.Printf("[*] Writing MX lookup results to file: %s%s\n", whoisOutputFile, fType.Extension())
err = export.WriteIPLookupToFile(result, whoisOutputFile, fType)
if err != nil {
if debugGlobal {
debug.PrintInfo("failed to write mx lookup to file")
debug.PrintError(err)
}
zap.L().Error("write_mx_lookup",
zap.String("message", "failed to write mx lookup to file"),
zap.Error(err),
)
fmt.Printf("Error writing MX lookup to file: %v\n", err)
}
// Pretty Print the JSON
var (
headers = []string{"Name", "Search Term", "First Seen", "Last Visit", "Type"}
rows [][]string
)
fmt.Println("MX Lookup Result:")
for _, r := range result {
rows = append(rows, []string{r.Name, r.SearchTerm, time.Unix(r.FirstSeen, 0).String(), time.Unix(r.LastVisit, 0).String(), r.Type})
}
pretty.Table(headers, rows)
return return
} }
if whoisNSAddress != "" { if whoisNSAddress != "" {
fmt.Println("[*] Performing reverse NS lookup...") fmt.Println("[*] Performing reverse NS lookup...")
// NS lookup // NS lookup
result, err := whois.WhoisNS(whoisNSAddress, key) result, err := w.WhoisNS(whoisNSAddress)
if err != nil { if err != nil {
if debugGlobal {
debug.PrintInfo("failed to perform ns lookup")
debug.PrintError(err)
}
zap.L().Error("whois_ns", zap.L().Error("whois_ns",
zap.String("message", "failed to perform ns lookup"), zap.String("message", "failed to perform ns lookup"),
zap.Error(err), zap.Error(err),
@@ -225,20 +443,49 @@ var (
fmt.Printf("Error performing NS lookup: %v\n", err) fmt.Printf("Error performing NS lookup: %v\n", err)
return return
} }
fmt.Println("NS Lookup Result:")
fmt.Println(result)
// Get credits // Get credits
credits, err := whois.GetWHOISCredits(key) if whoisShowCredits {
if err != nil { checkBalance(w)
zap.L().Error("get_whois_credits", }
zap.String("message", "failed to get whois credits"),
zap.Error(err), if len(result) == 0 {
fmt.Println("[!] No results found")
zap.L().Info("whois_ns",
zap.String("message", "no results found"),
zap.String("ns", whoisNSAddress),
) )
fmt.Printf("Error getting WHOIS credits: %v\n", err)
return return
} }
fmt.Printf("\nWHOIS Credits Remaining: %d\n", credits.WhoisCredits)
// Write the results to file
fmt.Printf("[*] Writing NS lookup results to file: %s%s\n", whoisOutputFile, fType.Extension())
err = export.WriteIPLookupToFile(result, whoisOutputFile, fType)
if err != nil {
if debugGlobal {
debug.PrintInfo("failed to write ns lookup to file")
debug.PrintError(err)
}
zap.L().Error("write_ns_lookup",
zap.String("message", "failed to write ns lookup to file"),
zap.Error(err),
)
fmt.Printf("Error writing NS lookup to file: %v\n", err)
}
// Pretty Print the JSON
var (
headers = []string{"Name", "Search Term", "First Seen", "Last Visit", "Type"}
rows [][]string
)
fmt.Println("NS Lookup Result:")
for _, r := range result {
rows = append(rows, []string{r.Name, r.SearchTerm, time.Unix(r.FirstSeen, 0).String(), time.Unix(r.LastVisit, 0).String(), r.Type})
}
pretty.Table(headers, rows)
return return
} }
@@ -247,25 +494,54 @@ var (
includeTerms := []string{} includeTerms := []string{}
if whoisInclude != "" { if whoisInclude != "" {
includeTerms = strings.Split(whoisInclude, ",") includeTerms = strings.Split(whoisInclude, ",")
if len(includeTerms) > 4 {
fmt.Println("[!] Error: Maximum of 4 include terms allowed.")
return
}
} }
excludeTerms := []string{} excludeTerms := []string{}
if whoisExclude != "" { if whoisExclude != "" {
excludeTerms = strings.Split(whoisExclude, ",") excludeTerms = strings.Split(whoisExclude, ",")
if len(excludeTerms) > 4 {
fmt.Println("[!] Error: Maximum of 4 exclude terms allowed.")
return
}
} }
if whoisReverseType == "" { if whoisReverseType == "" {
whoisReverseType = "registrant" if debugGlobal {
debug.PrintInfo("reverse type not specified, using default")
}
whoisReverseType = "current"
} else {
toLower := strings.ToLower(whoisReverseType)
if toLower != "current" && toLower != "historic" {
fmt.Println("[!] Error: Invalid reverse type. Must be 'current' or 'historic'.")
return
}
} }
fmt.Println("[*] Performing reverse WHOIS lookup...") fmt.Println("[*] Performing reverse WHOIS lookup...")
result, err := whois.ReverseWHOIS(includeTerms, excludeTerms, whoisReverseType, key) result, err := w.ReverseWHOIS(includeTerms, excludeTerms, whoisReverseType)
if err != nil { if err != nil {
if debugGlobal {
debug.PrintInfo("failed to perform reverse whois")
debug.PrintError(err)
}
zap.L().Error("reverse_whois",
zap.String("message", "failed to perform reverse whois"),
zap.Error(err),
)
fmt.Printf("Error performing reverse WHOIS: %v\n", err) fmt.Printf("Error performing reverse WHOIS: %v\n", err)
return return
} }
fmt.Println("Reverse WHOIS Result:") fmt.Println("Reverse WHOIS Result:")
fmt.Println(result) fmt.Println(result)
if whoisShowCredits {
checkBalance(w)
}
return return
} }
@@ -275,20 +551,22 @@ var (
} }
) )
func init() { func checkBalance(w *whois.DehashedWhoIs) {
// Add whois command to root command balance, err := w.Balance()
rootCmd.AddCommand(whoisCmd) if err != nil {
if debugGlobal {
// Add flags specific to whois command debug.PrintInfo("failed to get whois balance")
whoisCmd.Flags().StringVarP(&whoisDomain, "domain", "d", "", "Domain for WHOIS lookup, history search, and subdomain scan") debug.PrintError(err)
whoisCmd.Flags().StringVarP(&whoisIPAddress, "ip", "i", "", "IP address for reverse IP lookup") }
whoisCmd.Flags().StringVarP(&whoisMXAddress, "mx", "m", "", "MX address for reverse MX lookup") zap.L().Error("get_whois_credits",
whoisCmd.Flags().StringVarP(&whoisNSAddress, "ns", "n", "", "NS address for reverse NS lookup") zap.String("message", "failed to get whois balance"),
whoisCmd.Flags().StringVarP(&whoisInclude, "include", "I", "", "Terms to include in reverse WHOIS search (comma-separated)") zap.Error(err),
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)") fmt.Printf("Error getting WHOIS balance: %v\n", err)
whoisCmd.Flags().StringVarP(&whoisOutputFormat, "format", "f", "text", "Output format (text, json)") }
whoisCmd.Flags().BoolVarP(&whoisShowCredits, "credits", "c", false, "Show remaining WHOIS credits") fmt.Println("WHOIS Credits: ", balance)
whoisCmd.Flags().BoolVarP(&whoisHistory, "history", "H", false, "Perform WHOIS history search [25 Credits]") if balance == 0 {
whoisCmd.Flags().BoolVarP(&whoisSubdomainScan, "subdomains", "s", false, "Perform WHOIS subdomain scan") fmt.Println("[!] No WHOIS credits remaining.")
os.Exit(0)
}
} }
+21
View File
@@ -0,0 +1,21 @@
package debug
import (
"fmt"
"github.com/fatih/color"
)
func PrintError(err error) {
errLine := fmt.Sprintf("[DEBUG-ERROR] %s", err)
fmt.Println(color.RedString(errLine))
}
func PrintJson(json string) {
jsonLine := fmt.Sprintf("[DEBUG-JSON] %s", json)
fmt.Print(color.GreenString(jsonLine))
}
func PrintInfo(info string) {
infoLine := fmt.Sprintf("[DEBUG-INFO] %s", info)
fmt.Println(color.BlueString(infoLine))
}
@@ -1,8 +1,9 @@
package query package dehashed
import ( import (
"bytes" "bytes"
"crypto/sha256" "crypto/sha256"
"dehasher/internal/debug"
"dehasher/internal/sqlite" "dehasher/internal/sqlite"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
@@ -38,6 +39,7 @@ func (dp DehashedParameter) GetArgumentString(arg string) string {
type DehashedSearchRequest struct { type DehashedSearchRequest struct {
ForcePlaintext bool `json:"-"` ForcePlaintext bool `json:"-"`
Debug bool `json:"-"`
Page int `json:"page"` Page int `json:"page"`
Query string `json:"query"` Query string `json:"query"`
Size int `json:"size"` Size int `json:"size"`
@@ -46,42 +48,60 @@ type DehashedSearchRequest struct {
DeDupe bool `json:"de_dupe"` DeDupe bool `json:"de_dupe"`
} }
func NewDehashedSearchRequest(page, size int, wildcard, regex, forcePlaintext bool) *DehashedSearchRequest { func NewDehashedSearchRequest(page, size int, wildcard, regex, forcePlaintext, debug bool) *DehashedSearchRequest {
return &DehashedSearchRequest{Page: page, Query: "", Size: size, Wildcard: wildcard, Regex: regex, DeDupe: true, ForcePlaintext: forcePlaintext} return &DehashedSearchRequest{Page: page, Query: "", Size: size, Wildcard: wildcard, Regex: regex, DeDupe: true, ForcePlaintext: forcePlaintext, Debug: debug}
} }
func (dsr *DehashedSearchRequest) buildQuery(query string, param DehashedParameter) { func (dsr *DehashedSearchRequest) buildQuery(query string) {
if dsr.Debug {
debug.PrintInfo(fmt.Sprintf("building query: %s", query))
}
// Ensure query is properly formatted
query = strings.TrimSpace(query)
// For regex queries, we need to ensure the regex pattern is properly escaped
// and not enquoted, as that would break the regex pattern
if dsr.Regex && !strings.HasPrefix(query, "\"") && !strings.HasSuffix(query, "\"") {
// Don't add extra quotes for regex patterns
} else if strings.Contains(query, " ") && !strings.HasPrefix(query, "\"") {
query = fmt.Sprintf("\"%s\"", query)
}
if len(dsr.Query) > 0 { if len(dsr.Query) > 0 {
dsr.Query = fmt.Sprintf("%s&%s", strings.TrimSpace(dsr.Query), strings.TrimSpace(query)) dsr.Query = fmt.Sprintf("%s&%s", strings.TrimSpace(dsr.Query), query)
} else { } else {
dsr.Query = query dsr.Query = query
} }
if dsr.Debug {
debug.PrintInfo(fmt.Sprintf("query built: %s", dsr.Query))
}
} }
func (dsr *DehashedSearchRequest) AddUsernameQuery(query string) { func (dsr *DehashedSearchRequest) AddUsernameQuery(query string) {
query = enquoteSpaced(query) query = enquoteSpaced(query)
dsr.buildQuery(Username.GetArgumentString(query), Username) dsr.buildQuery(Username.GetArgumentString(query))
} }
func (dsr *DehashedSearchRequest) AddEmailQuery(query string) { func (dsr *DehashedSearchRequest) AddEmailQuery(query string) {
query = enquoteSpaced(query) query = enquoteSpaced(query)
dsr.buildQuery(Email.GetArgumentString(query), Email) dsr.buildQuery(Email.GetArgumentString(query))
} }
func (dsr *DehashedSearchRequest) AddIpAddressQuery(query string) { func (dsr *DehashedSearchRequest) AddIpAddressQuery(query string) {
query = enquoteSpaced(query) query = enquoteSpaced(query)
dsr.buildQuery(IpAddress.GetArgumentString(query), IpAddress) dsr.buildQuery(IpAddress.GetArgumentString(query))
} }
func (dsr *DehashedSearchRequest) AddDomainQuery(query string) { func (dsr *DehashedSearchRequest) AddDomainQuery(query string) {
query = enquoteSpaced(query) query = enquoteSpaced(query)
dsr.buildQuery(Domain.GetArgumentString(query), Domain) dsr.buildQuery(Domain.GetArgumentString(query))
} }
func (dsr *DehashedSearchRequest) AddPasswordQuery(query string) { func (dsr *DehashedSearchRequest) AddPasswordQuery(query string) {
if dsr.ForcePlaintext { if dsr.ForcePlaintext {
query = enquoteSpaced(query) query = enquoteSpaced(query)
dsr.buildQuery(Password.GetArgumentString(query), Password) dsr.buildQuery(Password.GetArgumentString(query))
return return
} }
hash := sha256.Sum256([]byte(query)) hash := sha256.Sum256([]byte(query))
@@ -91,89 +111,126 @@ func (dsr *DehashedSearchRequest) AddPasswordQuery(query string) {
func (dsr *DehashedSearchRequest) AddVinQuery(query string) { func (dsr *DehashedSearchRequest) AddVinQuery(query string) {
query = enquoteSpaced(query) query = enquoteSpaced(query)
dsr.buildQuery(Vin.GetArgumentString(query), Vin) dsr.buildQuery(Vin.GetArgumentString(query))
} }
func (dsr *DehashedSearchRequest) AddLicensePlateQuery(query string) { func (dsr *DehashedSearchRequest) AddLicensePlateQuery(query string) {
query = enquoteSpaced(query) query = enquoteSpaced(query)
dsr.buildQuery(LicensePlate.GetArgumentString(query), LicensePlate) dsr.buildQuery(LicensePlate.GetArgumentString(query))
} }
func (dsr *DehashedSearchRequest) AddAddressQuery(query string) { func (dsr *DehashedSearchRequest) AddAddressQuery(query string) {
query = enquoteSpaced(query) query = enquoteSpaced(query)
dsr.buildQuery(Address.GetArgumentString(query), Address) dsr.buildQuery(Address.GetArgumentString(query))
} }
func (dsr *DehashedSearchRequest) AddPhoneQuery(query string) { func (dsr *DehashedSearchRequest) AddPhoneQuery(query string) {
query = enquoteSpaced(query) query = enquoteSpaced(query)
dsr.buildQuery(Phone.GetArgumentString(query), Phone) dsr.buildQuery(Phone.GetArgumentString(query))
} }
func (dsr *DehashedSearchRequest) AddSocialQuery(query string) { func (dsr *DehashedSearchRequest) AddSocialQuery(query string) {
query = enquoteSpaced(query) query = enquoteSpaced(query)
dsr.buildQuery(Social.GetArgumentString(query), Social) dsr.buildQuery(Social.GetArgumentString(query))
} }
func (dsr *DehashedSearchRequest) AddCryptoAddressQuery(query string) { func (dsr *DehashedSearchRequest) AddCryptoAddressQuery(query string) {
query = enquoteSpaced(query) query = enquoteSpaced(query)
dsr.buildQuery(CryptoAddress.GetArgumentString(query), CryptoAddress) dsr.buildQuery(CryptoAddress.GetArgumentString(query))
} }
func (dsr *DehashedSearchRequest) AddHashedPasswordQuery(query string) { func (dsr *DehashedSearchRequest) AddHashedPasswordQuery(query string) {
query = strings.TrimSpace(query) query = strings.TrimSpace(query)
dsr.buildQuery(HashedPassword.GetArgumentString(query), HashedPassword) dsr.buildQuery(HashedPassword.GetArgumentString(query))
} }
func (dsr *DehashedSearchRequest) AddNameQuery(query string) { func (dsr *DehashedSearchRequest) AddNameQuery(query string) {
query = enquoteSpaced(query) query = enquoteSpaced(query)
dsr.buildQuery(Name.GetArgumentString(query), Name) dsr.buildQuery(Name.GetArgumentString(query))
} }
type DehashedClientV2 struct { type DehashedClientV2 struct {
apiKey string apiKey string
results []sqlite.Result results []sqlite.Result
debug bool
} }
func NewDehashedClientV2(apiKey string) *DehashedClientV2 { func NewDehashedClientV2(apiKey string, debug bool) *DehashedClientV2 {
return &DehashedClientV2{apiKey: apiKey} return &DehashedClientV2{apiKey: apiKey, debug: debug}
} }
func (dcv2 *DehashedClientV2) Search(searchRequest DehashedSearchRequest) (int, error) { func (dcv2 *DehashedClientV2) Search(searchRequest DehashedSearchRequest) (int, int, error) {
if dcv2.debug {
debug.PrintInfo("preparing search request")
zap.L().Info("v2_search_debug",
zap.String("message", "preparing search request"),
)
}
reqBody, _ := json.Marshal(searchRequest) reqBody, _ := json.Marshal(searchRequest)
if dcv2.debug {
j := string(reqBody)
jReq := fmt.Sprintf("Request Body: %s\n", j)
debug.PrintJson(jReq)
zap.L().Info("v2_search_debug",
zap.String("message", jReq),
zap.String("body", j),
)
}
req, err := http.NewRequest("POST", "https://api.dehashed.com/v2/search", bytes.NewReader(reqBody)) req, err := http.NewRequest("POST", "https://api.dehashed.com/v2/search", bytes.NewReader(reqBody))
if err != nil { if err != nil {
return -1, err return -1, -1, err
} }
if dcv2.debug {
debug.PrintInfo("setting headers")
zap.L().Info("v2_search_debug",
zap.String("message", "setting headers"),
)
}
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
req.Header.Set("Dehashed-Api-Key", dcv2.apiKey) req.Header.Set("Dehashed-Api-Key", dcv2.apiKey)
if dcv2.debug {
headers := req.Header.Clone()
h := fmt.Sprintf("Headers: %v\n", headers)
debug.PrintJson(h)
zap.L().Info("v2_search_debug",
zap.String("message", h),
zap.String("headers", fmt.Sprintf("%v", headers)),
)
debug.PrintInfo("performing request")
zap.L().Info("v2_search_debug",
zap.String("message", "performing request"),
)
}
res, err := http.DefaultClient.Do(req) res, err := http.DefaultClient.Do(req)
if res != nil { if res != nil {
defer res.Body.Close() defer res.Body.Close()
} }
if err != nil { if err != nil {
if dcv2.debug {
debug.PrintInfo("failed to perform request")
debug.PrintError(err)
}
zap.L().Error("v2_search", zap.L().Error("v2_search",
zap.String("message", "failed to perform request"), zap.String("message", "failed to perform request"),
zap.Error(err), zap.Error(err),
) )
return -1, err return -1, -1, err
} }
if res == nil { if res == nil {
if dcv2.debug {
debug.PrintInfo("response was nil")
}
zap.L().Error("v2_search", zap.L().Error("v2_search",
zap.String("message", "response was nil"), zap.String("message", "response was nil"),
) )
return -1, errors.New("response was nil") return -1, -1, errors.New("response was nil")
}
// Check for HTTP status code errors
if res.StatusCode != 200 {
dhErr := GetDehashedError(res.StatusCode)
fmt.Printf("[%d] API Error message: %s\n", res.StatusCode, dhErr.Error())
zap.L().Error("v2_search",
zap.String("message", "received error status code"),
zap.Int("status_code", res.StatusCode),
zap.String("error", dhErr.Error()),
)
return -1, &dhErr
} }
b, err := io.ReadAll(res.Body) b, err := io.ReadAll(res.Body)
@@ -182,21 +239,50 @@ func (dcv2 *DehashedClientV2) Search(searchRequest DehashedSearchRequest) (int,
zap.String("message", "failed to read response body"), zap.String("message", "failed to read response body"),
zap.Error(err), zap.Error(err),
) )
return -1, err return -1, -1, err
}
// Check for HTTP status code errors
if res.StatusCode != 200 {
if dcv2.debug {
debug.PrintInfo("received error status code")
debug.PrintJson(fmt.Sprintf("Status Code: %d\n", res.StatusCode))
debug.PrintJson(fmt.Sprintf("Body: %s\n", string(b[:])))
}
dhErr := GetDehashedError(res.StatusCode)
zap.L().Error("v2_search",
zap.String("message", "received error status code"),
zap.Int("status_code", res.StatusCode),
zap.String("error", dhErr.Error()),
zap.String("body_error", string(b)),
)
return -1, -1, &dhErr
} }
var responseResults sqlite.DehashedResponse var responseResults sqlite.DehashedResponse
err = json.Unmarshal(b, &responseResults) err = json.Unmarshal(b, &responseResults)
if err != nil { if err != nil {
if dcv2.debug {
debug.PrintInfo("failed to unmarshal response body")
debug.PrintError(err)
}
zap.L().Error("v2_search", zap.L().Error("v2_search",
zap.String("message", "failed to unmarshal response body"), zap.String("message", "failed to unmarshal response body"),
zap.Error(err), zap.Error(err),
) )
return -1, err return -1, -1, err
}
if dcv2.debug {
debug.PrintInfo("appending results")
debug.PrintJson(fmt.Sprintf("Total Results: %d\n", responseResults.TotalResults))
debug.PrintJson(fmt.Sprintf("Balance: %d\n", responseResults.Balance))
debug.PrintJson(fmt.Sprintf("Entries: %d\n", len(responseResults.Entries)))
} }
dcv2.results = append(dcv2.results, responseResults.Entries...) dcv2.results = append(dcv2.results, responseResults.Entries...)
return responseResults.TotalResults, nil return responseResults.TotalResults, responseResults.Balance, nil
} }
func (dcv2 *DehashedClientV2) GetResults() sqlite.DehashedResults { func (dcv2 *DehashedClientV2) GetResults() sqlite.DehashedResults {
@@ -1,6 +1,7 @@
package query package dehashed
import ( import (
"dehasher/internal/debug"
"dehasher/internal/export" "dehasher/internal/export"
"dehasher/internal/sqlite" "dehasher/internal/sqlite"
"encoding/json" "encoding/json"
@@ -13,6 +14,8 @@ import (
type Dehasher struct { type Dehasher struct {
options sqlite.QueryOptions options sqlite.QueryOptions
nextPage int nextPage int
debug bool
balance int
request *DehashedSearchRequest request *DehashedSearchRequest
client *DehashedClientV2 client *DehashedClientV2
} }
@@ -22,19 +25,24 @@ func NewDehasher(options *sqlite.QueryOptions) *Dehasher {
dh := &Dehasher{ dh := &Dehasher{
options: *options, options: *options,
nextPage: options.StartingPage + 1, nextPage: options.StartingPage + 1,
debug: options.Debug,
balance: 0,
} }
dh.setQueries() dh.setQueries()
dh.request = NewDehashedSearchRequest(dh.options.StartingPage, dh.options.MaxRecords, dh.options.WildcardMatch, dh.options.RegexMatch, false) dh.request = NewDehashedSearchRequest(dh.options.StartingPage, dh.options.MaxRecords, dh.options.WildcardMatch, dh.options.RegexMatch, false, options.Debug)
dh.buildRequest() dh.buildRequest()
return dh return dh
} }
// SetClientCredentials sets the client credentials for the dehasher // SetClientCredentials sets the client credentials for the dehasher
func (dh *Dehasher) SetClientCredentials(key string) { func (dh *Dehasher) SetClientCredentials(key string) {
dh.client = NewDehashedClientV2(key) dh.client = NewDehashedClientV2(key, dh.debug)
} }
func (dh *Dehasher) getNextPage() int { func (dh *Dehasher) getNextPage() int {
if dh.debug {
debug.PrintInfo(fmt.Sprintf("getting next page: %d", dh.nextPage))
}
nextPage := dh.nextPage nextPage := dh.nextPage
dh.nextPage += 1 dh.nextPage += 1
return nextPage return nextPage
@@ -44,6 +52,10 @@ func (dh *Dehasher) getNextPage() int {
func (dh *Dehasher) setQueries() { func (dh *Dehasher) setQueries() {
var numQueries int var numQueries int
if dh.debug {
debug.PrintInfo("setting queries")
}
switch { switch {
case dh.options.MaxRequests == 0: case dh.options.MaxRequests == 0:
zap.L().Error("max requests cannot be zero") zap.L().Error("max requests cannot be zero")
@@ -80,39 +92,69 @@ func (dh *Dehasher) setQueries() {
} }
dh.options.MaxRequests = numQueries dh.options.MaxRequests = numQueries
if dh.debug {
debug.PrintInfo(fmt.Sprintf("setting max requests: %d", numQueries))
debug.PrintInfo(fmt.Sprintf("setting max records: %d", dh.options.MaxRecords))
}
fmt.Printf("Making %d Requests for %d Records (%d Total)\n", dh.options.MaxRequests, dh.options.MaxRecords, dh.options.MaxRequests*dh.options.MaxRecords) fmt.Printf("Making %d Requests for %d Records (%d Total)\n", dh.options.MaxRequests, dh.options.MaxRecords, dh.options.MaxRequests*dh.options.MaxRecords)
} }
// Start starts the querying process // Start starts the querying process
func (dh *Dehasher) Start() { func (dh *Dehasher) Start() {
fmt.Println("[*] Querying Dehashed API...") fmt.Printf("[*] Querying Dehashed API...\n")
for i := 0; i < dh.options.MaxRequests; i++ { for i := 0; i < dh.options.MaxRequests; i++ {
fmt.Printf("\n\t[*] Performing Request...") fmt.Printf(" [*] Performing Request...\n")
count, err := dh.client.Search(*dh.request) count, balance, err := dh.client.Search(*dh.request)
if err != nil { if err != nil {
if dh.debug {
debug.PrintInfo("error performing request")
debug.PrintError(err)
}
// Check if it's a DehashError // Check if it's a DehashError
if dhErr, ok := err.(*DehashError); ok { if dhErr, ok := err.(*DehashError); ok {
fmt.Printf("\n\t[!] Dehashed API Error: %s (Code: %d)", dhErr.Message, dhErr.Code) fmt.Printf(" [!] Dehashed API Error: %s (Code: %d)\n", dhErr.Message, dhErr.Code)
zap.L().Error("dehashed_api_error", zap.L().Error("dehashed_api_error",
zap.String("message", dhErr.Message), zap.String("message", dhErr.Message),
zap.Int("code", dhErr.Code), zap.Int("code", dhErr.Code),
) )
} else { } else {
fmt.Printf("\n\t[!] Error performing request: %v", err) fmt.Printf(" [!] Error performing request: %v\n", err)
zap.L().Error("request_error", zap.L().Error("request_error",
zap.String("message", "failed to perform request"), zap.String("message", "failed to perform request"),
zap.Error(err), zap.Error(err),
) )
} }
if len(dh.client.results) > 0 {
fmt.Printf(" [!] Partial results retrieved. Storing Results...\n")
err := sqlite.StoreResults(dh.client.GetResults())
if err != nil {
zap.L().Error("store_results",
zap.String("message", "failed to store results"),
zap.Error(err),
)
fmt.Printf(" [!] Error storing results: %v\n", err)
}
}
dh.parseResults()
os.Exit(-1) os.Exit(-1)
} }
dh.balance = balance
if count < dh.options.MaxRecords { if count < dh.options.MaxRecords {
fmt.Printf("\n\t\t[+] Retrieved %d Records", count) fmt.Printf(" [+] Retrieved %d records\n", count)
fmt.Printf("\n[-] Not Enough Entries, ending queries") fmt.Printf(" [-] Not enough entries, ending queries\n")
break break
} else { } else {
fmt.Printf("\n\t\t[+] Retrieved %d Records", dh.options.MaxRecords) fmt.Printf(" [+] Retrieved %d records\n", dh.options.MaxRecords)
}
if dh.options.PrintBalance {
fmt.Printf(" [*] Balance: %d\n", balance)
} }
dh.request.Page = dh.getNextPage() dh.request.Page = dh.getNextPage()
@@ -171,7 +213,7 @@ func (dh *Dehasher) parseResults() {
zap.L().Info("extracting_credentials") zap.L().Info("extracting_credentials")
results := dh.client.GetResults() results := dh.client.GetResults()
creds := results.ExtractCredentials() creds := results.ExtractCredentials()
fmt.Printf("\n\t[*] Discovered %d Credentials", len(creds)) fmt.Printf("\n\t[+] Discovered %d Credentials", len(creds))
err := sqlite.StoreCreds(creds) err := sqlite.StoreCreds(creds)
if err != nil { if err != nil {
zap.L().Error("store_creds", zap.L().Error("store_creds",
@@ -1,4 +1,4 @@
package query package dehashed
type DehashError struct { type DehashError struct {
Message string Message string
+266
View File
@@ -0,0 +1,266 @@
package easyTime
import (
"dehasher/internal/debug"
"fmt"
"go.uber.org/zap"
"os"
"strconv"
"strings"
"time"
)
type TimeChunk struct {
StartTime time.Time
EndTime time.Time
set bool
}
func (tc *TimeChunk) isValid() bool {
if !tc.StartTime.IsZero() && !tc.EndTime.IsZero() && tc.StartTime.Before(tc.EndTime) {
tc.set = true
return true
}
tc.set = false
return false
}
func (tc *TimeChunk) IsSet() bool {
return tc.set
}
func NewTimeChunk(start, end string, debugOn bool) TimeChunk {
if debugOn {
debug.PrintInfo("parsing time chunk")
debug.PrintInfo(fmt.Sprintf("Start: %s, End: %s", start, end))
zap.L().Info("parsing time chunk",
zap.String("start", start),
zap.String("end", end),
)
}
if end == "" {
if debugOn {
debug.PrintInfo("no end time provided, using now")
}
end = "now"
}
tc := TimeChunk{
StartTime: parseUserTime(start),
EndTime: parseUserTime(end),
}
if debugOn {
debug.PrintInfo("checking if time chunk is valid")
debug.PrintInfo(fmt.Sprintf("Start: %s, End: %s", tc.StartTime, tc.EndTime))
}
if !tc.isValid() {
fmt.Println("[!] Invalid time chunk")
zap.L().Fatal("invalid_time_chunk",
zap.String("message", "invalid time chunk"),
)
os.Exit(1)
}
return tc
}
func parseUserTime(args string) time.Time {
args = strings.TrimSpace(args)
if strings.EqualFold(args, "now") {
return time.Now()
}
// Check if time contains a space, if so, it's in 'last 24 hours' format
if strings.Contains(args, " ") && !containsMonth(strings.Split(args, " ")) {
splitArgs := strings.Split(args, " ")
if len(splitArgs) == 0 {
fmt.Println("[!] No time provided")
zap.L().Fatal("no_time_provided",
zap.String("message", "no time provided"),
)
os.Exit(1)
} else if len(splitArgs) < 3 {
fmt.Println("[!] Invalid time format")
zap.L().Fatal("invalid_time_format",
zap.String("message", "invalid time format"),
)
os.Exit(1)
}
// Handle 'last 24 hours' format
var (
tense string
amount int
duration time.Duration
)
for _, arg := range splitArgs {
if isPasteTense(arg) {
tense = arg
} else if isNumber(arg) {
amount, _ = strconv.Atoi(arg)
} else if isDuration(arg) {
duration = getDuration(arg)
}
}
if tense == "" {
fmt.Println("[!] Invalid time format: tense not found")
zap.L().Fatal("invalid_time_format",
zap.String("message", "invalid time format"),
)
os.Exit(1)
} else if amount == 0 {
fmt.Println("[!] Invalid time format: amount not found")
zap.L().Fatal("invalid_time_format",
zap.String("message", "invalid time format"),
)
os.Exit(1)
} else if duration == 0 {
fmt.Println("[!] Invalid time format: duration not found")
zap.L().Fatal("invalid_time_format",
zap.String("message", "invalid time format"),
)
os.Exit(1)
}
// Return the appropriate time
if tense == "last" {
return time.Now().Add(-time.Duration(amount) * duration)
} else if tense == "ago" {
return time.Now().Add(-time.Duration(amount) * duration)
}
}
// Handle possible formats 'May 01, 2025', '05-01-2025', '05/01/2025', '05/01/25', '05-01-25'
var (
t time.Time
err error
found bool
)
possible := []string{"01-02-2006", "01/02/2006", "01/02/06", "01-02-06", "Jan 02, 2006", "Jan 2, 2006"}
for _, format := range possible {
t, err = time.Parse(format, args)
if err == nil {
found = true
break
}
}
if !found {
fmt.Println("[!] Invalid time format")
zap.L().Fatal("invalid_time_format",
zap.String("message", "invalid time format"),
)
os.Exit(1)
}
// Convert UTC time to local time
local, err := time.LoadLocation("Local")
if err != nil {
fmt.Println("[!] Error loading local timezone")
zap.L().Error("load_timezone",
zap.String("message", "failed to load local timezone"),
zap.Error(err),
)
return t
}
// Convert the parsed time to local time
return time.Date(
t.Year(),
t.Month(),
t.Day(),
t.Hour(),
t.Minute(),
t.Second(),
t.Nanosecond(),
local,
)
}
func isPasteTense(value string) bool {
for _, v := range []string{"last", "ago"} {
if strings.EqualFold(value, v) {
return true
}
}
return false
}
func isDuration(value string) bool {
for _, v := range []string{"hour", "hours", "minute", "minutes", "second", "seconds", "day", "days", "week", "weeks", "month", "months", "year", "years"} {
if strings.EqualFold(value, v) {
return true
}
}
return false
}
func isNumber(value string) bool {
_, err := strconv.Atoi(value)
return err == nil
}
func getDuration(timeBlock string) time.Duration {
timeBlock = strings.TrimSpace(strings.ToLower(timeBlock))
switch timeBlock {
case "hour":
return time.Hour
case "hours":
return time.Hour
case "minute":
return time.Minute
case "minutes":
return time.Minute
case "second":
return time.Second
case "seconds":
return time.Second
case "day":
return 24 * time.Hour
case "days":
return 24 * time.Hour
case "week":
return 7 * 24 * time.Hour
case "weeks":
return 7 * 24 * time.Hour
case "month":
return 30 * 24 * time.Hour
case "months":
return 30 * 24 * time.Hour
case "year":
return 365 * 24 * time.Hour
case "years":
return 365 * 24 * time.Hour
default:
fmt.Printf("[!] Unknown duration: %s", timeBlock)
zap.L().Fatal("unknown_duration",
zap.String("message", "unknown duration"),
zap.String("duration", timeBlock),
)
os.Exit(1)
}
return 0
}
func containsMonth(arr []string) bool {
for _, v := range arr {
if isMonth(v) {
return true
}
}
return false
}
func isMonth(value string) bool {
for _, v := range []string{"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"} {
if strings.EqualFold(value, v) {
return true
}
}
return false
}
+119
View File
@@ -11,6 +11,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"strings" "strings"
"time"
) )
func WriteCredsToFile(creds []sqlite.Creds, outputFile string, fileType files.FileType) error { func WriteCredsToFile(creds []sqlite.Creds, outputFile string, fileType files.FileType) error {
@@ -131,3 +132,121 @@ func WriteQueryResultsToFile(results []map[string]interface{}, outputFile string
filePath := fmt.Sprintf("%s.%s", outputFile, fileType.String()) filePath := fmt.Sprintf("%s.%s", outputFile, fileType.String())
return os.WriteFile(filePath, data, 0644) return os.WriteFile(filePath, data, 0644)
} }
func WriteWhoIsHistoryToFile(results []sqlite.HistoryRecord, outputFile string, fileType files.FileType) error {
var data []byte
var err error
switch fileType {
case files.JSON:
data, err = json.MarshalIndent(results, "", " ")
case files.XML:
data, err = xml.MarshalIndent(results, "", " ")
case files.YAML:
data, err = yaml.Marshal(results)
case files.TEXT:
var outStrings []string
for _, r := range results {
outStrings = append(outStrings, r.String()+"\n\n")
}
data = []byte(strings.Join(outStrings, ""))
default:
return errors.New("unsupported file type")
}
if err != nil {
return err
}
filePath := fmt.Sprintf("%s.%s", outputFile, fileType.String())
return os.WriteFile(filePath, data, 0644)
}
func WriteWhoIsRecordToFile(record sqlite.WhoisRecord, outputFile string, fileType files.FileType) error {
var data []byte
var err error
switch fileType {
case files.JSON:
data, err = json.MarshalIndent(record, "", " ")
case files.XML:
data, err = xml.MarshalIndent(record, "", " ")
case files.YAML:
data, err = yaml.Marshal(record)
case files.TEXT:
data = []byte(record.String())
default:
return errors.New("unsupported file type")
}
if err != nil {
return err
}
filePath := fmt.Sprintf("%s.%s", outputFile, fileType.String())
return os.WriteFile(filePath, data, 0644)
}
func WriteSubdomainsToFile(records []sqlite.SubdomainRecord, outputFile string, fileType files.FileType) error {
var data []byte
var err error
switch fileType {
case files.JSON:
data, err = json.MarshalIndent(records, "", " ")
case files.XML:
data, err = xml.MarshalIndent(records, "", " ")
case files.YAML:
data, err = yaml.Marshal(records)
case files.TEXT:
var outStrings []string
for _, r := range records {
out := fmt.Sprintf(
"Domain: %s\nFirst Seen: %s\nLast Seen: %s\n\n",
r.Domain, time.Unix(r.FirstSeen, 0).String(), time.Unix(r.LastSeen, 0).String())
outStrings = append(outStrings, out)
}
data = []byte(strings.Join(outStrings, ""))
default:
return errors.New("unsupported file type")
}
if err != nil {
return err
}
filePath := fmt.Sprintf("%s.%s", outputFile, fileType.String())
return os.WriteFile(filePath, data, 0644)
}
func WriteIPLookupToFile(records []sqlite.LookupResult, outputFile string, fileType files.FileType) error {
var data []byte
var err error
switch fileType {
case files.JSON:
data, err = json.MarshalIndent(records, "", " ")
case files.XML:
data, err = xml.MarshalIndent(records, "", " ")
case files.YAML:
data, err = yaml.Marshal(records)
case files.TEXT:
var outStrings []string
for _, r := range records {
out := fmt.Sprintf(
"Name: %s\nSearch Term: %s\nFirst Seen: %s\nLast Visit: %s\nType: %s\n\n",
r.Name, r.SearchTerm, time.Unix(r.FirstSeen, 0).String(), time.Unix(r.LastVisit, 0).String(), r.Type)
outStrings = append(outStrings, out)
}
data = []byte(strings.Join(outStrings, ""))
default:
return errors.New("unsupported file type")
}
if err != nil {
return err
}
filePath := fmt.Sprintf("%s.%s", outputFile, fileType.String())
return os.WriteFile(filePath, data, 0644)
}
+1
View File
@@ -7,6 +7,7 @@ const (
XML XML
YAML YAML
TEXT TEXT
UNKNOWN
) )
func GetFileType(filetype string) FileType { func GetFileType(filetype string) FileType {
+104
View File
@@ -0,0 +1,104 @@
package pretty
import (
"dehasher/internal/sqlite"
"fmt"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/tree"
)
func WhoIsTree(root string, record sqlite.WhoisRecord) {
enumeratorStyle := lipgloss.NewStyle().Foreground(purple).MarginRight(1)
rootStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("63"))
itemStyle := lipgloss.NewStyle().Foreground(gray)
rootTree := tree.Root(root)
// Child Trees
// Root Audit Tree
auditTree := tree.Root("Audit")
auditTree.Child(fmt.Sprintf("Created Date: %s", record.Audit.CreatedDate))
auditTree.Child(fmt.Sprintf("Updated Date: %s", record.Audit.UpdatedDate))
rootTree.Child(auditTree)
// Root Name Servers Tree
nameServersTree := tree.Root("Name Servers")
nameServersTree.Child("Host Names: " + fmt.Sprintf("%v", record.NameServers.HostNames))
nameServersTree.Child("IPs: " + fmt.Sprintf("%v", record.NameServers.IPs))
nameServersTree.Child("Raw Text: " + record.NameServers.RawText)
// Root Registry Data Tree
registryDataTree := tree.Root("Registry Data")
registryDataTree.Child("Audit: " + fmt.Sprintf("%v", record.RegistryData.Audit))
registryDataTree.Child("Created Date: " + record.RegistryData.CreatedDate)
registryDataTree.Child("Created Date Normalized: " + record.RegistryData.CreatedDateNormalized)
registryDataTree.Child("Domain Name: " + record.RegistryData.DomainName)
registryDataTree.Child("Expires Date: " + record.RegistryData.ExpiresDate)
registryDataTree.Child("Expires Date Normalized: " + record.RegistryData.ExpiresDateNormalized)
registryDataTree.Child("Footer: " + record.RegistryData.Footer)
registryDataTree.Child("Header: " + record.RegistryData.Header)
// Registry Data Name Servers Tree
registryNameServersTree := tree.Root("Name Servers")
registryNameServersTree.Child("Host Names: " + fmt.Sprintf("%v", record.RegistryData.NameServers.HostNames))
registryNameServersTree.Child("IPs: " + fmt.Sprintf("%v", record.RegistryData.NameServers.IPs))
registryNameServersTree.Child("Raw Text: " + record.RegistryData.NameServers.RawText)
registryDataTree.Child(registryNameServersTree)
// Root Registry Data Tree
registryDataTree.Child("Parse Code: " + fmt.Sprintf("%d", record.RegistryData.ParseCode))
registryDataTree.Child("Raw Text: " + record.RegistryData.RawText)
registryDataTree.Child("Registrar IANA ID: " + record.RegistryData.RegistrarIANAID)
registryDataTree.Child("Registrar Name: " + record.RegistryData.RegistrarName)
registryDataTree.Child("Status: " + record.RegistryData.Status)
registryDataTree.Child("Stripped Text: " + record.RegistryData.StrippedText)
registryDataTree.Child("Updated Date: " + record.RegistryData.UpdatedDate)
registryDataTree.Child("Updated Date Normalized: " + record.RegistryData.UpdatedDateNormalized)
registryDataTree.Child("Whois Server: " + record.RegistryData.WhoisServer)
// Root Contract Tree
technicalContactTree := tree.Root("Technical Contact")
technicalContactTree.Child("City: " + record.TechnicalContact.City)
technicalContactTree.Child("Country: " + record.TechnicalContact.Country)
technicalContactTree.Child("Country Code: " + record.TechnicalContact.CountryCode)
technicalContactTree.Child("Name: " + record.TechnicalContact.Name)
technicalContactTree.Child("Organization: " + record.TechnicalContact.Organization)
technicalContactTree.Child("Postal Code: " + record.TechnicalContact.PostalCode)
technicalContactTree.Child("Raw Text: " + record.TechnicalContact.RawText)
technicalContactTree.Child("State: " + record.TechnicalContact.State)
technicalContactTree.Child("Street 1: " + record.TechnicalContact.Street1)
technicalContactTree.Child("Telephone: " + record.TechnicalContact.Telephone)
// Root Tree Children
rootTree.Child("Contact Email: " + record.ContactEmail)
rootTree.Child("Created Date: " + record.CreatedDate)
rootTree.Child("Created Date Normalized: " + record.CreatedDateNormalized)
rootTree.Child("Domain Name: " + record.DomainName)
rootTree.Child("Domain Name Ext: " + record.DomainNameExt)
rootTree.Child("Estimated Domain Age: " + fmt.Sprintf("%d", record.EstimatedDomainAge))
rootTree.Child("Expires Date: " + record.ExpiresDate)
rootTree.Child("Expires Date Normalized: " + record.ExpiresDateNormalized)
rootTree.Child("Footer: " + record.Footer)
rootTree.Child("Header: " + record.Header)
rootTree.Child(nameServersTree)
rootTree.Child("Parse Code: " + fmt.Sprintf("%d", record.ParseCode))
rootTree.Child("Raw Text: " + record.RawText)
rootTree.Child("Registrant: " + fmt.Sprintf("%v", record.Registrant))
rootTree.Child("Registrar IANA ID: " + record.RegistrarIANAID)
rootTree.Child("Registrar Name: " + record.RegistrarName)
rootTree.Child(registryDataTree)
rootTree.Child("Status: " + record.Status)
rootTree.Child("Stripped Text: " + record.StrippedText)
rootTree.Child(technicalContactTree)
rootTree.Child("Updated Date: " + record.UpdatedDate)
rootTree.Child("Updated Date Normalized: " + record.UpdatedDateNormalized)
// Styles
rootTree.Enumerator(tree.RoundedEnumerator)
rootTree.EnumeratorStyle(enumeratorStyle)
rootTree.RootStyle(rootStyle)
rootTree.ItemStyle(itemStyle)
// Print Tree
fmt.Println(rootTree)
}
-419
View File
@@ -1,419 +0,0 @@
package sqlite
import (
"fmt"
"go.uber.org/zap"
"gorm.io/gorm"
"time"
)
// QueryResults queries the database for results based on the provided options
func QueryResults(options *DBOptions) ([]Result, error) {
db := GetDB()
var results []Result
query := db.Model(&Result{})
// Apply filters based on the provided options
query = applyFilters(query, options)
// Apply limit
if options.Limit > 0 {
query = query.Limit(options.Limit)
}
// Execute the query
if err := query.Find(&results).Error; err != nil {
zap.L().Error("query_results",
zap.String("message", "failed to query results"),
zap.Error(err),
)
return nil, fmt.Errorf("failed to query results: %w", err)
}
return results, nil
}
// applyFilters applies filters to the query based on the provided options
func applyFilters(query *gorm.DB, options *DBOptions) *gorm.DB {
// Helper function to apply filter based on exact match setting
applyFilter := func(field, value string) *gorm.DB {
if value == "" {
return query
}
if options.ExactMatch {
return query.Where(field+" = ?", value)
} else {
return query.Where(field+" LIKE ?", "%"+value+"%")
}
}
// Apply filters for each field if provided
if options.Email != "" {
query = applyFilter("email", options.Email)
}
if options.Username != "" {
query = applyFilter("username", options.Username)
}
if options.IPAddress != "" {
query = applyFilter("ip_address", options.IPAddress)
}
if options.Password != "" {
query = applyFilter("password", options.Password)
}
if options.HashedPassword != "" {
query = applyFilter("hashed_password", options.HashedPassword)
}
if options.Name != "" {
query = applyFilter("name", options.Name)
}
if options.Vin != "" {
query = applyFilter("vin", options.Vin)
}
if options.LicensePlate != "" {
query = applyFilter("license_plate", options.LicensePlate)
}
if options.Address != "" {
query = applyFilter("address", options.Address)
}
if options.Phone != "" {
query = applyFilter("phone", options.Phone)
}
if options.Social != "" {
query = applyFilter("social", options.Social)
}
if options.CryptoCurrencyAddress != "" {
query = applyFilter("cryptocurrency_address", options.CryptoCurrencyAddress)
}
if options.Domain != "" {
query = applyFilter("url", options.Domain)
}
// Apply non-empty field filters
for _, field := range options.NonEmptyFields {
switch field {
case "username":
query = query.Where("JSON_ARRAY_LENGTH(username) > 0")
case "email":
query = query.Where("JSON_ARRAY_LENGTH(email) > 0")
case "ip_address", "ipaddress", "ip":
query = query.Where("JSON_ARRAY_LENGTH(ip_address) > 0")
case "password":
query = query.Where("JSON_ARRAY_LENGTH(password) > 0")
case "hashed_password", "hash":
query = query.Where("JSON_ARRAY_LENGTH(hashed_password) > 0")
case "name":
query = query.Where("JSON_ARRAY_LENGTH(name) > 0")
case "vin":
query = query.Where("JSON_ARRAY_LENGTH(vin) > 0")
case "license_plate", "license":
query = query.Where("JSON_ARRAY_LENGTH(license_plate) > 0")
case "address":
query = query.Where("JSON_ARRAY_LENGTH(address) > 0")
case "phone":
query = query.Where("JSON_ARRAY_LENGTH(phone) > 0")
case "social":
query = query.Where("JSON_ARRAY_LENGTH(social) > 0")
case "cryptocurrency_address", "crypto":
query = query.Where("JSON_ARRAY_LENGTH(cryptocurrency_address) > 0")
case "url", "domain":
query = query.Where("JSON_ARRAY_LENGTH(url) > 0")
}
}
return query
}
// GetResultsCount returns the count of results matching the provided options
func GetResultsCount(options *DBOptions) (int64, error) {
db := GetDB()
var count int64
query := db.Model(&Result{})
// Apply filters based on the provided options
query = applyFilters(query, options)
// Count the results
if err := query.Count(&count).Error; err != nil {
zap.L().Error("get_results_count",
zap.String("message", "failed to count results"),
zap.Error(err),
)
return 0, fmt.Errorf("failed to count results: %w", err)
}
return count, nil
}
// QueryRuns queries the database for previous query runs (QueryOptions) based on the provided filters
func QueryRuns(limit, lastXRuns int, startDate, endDate time.Time, containsQuery string) ([]QueryOptions, error) {
db := GetDB()
var runs []QueryOptions
query := db.Model(&QueryOptions{})
// Apply date range filter if provided
if lastXRuns > 0 {
query = query.Order("created_at DESC").Limit(lastXRuns)
} else if !startDate.IsZero() && !endDate.IsZero() {
query = query.Where("created_at BETWEEN ? AND ?", startDate, endDate)
} else if !startDate.IsZero() {
query = query.Where("created_at >= ?", startDate)
} else if !endDate.IsZero() {
query = query.Where("created_at <= ?", endDate)
}
// Apply query filter if provided
if containsQuery != "" {
// Search in all query fields
query = query.Where(
"username_query LIKE ? OR "+
"email_query LIKE ? OR "+
"ip_query LIKE ? OR "+
"pass_query LIKE ? OR "+
"hash_query LIKE ? OR "+
"name_query LIKE ? OR "+
"domain_query LIKE ? OR "+
"vin_query LIKE ? OR "+
"license_plate_query LIKE ? OR "+
"address_query LIKE ? OR "+
"phone_query LIKE ? OR "+
"social_query LIKE ? OR "+
"crypto_address_query LIKE ?",
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
"%"+containsQuery+"%",
)
}
// Apply limit
if limit > 0 {
query = query.Limit(limit)
}
// Order by most recent first
query = query.Order("created_at DESC")
// Execute the query
if err := query.Find(&runs).Error; err != nil {
zap.L().Error("query_runs",
zap.String("message", "failed to query runs"),
zap.Error(err),
)
return nil, fmt.Errorf("failed to query runs: %w", err)
}
return runs, nil
}
// GetRunsCount returns the count of runs matching the provided filters
func GetRunsCount(lastXRuns int, startDate, endDate time.Time, containsQuery string) (int64, error) {
db := GetDB()
var count int64
query := db.Model(&QueryOptions{})
// Apply date range filter if provided
if lastXRuns > 0 {
query = query.Order("created_at DESC").Limit(lastXRuns)
} else if !startDate.IsZero() && !endDate.IsZero() {
query = query.Where("created_at BETWEEN ? AND ?", startDate, endDate)
} else if !startDate.IsZero() {
query = query.Where("created_at >= ?", startDate)
} else if !endDate.IsZero() {
query = query.Where("created_at <= ?", endDate)
}
// Apply query filter if provided
if containsQuery != "" {
// Search in all query fields
query = query.Where(
"username_query LIKE ? OR "+
"email_query LIKE ? OR "+
"ip_query LIKE ? OR "+
"pass_query LIKE ? OR "+
"hash_query LIKE ? OR "+
"name_query LIKE ? OR "+
"domain_query LIKE ? OR "+
"vin_query LIKE ? OR "+
"license_plate_query LIKE ? OR "+
"address_query LIKE ? OR "+
"phone_query LIKE ? OR "+
"social_query LIKE ? OR "+
"crypto_address_query LIKE ?",
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
"%"+containsQuery+"%",
)
}
// Count the results
if err := query.Count(&count).Error; err != nil {
zap.L().Error("get_runs_count",
zap.String("message", "failed to count runs"),
zap.Error(err),
)
return 0, fmt.Errorf("failed to count runs: %w", err)
}
return count, nil
}
// QueryCreds queries the database for credentials based on the provided filters
func QueryCreds(options *DBOptions) ([]Creds, error) {
db := GetDB()
var creds []Creds
query := db.Model(&Creds{})
// Apply filters based on the provided options
if options.Username != "" {
if options.ExactMatch {
query = query.Where("username = ?", options.Username)
} else {
query = query.Where("username LIKE ?", "%"+options.Username+"%")
}
}
if options.Email != "" {
if options.ExactMatch {
query = query.Where("email = ?", options.Email)
} else {
query = query.Where("email LIKE ?", "%"+options.Email+"%")
}
}
if options.Password != "" {
if options.ExactMatch {
query = query.Where("password = ?", options.Password)
} else {
query = query.Where("password LIKE ?", "%"+options.Password+"%")
}
}
// Apply limit
if options.Limit > 0 {
query = query.Limit(options.Limit)
}
// Execute the query
if err := query.Find(&creds).Error; err != nil {
zap.L().Error("query_creds",
zap.String("message", "failed to query credentials"),
zap.Error(err),
)
return nil, fmt.Errorf("failed to query credentials: %w", err)
}
return creds, nil
}
// GetCredsCount returns the count of credentials matching the provided filters
func GetCredsCount(options *DBOptions) (int64, error) {
db := GetDB()
var count int64
query := db.Model(&Creds{})
// Apply filters based on the provided options
if options.Username != "" {
if options.ExactMatch {
query = query.Where("username = ?", options.Username)
} else {
query = query.Where("username LIKE ?", "%"+options.Username+"%")
}
}
if options.Email != "" {
if options.ExactMatch {
query = query.Where("email = ?", options.Email)
} else {
query = query.Where("email LIKE ?", "%"+options.Email+"%")
}
}
if options.Password != "" {
if options.ExactMatch {
query = query.Where("password = ?", options.Password)
} else {
query = query.Where("password LIKE ?", "%"+options.Password+"%")
}
}
// Count the results
if err := query.Count(&count).Error; err != nil {
zap.L().Error("get_creds_count",
zap.String("message", "failed to count credentials"),
zap.Error(err),
)
return 0, fmt.Errorf("failed to count credentials: %w", err)
}
return count, nil
}
// ExecuteRawQuery executes a raw SQL query and returns the results as a slice of maps
func ExecuteRawQuery(query string) ([]map[string]interface{}, error) {
db := GetDB()
rows, err := db.Raw(query).Rows()
if err != nil {
zap.L().Error("raw_query",
zap.String("message", "failed to execute raw query"),
zap.Error(err),
)
return nil, fmt.Errorf("failed to execute raw query: %w", err)
}
defer rows.Close()
columns, err := rows.Columns()
if err != nil {
zap.L().Error("raw_query",
zap.String("message", "failed to get columns from raw query"),
zap.Error(err),
)
return nil, fmt.Errorf("failed to get columns from raw query: %w", err)
}
var results []map[string]interface{}
for rows.Next() {
// Create a slice of interface{} to hold the values
values := make([]interface{}, len(columns))
pointers := make([]interface{}, len(columns))
for i := range values {
pointers[i] = &values[i]
}
// Scan the result into the pointers
if err := rows.Scan(pointers...); err != nil {
zap.L().Error("raw_query",
zap.String("message", "failed to scan row from raw query"),
zap.Error(err),
)
return nil, fmt.Errorf("failed to scan row from raw query: %w", err)
}
// Create a map for this row
rowMap := make(map[string]interface{})
for i, col := range columns {
val := values[i]
rowMap[col] = val
}
results = append(results, rowMap)
}
return results, nil
}
+33 -2
View File
@@ -51,7 +51,7 @@ func InitDB(dbPath string) (*gorm.DB, error) {
} }
// Auto migrate your models // Auto migrate your models
err = db.AutoMigrate(&Result{}, &Creds{}, QueryOptions{}, Creds{}, WhoisRecord{}, SubdomainRecord{}, HistoryRecord{}) err = db.AutoMigrate(&Result{}, &Creds{}, &QueryOptions{}, &Creds{}, &WhoisRecord{}, &SubdomainRecord{}, &HistoryRecord{}, &LookupResult{})
if err != nil { if err != nil {
zap.L().Error("Failed to migrate database", zap.Error(err)) zap.L().Error("Failed to migrate database", zap.Error(err))
return nil, fmt.Errorf("failed to migrate database: %w", err) return nil, fmt.Errorf("failed to migrate database: %w", err)
@@ -163,7 +163,7 @@ func StoreWhoisRecord(whoisRecord WhoisRecord) error {
return nil return nil
} }
func StoreSubdomainRecord(subdomainRecords []SubdomainRecord) error { func StoreSubdomainRecords(subdomainRecords []SubdomainRecord) error {
if len(subdomainRecords) == 0 { if len(subdomainRecords) == 0 {
return nil return nil
} }
@@ -224,3 +224,34 @@ func StoreHistoryRecord(historyRecords []HistoryRecord) error {
return lastErr return lastErr
} }
func StoreIPLookup(ipLookup []LookupResult) error {
if len(ipLookup) == 0 {
return nil
}
zap.L().Info("Storing IP lookup records", zap.Int("count", len(ipLookup)))
db := GetDB()
// Use batch insert with conflict handling
const batchSize = 100
var lastErr error
for i := 0; i < len(ipLookup); i += batchSize {
end := i + batchSize
if end > len(ipLookup) {
end = len(ipLookup)
}
batch := ipLookup[i:end]
// Use Clauses with OnConflict DoNothing to skip conflicts
err := db.Clauses(clause.OnConflict{DoNothing: true}).CreateInBatches(&batch, batchSize).Error
if err != nil {
zap.L().Warn("Error storing some IP lookup records", zap.Error(err))
lastErr = err
// Continue with next batch despite error
}
}
return lastErr
}
+6 -4
View File
@@ -67,13 +67,14 @@ type QueryOptions struct {
CryptoAddressQuery string `json:"crypto_address_query"` CryptoAddressQuery string `json:"crypto_address_query"`
PrintBalance bool `json:"print_balance"` PrintBalance bool `json:"print_balance"`
CredsOnly bool `json:"creds_only"` CredsOnly bool `json:"creds_only"`
Debug bool `json:"debug"`
} }
func (QueryOptions) TableName() string { func (QueryOptions) TableName() string {
return "query_options" return "query_options"
} }
func NewQueryOptions(maxRecords, maxRequests, startingPage int, outputFormat, outputFile, usernameQuery, emailQuery, ipQuery, passQuery, hashQuery, nameQuery, domainQuery, vinQuery, licensePlateQuery, addressQuery, phoneQuery, socialQuery, cryptoAddressQuery string, regexMatch, wildcardMatch, printBalance, credsOnly bool) *QueryOptions { func NewQueryOptions(maxRecords, maxRequests, startingPage int, outputFormat, outputFile, usernameQuery, emailQuery, ipQuery, passQuery, hashQuery, nameQuery, domainQuery, vinQuery, licensePlateQuery, addressQuery, phoneQuery, socialQuery, cryptoAddressQuery string, regexMatch, wildcardMatch, printBalance, credsOnly, debug bool) *QueryOptions {
return &QueryOptions{ return &QueryOptions{
MaxRecords: maxRecords, MaxRecords: maxRecords,
MaxRequests: maxRequests, MaxRequests: maxRequests,
@@ -97,14 +98,15 @@ func NewQueryOptions(maxRecords, maxRequests, startingPage int, outputFormat, ou
PhoneQuery: phoneQuery, PhoneQuery: phoneQuery,
SocialQuery: socialQuery, SocialQuery: socialQuery,
CryptoAddressQuery: cryptoAddressQuery, CryptoAddressQuery: cryptoAddressQuery,
Debug: debug,
} }
} }
type Creds struct { type Creds struct {
gorm.Model gorm.Model
Email string `json:"email" yaml:"email" xml:"email"` Email string `json:"email" yaml:"email" xml:"email" gorm:"uniqueIndex:idx_email_username_password"`
Username string `json:"username" yaml:"username" xml:"username"` Username string `json:"username" yaml:"username" xml:"username" gorm:"uniqueIndex:idx_email_username_password"`
Password string `json:"password" yaml:"password" xml:"password"` Password string `json:"password" yaml:"password" xml:"password" gorm:"uniqueIndex:idx_email_username_password"`
} }
func (Creds) TableName() string { func (Creds) TableName() string {
+5
View File
@@ -11,6 +11,7 @@ const (
WhoIsTable WhoIsTable
SubdomainsTable SubdomainsTable
HistoryTable HistoryTable
LookupTable
UnknownTable UnknownTable
) )
@@ -28,6 +29,8 @@ func GetTable(userInput string) Table {
return SubdomainsTable return SubdomainsTable
case "history": case "history":
return HistoryTable return HistoryTable
case "lookup":
return LookupTable
default: default:
return UnknownTable return UnknownTable
} }
@@ -47,6 +50,8 @@ func (t Table) Object() interface{} {
return SubdomainRecord{} return SubdomainRecord{}
case HistoryTable: case HistoryTable:
return HistoryRecord{} return HistoryRecord{}
case LookupTable:
return LookupResult{}
default: default:
return nil return nil
} }
+346 -4
View File
@@ -1,6 +1,10 @@
package sqlite package sqlite
import "gorm.io/gorm" import (
"fmt"
"gorm.io/gorm"
"strings"
)
type WhoIsLookupResult struct { type WhoIsLookupResult struct {
RemainingCredits int `json:"remaining_credits"` RemainingCredits int `json:"remaining_credits"`
@@ -17,7 +21,7 @@ type WhoisRecord struct {
ContactEmail string `json:"contactEmail"` ContactEmail string `json:"contactEmail"`
CreatedDate string `json:"createdDate"` CreatedDate string `json:"createdDate"`
CreatedDateNormalized string `json:"createdDateNormalized"` CreatedDateNormalized string `json:"createdDateNormalized"`
DomainName string `json:"domainName"` DomainName string `json:"domainName" gorm:"unique"`
DomainNameExt string `json:"domainNameExt"` DomainNameExt string `json:"domainNameExt"`
EstimatedDomainAge int `json:"estimatedDomainAge"` EstimatedDomainAge int `json:"estimatedDomainAge"`
ExpiresDate string `json:"expiresDate"` ExpiresDate string `json:"expiresDate"`
@@ -38,6 +42,142 @@ type WhoisRecord struct {
UpdatedDateNormalized string `json:"updatedDateNormalized"` UpdatedDateNormalized string `json:"updatedDateNormalized"`
} }
func (w WhoisRecord) String() string {
var sb strings.Builder
// Main domain information
sb.WriteString(fmt.Sprintf("Domain Name: %s\n", w.DomainName))
sb.WriteString(fmt.Sprintf("Domain Name Ext: %s\n", w.DomainNameExt))
sb.WriteString(fmt.Sprintf("Registrar Name: %s\n", w.RegistrarName))
sb.WriteString(fmt.Sprintf("Registrar IANA ID: %s\n", w.RegistrarIANAID))
sb.WriteString(fmt.Sprintf("Contact Email: %s\n", w.ContactEmail))
sb.WriteString(fmt.Sprintf("Estimated Domain Age: %d days\n", w.EstimatedDomainAge))
// Dates
sb.WriteString(fmt.Sprintf("Created Date: %s (Normalized: %s)\n", w.CreatedDate, w.CreatedDateNormalized))
sb.WriteString(fmt.Sprintf("Updated Date: %s (Normalized: %s)\n", w.UpdatedDate, w.UpdatedDateNormalized))
sb.WriteString(fmt.Sprintf("Expires Date: %s (Normalized: %s)\n", w.ExpiresDate, w.ExpiresDateNormalized))
// Status
sb.WriteString(fmt.Sprintf("Status: %s\n", w.Status))
// Parse code
sb.WriteString(fmt.Sprintf("Parse Code: %d\n", w.ParseCode))
// Audit information
sb.WriteString("\nAudit Information:\n")
sb.WriteString(fmt.Sprintf(" Created Date: %s\n", w.Audit.CreatedDate))
sb.WriteString(fmt.Sprintf(" Updated Date: %s\n", w.Audit.UpdatedDate))
// Name servers
sb.WriteString("\nName Servers:\n")
if len(w.NameServers.HostNames) > 0 {
for i, ns := range w.NameServers.HostNames {
ip := ""
if i < len(w.NameServers.IPs) {
ip = fmt.Sprintf(" (%s)", w.NameServers.IPs[i])
}
sb.WriteString(fmt.Sprintf(" %d. %s%s\n", i+1, ns, ip))
}
} else {
sb.WriteString(" None listed\n")
}
if w.NameServers.RawText != "" {
sb.WriteString(fmt.Sprintf(" Raw Text: %s\n", w.NameServers.RawText))
}
// Contact information
sb.WriteString("\nRegistrant Contact:\n")
formatWhoisContact(&sb, w.Registrant, " ")
sb.WriteString("\nTechnical Contact:\n")
formatWhoisContact(&sb, w.TechnicalContact, " ")
// Registry Data
sb.WriteString("\nRegistry Data:\n")
if w.RegistryData.DomainName != "" {
sb.WriteString(fmt.Sprintf(" Domain Name: %s\n", w.RegistryData.DomainName))
sb.WriteString(fmt.Sprintf(" Registrar Name: %s\n", w.RegistryData.RegistrarName))
sb.WriteString(fmt.Sprintf(" Registrar IANA ID: %s\n", w.RegistryData.RegistrarIANAID))
sb.WriteString(fmt.Sprintf(" Whois Server: %s\n", w.RegistryData.WhoisServer))
sb.WriteString(fmt.Sprintf(" Status: %s\n", w.RegistryData.Status))
// Registry dates
sb.WriteString(fmt.Sprintf(" Created Date: %s (Normalized: %s)\n",
w.RegistryData.CreatedDate, w.RegistryData.CreatedDateNormalized))
sb.WriteString(fmt.Sprintf(" Updated Date: %s (Normalized: %s)\n",
w.RegistryData.UpdatedDate, w.RegistryData.UpdatedDateNormalized))
sb.WriteString(fmt.Sprintf(" Expires Date: %s (Normalized: %s)\n",
w.RegistryData.ExpiresDate, w.RegistryData.ExpiresDateNormalized))
// Registry nameservers
sb.WriteString(" Name Servers:\n")
if len(w.RegistryData.NameServers.HostNames) > 0 {
for i, ns := range w.RegistryData.NameServers.HostNames {
ip := ""
if i < len(w.RegistryData.NameServers.IPs) {
ip = fmt.Sprintf(" (%s)", w.RegistryData.NameServers.IPs[i])
}
sb.WriteString(fmt.Sprintf(" %d. %s%s\n", i+1, ns, ip))
}
} else {
sb.WriteString(" None listed\n")
}
// Registry audit
sb.WriteString(" Audit Information:\n")
sb.WriteString(fmt.Sprintf(" Created Date: %s\n", w.RegistryData.Audit.CreatedDate))
sb.WriteString(fmt.Sprintf(" Updated Date: %s\n", w.RegistryData.Audit.UpdatedDate))
} else {
sb.WriteString(" No registry data available\n")
}
// Header and footer
if w.Header != "" {
headerPreview := w.Header
if len(headerPreview) > 100 {
headerPreview = headerPreview[:100] + "... [truncated]"
}
sb.WriteString("\nHeader:\n")
sb.WriteString(headerPreview)
sb.WriteString("\n")
}
if w.Footer != "" {
footerPreview := w.Footer
if len(footerPreview) > 100 {
footerPreview = footerPreview[:100] + "... [truncated]"
}
sb.WriteString("\nFooter:\n")
sb.WriteString(footerPreview)
sb.WriteString("\n")
}
// Raw text (truncated if too long)
if w.RawText != "" {
rawTextPreview := w.RawText
if len(rawTextPreview) > 500 {
rawTextPreview = rawTextPreview[:500] + "... [truncated]"
}
sb.WriteString("\nRaw Text:\n")
sb.WriteString(rawTextPreview)
sb.WriteString("\n")
}
if w.StrippedText != "" {
strippedTextPreview := w.StrippedText
if len(strippedTextPreview) > 500 {
strippedTextPreview = strippedTextPreview[:500] + "... [truncated]"
}
sb.WriteString("\nStripped Text:\n")
sb.WriteString(strippedTextPreview)
sb.WriteString("\n")
}
return sb.String()
}
func (WhoisRecord) TableName() string { func (WhoisRecord) TableName() string {
return "whois" return "whois"
} }
@@ -104,7 +244,7 @@ type ScanResult struct {
type SubdomainRecord struct { type SubdomainRecord struct {
gorm.Model gorm.Model
Domain string `json:"domain"` Domain string `json:"domain" gorm:"unique"`
FirstSeen int64 `json:"firstSeen"` FirstSeen int64 `json:"firstSeen"`
LastSeen int64 `json:"lastSeen"` LastSeen int64 `json:"lastSeen"`
} }
@@ -131,7 +271,7 @@ type HistoryRecord struct {
CleanText string `json:"cleanText"` CleanText string `json:"cleanText"`
CreatedDateISO8601 string `json:"createdDateISO8601"` CreatedDateISO8601 string `json:"createdDateISO8601"`
CreatedDateRaw string `json:"createdDateRaw"` CreatedDateRaw string `json:"createdDateRaw"`
DomainName string `json:"domainName"` DomainName string `json:"domainName" gorm:"unique"`
DomainType string `json:"domainType"` DomainType string `json:"domainType"`
ExpiresDateISO8601 string `json:"expiresDateISO8601"` ExpiresDateISO8601 string `json:"expiresDateISO8601"`
ExpiresDateRaw string `json:"expiresDateRaw"` ExpiresDateRaw string `json:"expiresDateRaw"`
@@ -147,6 +287,131 @@ type HistoryRecord struct {
ZoneContact ContactInfo `json:"zoneContact" gorm:"serializer:json"` ZoneContact ContactInfo `json:"zoneContact" gorm:"serializer:json"`
} }
func (h HistoryRecord) String() string {
var sb strings.Builder
// Main domain information
sb.WriteString(fmt.Sprintf("Domain Name: %s\n", h.DomainName))
sb.WriteString(fmt.Sprintf("Domain Type: %s\n", h.DomainType))
sb.WriteString(fmt.Sprintf("Registrar Name: %s\n", h.RegistrarName))
sb.WriteString(fmt.Sprintf("Whois Server: %s\n", h.WhoisServer))
// Dates
sb.WriteString(fmt.Sprintf("Created Date: %s (Raw: %s)\n", h.CreatedDateISO8601, h.CreatedDateRaw))
sb.WriteString(fmt.Sprintf("Updated Date: %s (Raw: %s)\n", h.UpdatedDateISO8601, h.UpdatedDateRaw))
sb.WriteString(fmt.Sprintf("Expires Date: %s (Raw: %s)\n", h.ExpiresDateISO8601, h.ExpiresDateRaw))
// Status
sb.WriteString("Status: ")
if len(h.Status) > 0 {
sb.WriteString(strings.Join(h.Status, ", "))
} else {
sb.WriteString("N/A")
}
sb.WriteString("\n")
// Audit information
sb.WriteString("\nAudit Information:\n")
sb.WriteString(fmt.Sprintf(" Created Date: %s\n", h.Audit.CreatedDate))
sb.WriteString(fmt.Sprintf(" Updated Date: %s\n", h.Audit.UpdatedDate))
// Name servers
sb.WriteString("\nName Servers:\n")
if len(h.NameServers) > 0 {
for i, ns := range h.NameServers {
sb.WriteString(fmt.Sprintf(" %d. %s\n", i+1, ns))
}
} else {
sb.WriteString(" None listed\n")
}
// Contact information
sb.WriteString("\nRegistrant Contact:\n")
formatContact(&sb, h.RegistrantContact, " ")
sb.WriteString("\nAdministrative Contact:\n")
formatContact(&sb, h.AdministrativeContact, " ")
sb.WriteString("\nTechnical Contact:\n")
formatContact(&sb, h.TechnicalContact, " ")
sb.WriteString("\nBilling Contact:\n")
formatContact(&sb, h.BillingContact, " ")
sb.WriteString("\nZone Contact:\n")
formatContact(&sb, h.ZoneContact, " ")
// Raw text (truncated if too long)
if len(h.RawText) > 0 {
rawTextPreview := h.RawText
if len(rawTextPreview) > 500 {
rawTextPreview = rawTextPreview[:500] + "... [truncated]"
}
sb.WriteString("\nRaw Text:\n")
sb.WriteString(rawTextPreview)
sb.WriteString("\n")
}
if len(h.CleanText) > 0 {
cleanTextPreview := h.CleanText
if len(cleanTextPreview) > 500 {
cleanTextPreview = cleanTextPreview[:500] + "... [truncated]"
}
sb.WriteString("\nClean Text:\n")
sb.WriteString(cleanTextPreview)
sb.WriteString("\n")
}
return sb.String()
}
// Helper function to format contact information
func formatContact(sb *strings.Builder, contact ContactInfo, indent string) {
if contact.Name == "" && contact.Organization == "" && contact.Email == "" {
sb.WriteString(indent + "No contact information available\n")
return
}
if contact.Name != "" {
sb.WriteString(indent + "Name: " + contact.Name + "\n")
}
if contact.Organization != "" {
sb.WriteString(indent + "Organization: " + contact.Organization + "\n")
}
if contact.Email != "" {
sb.WriteString(indent + "Email: " + contact.Email + "\n")
}
if contact.Street != "" {
sb.WriteString(indent + "Street: " + contact.Street + "\n")
}
if contact.City != "" {
sb.WriteString(indent + "City: " + contact.City + "\n")
}
if contact.State != "" {
sb.WriteString(indent + "State: " + contact.State + "\n")
}
if contact.PostalCode != "" {
sb.WriteString(indent + "Postal Code: " + contact.PostalCode + "\n")
}
if contact.Country != "" {
sb.WriteString(indent + "Country: " + contact.Country + "\n")
}
if contact.Telephone != "" {
phone := contact.Telephone
if contact.TelephoneExt != "" {
phone += " ext. " + contact.TelephoneExt
}
sb.WriteString(indent + "Telephone: " + phone + "\n")
}
if contact.Fax != "" {
fax := contact.Fax
if contact.FaxExt != "" {
fax += " ext. " + contact.FaxExt
}
sb.WriteString(indent + "Fax: " + fax + "\n")
}
}
func (HistoryRecord) TableName() string { func (HistoryRecord) TableName() string {
return "history" return "history"
} }
@@ -170,3 +435,80 @@ type ContactInfo struct {
type WhoIsCredits struct { type WhoIsCredits struct {
WhoisCredits int `json:"whois_credits"` WhoisCredits int `json:"whois_credits"`
} }
type WhoIsIPLookup struct {
RemainingCredits int `json:"remaining_credits"`
Data IPData `json:"data"`
}
type WhoIsMXLookup struct {
RemainingCredits int `json:"remaining_credits"`
Data IPData `json:"data"`
}
type WhoIsNSLookup struct {
RemainingCredits int `json:"remaining_credits"`
Data IPData `json:"data"`
}
type IPData struct {
CurrentPage string `json:"current_page"`
Result []LookupResult `json:"result"`
Size int `json:"size"`
}
type LookupResult struct {
gorm.Model
FirstSeen int64 `json:"first_seen"`
LastVisit int64 `json:"last_visit"`
Name string `json:"name" gorm:"unique"`
SearchTerm string `json:"search_term,omitempty"` // For storing the IP address this domain is associated with
Type string `json:"type,omitempty"` // For storing the MX address this domain is associated with
}
func (LookupResult) TableName() string {
return "lookup"
}
// Helper function to format contact information for WhoisRecord
func formatWhoisContact(sb *strings.Builder, contact Contact, indent string) {
if contact.Name == "" && contact.Organization == "" {
sb.WriteString(indent + "No contact information available\n")
return
}
if contact.Name != "" {
sb.WriteString(indent + "Name: " + contact.Name + "\n")
}
if contact.Organization != "" {
sb.WriteString(indent + "Organization: " + contact.Organization + "\n")
}
if contact.Street1 != "" {
sb.WriteString(indent + "Street: " + contact.Street1 + "\n")
}
if contact.City != "" {
sb.WriteString(indent + "City: " + contact.City + "\n")
}
if contact.State != "" {
sb.WriteString(indent + "State: " + contact.State + "\n")
}
if contact.PostalCode != "" {
sb.WriteString(indent + "Postal Code: " + contact.PostalCode + "\n")
}
if contact.Country != "" {
sb.WriteString(indent + "Country: " + contact.Country + "\n")
}
if contact.CountryCode != "" {
sb.WriteString(indent + "Country Code: " + contact.CountryCode + "\n")
}
if contact.Telephone != "" {
sb.WriteString(indent + "Telephone: " + contact.Telephone + "\n")
}
if contact.RawText != "" {
rawTextPreview := contact.RawText
if len(rawTextPreview) > 100 {
rawTextPreview = rawTextPreview[:100] + "... [truncated]"
}
sb.WriteString(indent + "Raw Text: " + rawTextPreview + "\n")
}
}
+730 -139
View File
File diff suppressed because it is too large Load Diff