first commit

This commit is contained in:
Ar1ste1a
2025-05-14 22:00:38 -04:00
commit a4dffe61bf
27 changed files with 4742 additions and 0 deletions
+656
View File
@@ -0,0 +1,656 @@
package cmd
import (
"dehasher/internal/export"
"dehasher/internal/files"
"dehasher/internal/pretty"
"dehasher/internal/sqlite"
"encoding/json"
"fmt"
"github.com/spf13/cobra"
"go.uber.org/zap"
"strings"
"time"
)
var (
// DB command flags
dbPath string
// DB query command flags
usernameDBQuery string
emailDBQuery string
ipDBQuery string
passwordDBQuery string
hashDBQuery string
nameDBQuery string
vinDBQuery string
licensePlateDBQuery string
addressDBQuery string
phoneDBQuery string
socialDBQuery string
cryptoCurrencyAddressDBQuery string
domainDBQuery string
limitResultsDB int
exactMatchDBQuery bool
outputFormatDB string
nonEmptyFieldsDBQuery string
displayFieldsDBQuery string
tableTypeDBQuery string
// DB runs command flags
startDateDBRuns string
endDateDBRuns string
containsQueryDBRuns string
lastXRunsDBRuns int
// DB command
dbCmd = &cobra.Command{
Use: "db",
Short: "Database operations for Dehasher",
Long: `Perform database operations like export, import, and query on the local Dehasher database.`,
}
)
func init() {
// Add subcommands to db command
dbCmd.AddCommand(dbExportCmd)
dbCmd.AddCommand(dbQueryCmd)
dbCmd.AddCommand(dbRunsCmd)
dbCmd.AddCommand(dbCredsCmd)
// Add flags specific to db command
dbCmd.PersistentFlags().StringVarP(&dbPath, "db-path", "D", "", "Path to database (default: ~/.local/share/Dehasher/dehashed.db)")
// Add flags specific to db query command
dbQueryCmd.Flags().StringVarP(&usernameDBQuery, "username", "u", "", "Filter by username")
dbQueryCmd.Flags().StringVarP(&emailDBQuery, "email", "e", "", "Filter by email")
dbQueryCmd.Flags().StringVarP(&ipDBQuery, "ip", "i", "", "Filter by IP address")
dbQueryCmd.Flags().StringVarP(&passwordDBQuery, "password", "p", "", "Filter by password")
dbQueryCmd.Flags().StringVarP(&hashDBQuery, "hash", "H", "", "Filter by hashed password")
dbQueryCmd.Flags().StringVarP(&nameDBQuery, "name", "n", "", "Filter by name")
dbQueryCmd.Flags().StringVarP(&vinDBQuery, "vin", "v", "", "Filter by VIN")
dbQueryCmd.Flags().StringVarP(&licensePlateDBQuery, "license", "L", "", "Filter by license plate")
dbQueryCmd.Flags().StringVarP(&addressDBQuery, "address", "a", "", "Filter by address")
dbQueryCmd.Flags().StringVarP(&phoneDBQuery, "phone", "P", "", "Filter by phone number")
dbQueryCmd.Flags().StringVarP(&socialDBQuery, "social", "s", "", "Filter by social media handle")
dbQueryCmd.Flags().StringVarP(&cryptoCurrencyAddressDBQuery, "crypto", "c", "", "Filter by cryptocurrency address")
dbQueryCmd.Flags().StringVarP(&domainDBQuery, "domain", "d", "", "Filter by domain/URL")
dbQueryCmd.Flags().IntVarP(&limitResultsDB, "limit", "l", 100, "Limit number of results")
dbQueryCmd.Flags().BoolVarP(&exactMatchDBQuery, "exact", "x", false, "Use exact matching instead of partial matching")
dbQueryCmd.Flags().StringVarP(&outputFormatDB, "format", "f", "table", "Output format (json, table, simple)")
dbQueryCmd.Flags().StringVar(&nonEmptyFieldsDBQuery, "non-empty", "", "Filter for non-empty fields (comma-separated list, e.g., 'password,email')")
dbQueryCmd.Flags().StringVar(&displayFieldsDBQuery, "display", "", "Fields to display in output (comma-separated list, e.g., 'username,email,password')")
dbQueryCmd.Flags().StringVarP(&tableTypeDBQuery, "table", "t", "results", "Table to query (results, runs, creds)")
// Add flags specific to db runs command
dbRunsCmd.Flags().StringVarP(&startDateDBRuns, "start-date", "s", "", "Start date for filtering runs (format: YYYY-MM-DD)")
dbRunsCmd.Flags().StringVarP(&endDateDBRuns, "end-date", "e", "", "End date for filtering runs (format: YYYY-MM-DD)")
dbRunsCmd.Flags().StringVarP(&containsQueryDBRuns, "contains", "c", "", "Filter runs containing this query string")
dbRunsCmd.Flags().IntVarP(&lastXRunsDBRuns, "last", "x", 0, "Show the last X runs")
dbRunsCmd.Flags().IntVarP(&limitResultsDB, "limit", "l", 100, "Limit number of results")
dbRunsCmd.Flags().StringVarP(&outputFormatDB, "format", "f", "table", "Output format (json, table, simple)")
// Add flags specific to db creds command
dbCredsCmd.Flags().StringVarP(&usernameDBQuery, "username", "u", "", "Filter by username")
dbCredsCmd.Flags().StringVarP(&emailDBQuery, "email", "e", "", "Filter by email")
dbCredsCmd.Flags().StringVarP(&passwordDBQuery, "password", "p", "", "Filter by password")
dbCredsCmd.Flags().IntVarP(&limitResultsDB, "limit", "l", 100, "Limit number of results")
dbCredsCmd.Flags().BoolVarP(&exactMatchDBQuery, "exact", "x", false, "Use exact matching instead of partial matching")
dbCredsCmd.Flags().StringVarP(&outputFormatDB, "format", "f", "table", "Output format (json, table, simple)")
dbCredsCmd.Flags().StringVar(&nonEmptyFieldsDBQuery, "non-empty", "", "Filter for non-empty fields (comma-separated list, e.g., 'password,email')")
dbCredsCmd.Flags().StringVar(&displayFieldsDBQuery, "display", "", "Fields to display in output (comma-separated list, e.g., 'username,email,password')")
}
// DB export command
var dbExportCmd = &cobra.Command{
Use: "export",
Short: "Export database to file",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Exporting database...")
// Create DBOptions with the provided parameters
options := &sqlite.DBOptions{
Username: usernameDBQuery,
Email: emailDBQuery,
IPAddress: ipDBQuery,
Password: passwordDBQuery,
HashedPassword: hashDBQuery,
Name: nameDBQuery,
Limit: limitResultsDB,
ExactMatch: exactMatchDBQuery,
}
// Parse non-empty fields if provided
if nonEmptyFieldsDBQuery != "" {
options.NonEmptyFields = strings.Split(nonEmptyFieldsDBQuery, ",")
}
// Parse display fields if provided
if displayFieldsDBQuery != "" {
options.DisplayFields = strings.Split(displayFieldsDBQuery, ",")
}
// Check if at least one search parameter is provided
if options.Username == "" && options.Email == "" && options.IPAddress == "" &&
options.Password == "" && options.HashedPassword == "" && options.Name == "" &&
len(options.NonEmptyFields) == 0 {
fmt.Println("Error: At least one search parameter is required.")
cmd.Help()
return
}
// Get the count of matching results
count, err := sqlite.GetResultsCount(options)
if err != nil {
fmt.Printf("Error counting results: %v\n", err)
return
}
// Query the database
results, err := sqlite.QueryResults(options)
if err != nil {
fmt.Printf("Error querying database: %v\n", err)
return
}
dhResults := sqlite.DehashedResults{Results: results}
fmt.Printf("Found %d results (showing %d):\n", count, len(results))
// Output results based on format
ft := files.GetFileType(outputFormatDB)
err = export.WriteToFile(dhResults, "dehasher_export", ft)
if err != nil {
zap.L().Error("write_to_file",
zap.String("message", "failed to write to file"),
zap.Error(err),
)
fmt.Printf("Error writing to file: %v\n", err)
return
}
fmt.Printf("Exported successfully to file: dehasher_export%s\n", ft.Extension())
},
}
// DB query command
var dbQueryCmd = &cobra.Command{
Use: "query",
Short: "Query local database",
Long: `Query the local database for previously run dehasher queries based on various parameters.`,
Run: func(cmd *cobra.Command, args []string) {
// Determine which table to query based on the tableTypeDBQuery parameter
switch tableTypeDBQuery {
case "results":
queryResultsTable(cmd)
case "runs":
queryRunsTable()
case "creds":
queryCredsTable(cmd)
default:
fmt.Printf("Error: Unknown table type '%s'. Valid options are: results, runs, creds\n", tableTypeDBQuery)
cmd.Help()
return
}
},
}
func queryRunsTable() {
// Parse date strings to time.Time
var startDate, endDate time.Time
var err error
if startDateDBRuns != "" {
startDate, err = time.Parse("2006-01-02", startDateDBRuns)
if err != nil {
fmt.Printf("Error parsing start date: %v\n", err)
return
}
}
if endDateDBRuns != "" {
endDate, err = time.Parse("2006-01-02", endDateDBRuns)
if err != nil {
fmt.Printf("Error parsing end date: %v\n", err)
return
}
// Set end date to end of day
endDate = endDate.Add(24*time.Hour - time.Second)
}
// Get the count of matching runs
count, err := sqlite.GetRunsCount(lastXRunsDBRuns, startDate, endDate, containsQueryDBRuns)
if err != nil {
fmt.Printf("Error counting runs: %v\n", err)
return
}
// Query the database
runs, err := sqlite.QueryRuns(limitResultsDB, lastXRunsDBRuns, startDate, endDate, containsQueryDBRuns)
if err != nil {
fmt.Printf("Error querying runs: %v\n", err)
return
}
displayRunsResults(count, runs)
}
func displayRunsResults(count int64, runs []sqlite.QueryOptions) {
// Display the results
fmt.Printf("Found %d runs (showing %d):\n", count, len(runs))
if len(runs) == 0 {
fmt.Println("No runs found.")
return
}
// Output results based on format
switch outputFormatDB {
case "json":
data, err := json.MarshalIndent(runs, "", " ")
if err != nil {
fmt.Printf("Error formatting results: %v\n", err)
return
}
fmt.Println(string(data))
case "table":
// Define headers and rows for the table
headers := []string{"ID", "Created At", "Max Records", "Username Query", "Email Query", "IP Query", "Password Query", "Hash Query", "Name Query", "Domain Query"}
rows := make([][]string, len(runs))
for i, run := range runs {
rows[i] = []string{
fmt.Sprintf("%d", run.ID),
run.CreatedAt.Format("2006-01-02 15:04:05"),
fmt.Sprintf("%d", run.MaxRecords),
truncate(run.UsernameQuery, 20),
truncate(run.EmailQuery, 20),
truncate(run.IpQuery, 20),
truncate(run.PassQuery, 20),
truncate(run.HashQuery, 20),
truncate(run.NameQuery, 20),
truncate(run.DomainQuery, 20),
}
}
pretty.Table(headers, rows)
default:
// Simple output
for _, run := range runs {
fmt.Printf("Run ID: %d\n", run.ID)
fmt.Printf(" Created At: %s\n", run.CreatedAt.Format("2006-01-02 15:04:05"))
fmt.Printf(" Max Records: %d\n", run.MaxRecords)
fmt.Printf(" Max Requests: %d\n", run.MaxRequests)
fmt.Printf(" Starting Page: %d\n", run.StartingPage)
fmt.Printf(" Output Format: %s\n", run.OutputFormat.String())
fmt.Printf(" Output File: %s\n", run.OutputFile)
fmt.Printf(" Regex Match: %t\n", run.RegexMatch)
fmt.Printf(" Wildcard Match: %t\n", run.WildcardMatch)
fmt.Printf(" Username Query: %s\n", run.UsernameQuery)
fmt.Printf(" Email Query: %s\n", run.EmailQuery)
fmt.Printf(" IP Query: %s\n", run.IpQuery)
fmt.Printf(" Password Query: %s\n", run.PassQuery)
fmt.Printf(" Hash Query: %s\n", run.HashQuery)
fmt.Printf(" Name Query: %s\n", run.NameQuery)
fmt.Printf(" Domain Query: %s\n", run.DomainQuery)
fmt.Printf(" VIN Query: %s\n", run.VinQuery)
fmt.Printf(" License Plate Query: %s\n", run.LicensePlateQuery)
fmt.Printf(" Address Query: %s\n", run.AddressQuery)
fmt.Printf(" Phone Query: %s\n", run.PhoneQuery)
fmt.Printf(" Social Query: %s\n", run.SocialQuery)
fmt.Printf(" Crypto Address Query: %s\n", run.CryptoAddressQuery)
fmt.Printf(" Print Balance: %t\n", run.PrintBalance)
fmt.Printf(" Creds Only: %t\n", run.CredsOnly)
fmt.Println()
}
}
}
// queryResultsTable queries the results table
func queryResultsTable(cmd *cobra.Command) {
// Create DBOptions with the provided parameters
options := &sqlite.DBOptions{
Username: usernameDBQuery,
Email: emailDBQuery,
IPAddress: ipDBQuery,
Password: passwordDBQuery,
HashedPassword: hashDBQuery,
Name: nameDBQuery,
Vin: vinDBQuery,
LicensePlate: licensePlateDBQuery,
Address: addressDBQuery,
Phone: phoneDBQuery,
Social: socialDBQuery,
CryptoCurrencyAddress: cryptoCurrencyAddressDBQuery,
Domain: domainDBQuery,
Limit: limitResultsDB,
ExactMatch: exactMatchDBQuery,
}
// Parse non-empty fields if provided
if nonEmptyFieldsDBQuery != "" {
options.NonEmptyFields = strings.Split(nonEmptyFieldsDBQuery, ",")
}
// Parse display fields if provided
if displayFieldsDBQuery != "" {
options.DisplayFields = strings.Split(displayFieldsDBQuery, ",")
}
// Check if at least one search parameter is provided
if options.Empty() {
fmt.Println("Error: At least one search parameter is required.")
cmd.Help()
return
}
// Get the count of matching results
count, err := sqlite.GetResultsCount(options)
if err != nil {
fmt.Printf("Error counting results: %v\n", err)
return
}
// Query the database
results, err := sqlite.QueryResults(options)
if err != nil {
fmt.Printf("Error querying database: %v\n", err)
return
}
// Display the results
displayResultsTable(count, results, options)
}
// displayResultsTable displays the results from the results table
func displayResultsTable(count int64, results []sqlite.Result, options *sqlite.DBOptions) {
// Display the results
fmt.Printf("Found %d results (showing %d):\n", count, len(results))
if len(results) == 0 {
fmt.Println("No results found.")
return
}
// Output results based on format
switch outputFormatDB {
case "json":
data, err := json.MarshalIndent(results, "", " ")
if err != nil {
fmt.Printf("Error formatting results: %v\n", err)
return
}
fmt.Println(string(data))
case "table":
// Determine which fields to display
type FieldInfo struct {
Name string
Width int
Getter func(result sqlite.Result) string
}
// Define all available fields
allFields := []FieldInfo{
{"Username", 20, func(r sqlite.Result) string { return truncate(arrayToString(r.Username), 20) }},
{"Email", 30, func(r sqlite.Result) string { return truncate(arrayToString(r.Email), 30) }},
{"IP Address", 15, func(r sqlite.Result) string { return truncate(arrayToString(r.IpAddress), 15) }},
{"Password", 20, func(r sqlite.Result) string { return truncate(arrayToString(r.Password), 20) }},
{"Hashed Password", 20, func(r sqlite.Result) string { return truncate(arrayToString(r.HashedPassword), 20) }},
{"Name", 20, func(r sqlite.Result) string { return truncate(arrayToString(r.Name), 20) }},
{"VIN", 20, func(r sqlite.Result) string { return truncate(arrayToString(r.Vin), 20) }},
{"License Plate", 15, func(r sqlite.Result) string { return truncate(arrayToString(r.LicensePlate), 15) }},
{"Address", 30, func(r sqlite.Result) string { return truncate(arrayToString(r.Address), 30) }},
{"Phone", 15, func(r sqlite.Result) string { return truncate(arrayToString(r.Phone), 15) }},
{"Social", 20, func(r sqlite.Result) string { return truncate(arrayToString(r.Social), 20) }},
{"Crypto Address", 20, func(r sqlite.Result) string { return truncate(arrayToString(r.CryptoCurrencyAddress), 20) }},
{"Domain/URL", 30, func(r sqlite.Result) string { return truncate(arrayToString(r.Url), 30) }},
}
// Select fields to display
var fieldsToDisplay []FieldInfo
var headers []string
if len(options.DisplayFields) > 0 {
// Use specified fields
for _, fieldName := range options.DisplayFields {
fieldName = strings.ToLower(strings.TrimSpace(fieldName))
for _, field := range allFields {
if strings.ToLower(field.Name) == fieldName ||
(fieldName == "ip" && strings.ToLower(field.Name) == "ip address") ||
(fieldName == "hash" && strings.ToLower(field.Name) == "hashed password") ||
(fieldName == "license" && strings.ToLower(field.Name) == "license plate") ||
(fieldName == "crypto" && strings.ToLower(field.Name) == "crypto address") ||
(fieldName == "url" && strings.ToLower(field.Name) == "domain/url") {
fieldsToDisplay = append(fieldsToDisplay, field)
headers = append(headers, field.Name)
break
}
}
}
} else {
// Default fields (first 6)
fieldsToDisplay = allFields[:6]
}
var rows [][]string
for _, result := range results {
rowValues := []string{}
for _, field := range fieldsToDisplay {
rowValues = append(rowValues, field.Getter(result))
}
rows = append(rows, rowValues)
}
pretty.Table(headers, rows)
default:
// Simple output
for i, result := range results {
fmt.Printf("Result %d:\n", i+1)
// Determine which fields to display
if len(options.DisplayFields) > 0 {
// Display only specified fields
for _, field := range options.DisplayFields {
field = strings.ToLower(strings.TrimSpace(field))
switch field {
case "username":
fmt.Printf(" Username: %s\n", result.Username)
case "email":
fmt.Printf(" Email: %s\n", result.Email)
case "ip", "ipaddress", "ip_address":
fmt.Printf(" IP Address: %s\n", result.IpAddress)
case "password":
fmt.Printf(" Password: %s\n", result.Password)
case "hash", "hashed_password":
fmt.Printf(" Hashed Password: %s\n", result.HashedPassword)
case "name":
fmt.Printf(" Name: %s\n", result.Name)
case "vin":
fmt.Printf(" VIN: %s\n", result.Vin)
case "license", "license_plate":
fmt.Printf(" License Plate: %s\n", result.LicensePlate)
case "address":
fmt.Printf(" Address: %s\n", result.Address)
case "phone":
fmt.Printf(" Phone: %s\n", result.Phone)
case "social":
fmt.Printf(" Social: %s\n", result.Social)
case "crypto", "cryptocurrency_address":
fmt.Printf(" Crypto Address: %s\n", result.CryptoCurrencyAddress)
case "domain", "url":
fmt.Printf(" Domain/URL: %s\n", result.Url)
}
}
} else {
// Display default fields
fmt.Printf(" Username: %s\n", result.Username)
fmt.Printf(" Email: %s\n", result.Email)
fmt.Printf(" IP Address: %s\n", result.IpAddress)
fmt.Printf(" Password: %s\n", result.Password)
fmt.Printf(" Hashed Password: %s\n", result.HashedPassword)
fmt.Printf(" Name: %s\n", result.Name)
}
fmt.Println()
}
}
}
// truncate truncates a string to the specified length and adds ellipsis if needed
func truncate(s string, length int) string {
if len(s) <= length {
return s
}
return s[:length-3] + "..."
}
func arrayToString(a []string) string {
return strings.Join(a, ", ")
}
// DB runs command
var dbRunsCmd = &cobra.Command{
Use: "runs",
Short: "Query previous query runs",
Long: `Query the database for previous query runs (QueryOptions) based on date range and query content.`,
Run: func(cmd *cobra.Command, args []string) {
// Call queryRunsTable directly
queryRunsTable()
},
}
// DB creds command
var dbCredsCmd = &cobra.Command{
Use: "creds",
Short: "Query credentials",
Long: `Query the database for credentials based on username, email, and password.`,
Run: func(cmd *cobra.Command, args []string) {
// Call queryCredsTable directly
queryCredsTable(cmd)
},
}
// queryCredsTable queries the credentials table
func queryCredsTable(cmd *cobra.Command) {
// Create DBOptions with the provided parameters
options := &sqlite.DBOptions{
Username: usernameDBQuery,
Email: emailDBQuery,
Password: passwordDBQuery,
Limit: limitResultsDB,
ExactMatch: exactMatchDBQuery,
}
// Parse non-empty fields if provided
if nonEmptyFieldsDBQuery != "" {
options.NonEmptyFields = strings.Split(nonEmptyFieldsDBQuery, ",")
}
// Parse display fields if provided
if displayFieldsDBQuery != "" {
options.DisplayFields = strings.Split(displayFieldsDBQuery, ",")
}
// Check if at least one search parameter is provided
if options.Username == "" && options.Email == "" && options.Password == "" && len(options.NonEmptyFields) == 0 {
fmt.Println("Error: At least one search parameter is required.")
cmd.Help()
return
}
// Get the count of matching credentials
count, err := sqlite.GetCredsCount(options)
if err != nil {
fmt.Printf("Error counting credentials: %v\n", err)
return
}
// Query the database
creds, err := sqlite.QueryCreds(options)
if err != nil {
fmt.Printf("Error querying credentials: %v\n", err)
return
}
// Display the results
displayCredsResults(count, creds)
}
// displayCredsResults displays the results from the creds table
func displayCredsResults(count int64, creds []sqlite.Creds) {
// Display the results
fmt.Printf("Found %d credentials (showing %d):\n", count, len(creds))
if len(creds) == 0 {
fmt.Println("No credentials found.")
return
}
// Output results based on format
switch outputFormatDB {
case "json":
data, err := json.MarshalIndent(creds, "", " ")
if err != nil {
fmt.Printf("Error formatting results: %v\n", err)
return
}
fmt.Println(string(data))
case "table":
// Define all available fields
type FieldInfo struct {
Name string
Getter func(cred sqlite.Creds) string
}
allFields := []FieldInfo{
{"ID", func(c sqlite.Creds) string { return fmt.Sprintf("%d", c.ID) }},
{"Created At", func(c sqlite.Creds) string { return c.CreatedAt.Format("2006-01-02 15:04:05") }},
{"Email", func(c sqlite.Creds) string { return c.Email }},
{"Username", func(c sqlite.Creds) string { return c.Username }},
{"Password", func(c sqlite.Creds) string { return c.Password }},
}
// Select fields to display
var fieldsToDisplay []FieldInfo
var headers []string
if len(displayFieldsDBQuery) > 0 {
// Use specified display fields
displayFields := strings.Split(displayFieldsDBQuery, ",")
for _, fieldName := range displayFields {
fieldName = strings.ToLower(strings.TrimSpace(fieldName))
for _, field := range allFields {
if strings.ToLower(field.Name) == fieldName {
fieldsToDisplay = append(fieldsToDisplay, field)
headers = append(headers, field.Name)
break
}
}
}
} else {
// Default fields
fieldsToDisplay = allFields
for _, field := range fieldsToDisplay {
headers = append(headers, field.Name)
}
}
// Create rows
rows := make([][]string, len(creds))
for i, cred := range creds {
rowValues := []string{}
for _, field := range fieldsToDisplay {
rowValues = append(rowValues, field.Getter(cred))
}
rows[i] = rowValues
}
pretty.Table(headers, rows)
default:
// Simple output
for _, cred := range creds {
fmt.Printf("Credential ID: %d\n", cred.ID)
fmt.Printf(" Created At: %s\n", cred.CreatedAt.Format("2006-01-02 15:04:05"))
fmt.Printf(" Email: %s\n", cred.Email)
fmt.Printf(" Username: %s\n", cred.Username)
fmt.Printf(" Password: %s\n", cred.Password)
fmt.Println()
}
}
}