fixed db query and export
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user