fixed db query and export

This commit is contained in:
Evan Hosinski
2025-05-15 14:43:13 -04:00
parent a4dffe61bf
commit 9c09684038
15 changed files with 331 additions and 1607 deletions
+176 -614
View File
@@ -1,656 +1,218 @@
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"
"os"
"strings"
"time"
)
var (
// DB command flags
dbPath string
dbQueryTableName string
dbQueryLimitRows int
dbQueryNotNull string
dbQueryColumns string
dbQueryUserQuery string
dbQueryRawQuery string
dbQueryListAll bool
// 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{
databaseQueryCmd = &cobra.Command{
Use: "db",
Short: "Database operations for Dehasher",
Long: `Perform database operations like export, import, and query on the local Dehasher database.`,
Short: "Query the database",
Long: `Query the database for various information.`,
Run: func(cmd *cobra.Command, args []string) {
// If Raw Query is set, execute it and return
if dbQueryRawQuery != "" {
fmt.Println("[*] Executing Raw Query...")
rawDBQuery()
os.Exit(1)
}
// Determine which table to query based on the tableTypeDBQuery parameter
table := GetTable(dbQueryTableName)
if table == UnknownTable {
fmt.Printf("Error: Unknown table type '%s'.\n", dbQueryTableName)
cmd.Help()
return
}
fmt.Println("[*] Querying Database...")
tableQuery(table)
},
}
)
func init() {
// Add subcommands to db command
dbCmd.AddCommand(dbExportCmd)
dbCmd.AddCommand(dbQueryCmd)
dbCmd.AddCommand(dbRunsCmd)
dbCmd.AddCommand(dbCredsCmd)
// Add whois command to root command
rootCmd.AddCommand(databaseQueryCmd)
// 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 whois command
databaseQueryCmd.Flags().StringVarP(&dbQueryTableName, "table", "t", "", "Table to query (results, creds, whois, subdomains, history, query_options)")
databaseQueryCmd.Flags().IntVarP(&dbQueryLimitRows, "limit", "l", 100, "Limit number of results")
databaseQueryCmd.Flags().StringVarP(&dbQueryNotNull, "not-null", "n", "", "Filter for non-null values (comma-separated list, e.g., 'password,email')")
databaseQueryCmd.Flags().StringVarP(&dbQueryColumns, "columns", "c", "", "Columns to display in output (comma-separated list, e.g., 'username,email,password')")
databaseQueryCmd.Flags().StringVarP(&dbQueryUserQuery, "query", "q", "", "User query to execute")
databaseQueryCmd.Flags().StringVarP(&dbQueryRawQuery, "raw-query", "r", "", "Raw SQL query to execute")
databaseQueryCmd.Flags().BoolVarP(&dbQueryListAll, "list-all", "a", false, "List all columns")
// 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')")
// Add mutually exclusive flags to query and raw-query
// Cannot use query and raw-query at the same time
databaseQueryCmd.MarkFlagsMutuallyExclusive("query", "raw-query")
// Raw query does not require a table
databaseQueryCmd.MarkFlagsMutuallyExclusive("query", "table")
// List all columns does not require a query or raw-query
databaseQueryCmd.MarkFlagsMutuallyExclusive("raw-query", "list-all")
}
// 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,
func tableQuery(table Table) {
// Get the columns to query
columns := []string{"*"}
if dbQueryColumns != "" {
columns = strings.Split(dbQueryColumns, ",")
}
// Get the not null fields
notNullFields := []string{}
if dbQueryNotNull != "" {
notNullFields = strings.Split(dbQueryNotNull, ",")
}
// Get the user query
userQuery := ""
if dbQueryUserQuery != "" {
userQuery = dbQueryUserQuery
}
// Get the limit
limit := dbQueryLimitRows
// Get the object for the table
object := table.Object()
// Query the database
db := sqlite.GetDB()
query := db.Model(object).Select(columns)
if len(notNullFields) > 0 {
for _, field := range notNullFields {
query = query.Where(fmt.Sprintf("%s IS NOT NULL", field))
}
}
if userQuery != "" {
query = query.Where(userQuery)
}
if limit > 0 {
query = query.Limit(limit)
}
rows, err := query.Rows()
if err != nil {
zap.L().Error("db_query",
zap.String("message", "failed to execute query"),
zap.Error(err),
)
fmt.Printf("[!] Error executing query: %v\n", err)
}
defer rows.Close()
// Parse non-empty fields if provided
if nonEmptyFieldsDBQuery != "" {
options.NonEmptyFields = strings.Split(nonEmptyFieldsDBQuery, ",")
// Get the columns
cols, err := rows.Columns()
if err != nil {
zap.L().Error("db_query",
zap.String("message", "failed to get columns from query"),
zap.Error(err),
)
fmt.Printf("[!] Error getting columns from query: %v\n", err)
}
// Prepare data for pretty.Table
headers := cols
var tableRows [][]string
// Process the rows
for rows.Next() {
values := make([]interface{}, len(cols))
pointers := make([]interface{}, len(cols))
for i := range values {
pointers[i] = &values[i]
}
// 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"),
if err := rows.Scan(pointers...); err != nil {
zap.L().Error("db_query",
zap.String("message", "failed to scan row from query"),
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),
}
fmt.Printf("[!] Error scanning row from query: %v\n", err)
}
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)
}
}
// Convert row values to strings
rowStrings := make([]string, len(values))
for i, value := range values {
if value == nil {
rowStrings[i] = " "
} 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)
rowStrings[i] = fmt.Sprintf("%v", value)
}
fmt.Println()
}
tableRows = append(tableRows, rowStrings)
}
// Display the table
pretty.Table(headers, tableRows)
}
// 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)
func rawDBQuery() {
db := sqlite.GetDB()
rows, err := db.Raw(dbQueryRawQuery).Rows()
if err != nil {
fmt.Printf("Error counting credentials: %v\n", err)
return
zap.L().Error("raw_query",
zap.String("message", "failed to execute raw query"),
zap.Error(err),
)
fmt.Printf("[!] Error executing raw query: %v\n", err)
}
defer rows.Close()
// Query the database
creds, err := sqlite.QueryCreds(options)
columns, err := rows.Columns()
if err != nil {
fmt.Printf("Error querying credentials: %v\n", err)
return
zap.L().Error("raw_query",
zap.String("message", "failed to get columns from raw query"),
zap.Error(err),
)
fmt.Printf("[!] Error getting columns from raw query: %v\n", err)
}
// 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()
}
}
// Prepare data for pretty.Table
headers := columns
var tableRows [][]string
// Process the rows
for rows.Next() {
values := make([]interface{}, len(columns))
pointers := make([]interface{}, len(columns))
for i := range values {
pointers[i] = &values[i]
}
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),
)
fmt.Printf("[!] Error scanning row from raw query: %v\n", err)
}
// Convert row values to strings
rowStrings := make([]string, len(values))
for i, value := range values {
if value == nil {
rowStrings[i] = " "
} else {
rowStrings[i] = fmt.Sprintf("%v", value)
}
}
tableRows = append(tableRows, rowStrings)
}
// Display the table
pretty.Table(headers, tableRows)
}
+46
View File
@@ -0,0 +1,46 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
func init() {
// Add Subcommand to db command
rootCmd.AddCommand(exportCmd)
}
// DB export command
var (
exportLimitRows int
exportListAll bool
exportTableName string
exportNotNull string
exportColumns string
exportUserQuery string
exportRawQuery string
exportFormat string
exportFile string
exportCmd = &cobra.Command{
Use: "export",
Short: "Export database to file",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Exporting database...")
},
}
)
func init() {
// Add flags specific to export command
exportCmd.Flags().IntVarP(&exportLimitRows, "limit", "l", 100, "Limit number of results")
exportCmd.Flags().BoolVarP(&exportListAll, "list-all", "a", false, "List all columns")
exportCmd.Flags().StringVarP(&exportTableName, "table", "t", "", "Table to export")
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(&exportUserQuery, "query", "q", "", "User query to execute")
exportCmd.Flags().StringVarP(&exportRawQuery, "raw-query", "r", "", "Raw SQL query to execute")
exportCmd.Flags().StringVarP(&exportFormat, "format", "f", "json", "Output format (json, yaml, xml, txt)")
exportCmd.Flags().StringVarP(&exportFile, "file", "o", "export", "File to output results to including extension")
}
+56
View File
@@ -0,0 +1,56 @@
package cmd
import (
"dehasher/internal/sqlite"
"strings"
)
type Table int64
const (
ResultsTable Table = iota
RunsTable
CredsTable
WhoIsTable
SubdomainsTable
HistoryTable
UnknownTable
)
func GetTable(userInput string) Table {
switch strings.ToLower(userInput) {
case "results":
return ResultsTable
case "runs":
return RunsTable
case "creds":
return CredsTable
case "whois":
return WhoIsTable
case "subdomains":
return SubdomainsTable
case "history":
return HistoryTable
default:
return UnknownTable
}
}
func (t Table) Object() interface{} {
switch t {
case ResultsTable:
return sqlite.Result{}
case RunsTable:
return sqlite.QueryOptions{}
case CredsTable:
return sqlite.Creds{}
case WhoIsTable:
return sqlite.WhoisRecord{}
case SubdomainsTable:
return sqlite.SubdomainRecord{}
case HistoryTable:
return sqlite.HistoryRecord{}
default:
return nil
}
}
+6 -17
View File
@@ -39,21 +39,11 @@ var (
Short: "Query the Dehashed API",
Long: `Query the Dehashed API for emails, usernames, passwords, hashes, IP addresses, and names.`,
Run: func(cmd *cobra.Command, args []string) {
// Check if API key and email are provided
key := apiKey
email := apiEmail
// If not provided as flags, try to get from stored values
if key == "" {
key = getStoredApiKey()
}
if email == "" {
email = getStoredApiEmail()
}
key := getStoredApiKey()
// Validate credentials
if key == "" || email == "" {
fmt.Println("API key and email are required. Use --key and --email flags or set them with set-key and set-email commands.")
if key == "" {
fmt.Println("API key is required. Set the key with the \"set-key\" command. [dehasher set-key <api_key>]")
return
}
@@ -99,6 +89,9 @@ var (
)
func init() {
// Add query command to root command
rootCmd.AddCommand(queryCmd)
// Add flags specific to query command
queryCmd.Flags().IntVarP(&maxRecords, "max-records", "m", 30000, "Maximum amount of records to return")
queryCmd.Flags().IntVarP(&maxRequests, "max-requests", "r", -1, "Maximum number of requests to make")
@@ -131,7 +124,3 @@ func init() {
func getStoredApiKey() string {
return badger.GetKey()
}
func getStoredApiEmail() string {
return badger.GetEmail()
}
+3 -33
View File
@@ -10,8 +10,7 @@ import (
var (
// Global Flags
apiKey string
apiEmail string
alternativeDatabase string
// rootCmd is the base command for the CLI.
rootCmd = &cobra.Command{
@@ -62,14 +61,11 @@ func init() {
// Hide the default help command
rootCmd.CompletionOptions.HiddenDefaultCmd = true
// Add global flags for API key and email
rootCmd.PersistentFlags().StringVarP(&apiKey, "key", "k", "", "API Key for authentication")
// Add global flags
rootCmd.PersistentFlags().StringVar(&alternativeDatabase, "alt-db", "", "Alternative database path")
// Add subcommands
rootCmd.AddCommand(dbCmd)
rootCmd.AddCommand(queryCmd)
rootCmd.AddCommand(setKeyCmd)
rootCmd.AddCommand(setEmailCmd)
}
// Command to set API key
@@ -89,23 +85,6 @@ var setKeyCmd = &cobra.Command{
},
}
// Command to set API email
var setEmailCmd = &cobra.Command{
Use: "set-email [email]",
Short: "Set and store API email",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
email := args[0]
// Store email in badger DB
err := storeApiEmail(email)
if err != nil {
fmt.Printf("Error storing API email: %v\n", err)
return
}
fmt.Println("API email stored successfully")
},
}
// Helper functions to store API credentials
func storeApiKey(key string) error {
err := badger.StoreKey(key)
@@ -115,12 +94,3 @@ func storeApiKey(key string) error {
}
return nil
}
func storeApiEmail(email string) error {
err := badger.StoreEmail(email)
if err != nil {
fmt.Printf("Error storing API email: %v\n", err)
return err
}
return nil
}
+3 -12
View File
@@ -29,17 +29,11 @@ var (
Short: "Dehashed WHOIS lookups and reverse WHOIS searches",
Long: `Perform WHOIS lookups, history searches, reverse WHOIS searches, IP lookups, MX lookups, NS lookups, and subdomain scans.`,
Run: func(cmd *cobra.Command, args []string) {
// Check if API key is provided
key := apiKey
// If not provided as flag, try to get from stored value
if key == "" {
key = getStoredApiKey()
}
key := getStoredApiKey()
// Validate credentials
if key == "" {
fmt.Println("API key is required. Use --key flag or set it with set-key command.")
fmt.Println("API key is required. Set the key with the \"set-key\" command. [dehasher set-key <api_key>]")
return
}
@@ -80,7 +74,7 @@ var (
fmt.Printf("Error performing WHOIS lookup: %v\n", err)
return
}
// Fix the output format to use proper formatting
fmt.Printf("WHOIS Lookup Result:\n%+v\n", result.Data.WhoisRecord)
@@ -297,7 +291,4 @@ func init() {
whoisCmd.Flags().BoolVarP(&whoisShowCredits, "credits", "c", false, "Show remaining WHOIS credits")
whoisCmd.Flags().BoolVarP(&whoisHistory, "history", "H", false, "Perform WHOIS history search [25 Credits]")
whoisCmd.Flags().BoolVarP(&whoisSubdomainScan, "subdomains", "s", false, "Perform WHOIS subdomain scan")
// Add API key flag
whoisCmd.Flags().StringVarP(&apiKey, "key", "k", "", "Dehashed API key")
}