diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1c2d52b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea/* diff --git a/cmd/api.go b/cmd/api.go new file mode 100644 index 0000000..6e3ece0 --- /dev/null +++ b/cmd/api.go @@ -0,0 +1,126 @@ +package cmd + +import ( + "dehasher/internal/badger" + "dehasher/internal/query" + "dehasher/internal/sqlite" + "fmt" + "github.com/spf13/cobra" +) + +func init() { + // Add query command to root command + rootCmd.AddCommand(apiCmd) + + // Add flags specific to query command + apiCmd.Flags().IntVarP(&maxRecords, "max-records", "m", 30000, "Maximum amount of records to return") + apiCmd.Flags().IntVarP(&maxRequests, "max-requests", "r", -1, "Maximum number of requests to make") + apiCmd.Flags().IntVarP(&startingPage, "starting-page", "s", 1, "Starting page for requests") + apiCmd.Flags().BoolVarP(&printBalance, "print-balance", "b", false, "Print remaining balance after requests") + apiCmd.Flags().BoolVarP(®exMatch, "regex-match", "R", false, "Use regex matching on query fields") + apiCmd.Flags().BoolVarP(&wildcardMatch, "wildcard-match", "W", false, "Use wildcard matching on query fields (Use ? to replace a single character, and * for multiple characters)") + apiCmd.Flags().BoolVarP(&credsOnly, "creds-only", "C", false, "Return credentials only") + apiCmd.Flags().StringVarP(&outputFormat, "format", "f", "json", "Output format (json, yaml, xml, txt)") + apiCmd.Flags().StringVarP(&outputFile, "output", "o", "query", "File to output results to including extension") + apiCmd.Flags().StringVarP(&usernameQuery, "username", "U", "", "Username query") + apiCmd.Flags().StringVarP(&emailQuery, "email-query", "E", "", "Email query") + apiCmd.Flags().StringVarP(&ipQuery, "ip", "I", "", "IP address query") + apiCmd.Flags().StringVarP(&domainQuery, "domain", "D", "", "Domain query") + apiCmd.Flags().StringVarP(&passwordQuery, "password", "P", "", "Password query") + apiCmd.Flags().StringVarP(&vinQuery, "vin", "V", "", "VIN query") + apiCmd.Flags().StringVarP(&licensePlateQuery, "license", "L", "", "License plate query") + apiCmd.Flags().StringVarP(&addressQuery, "address", "A", "", "Address query") + apiCmd.Flags().StringVarP(&phoneQuery, "phone", "M", "", "Phone query") + apiCmd.Flags().StringVarP(&socialQuery, "social", "S", "", "Social query") + apiCmd.Flags().StringVarP(&cryptoCurrencyAddressQuery, "crypto", "B", "", "Crypto currency address query") + apiCmd.Flags().StringVarP(&hashQuery, "hash", "Q", "", "Hashed password query") + apiCmd.Flags().StringVarP(&nameQuery, "name", "N", "", "Name query") + + // Add mutually exclusive flags to exact match and regex match + apiCmd.MarkFlagsMutuallyExclusive("regex-match", "wildcard-match") +} + +var ( + // Query command flags + maxRecords int + maxRequests int + startingPage int + credsOnly bool + printBalance bool + regexMatch bool + wildcardMatch bool + outputFormat string + outputFile string + usernameQuery string + emailQuery string + ipQuery string + passwordQuery string + hashQuery string + nameQuery string + domainQuery string + vinQuery string + licensePlateQuery string + addressQuery string + phoneQuery string + socialQuery string + cryptoCurrencyAddressQuery string + + // Query command + apiCmd = &cobra.Command{ + Use: "api", + 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) { + key := getStoredApiKey() + + // Validate credentials + if key == "" { + fmt.Println("API key is required. Set the key with the \"set-key\" command. [dehasher set-key ]") + return + } + + // Create new QueryOptions + queryOptions := sqlite.NewQueryOptions( + maxRecords, + maxRequests, + startingPage, + outputFormat, + outputFile, + usernameQuery, + emailQuery, + ipQuery, + passwordQuery, + hashQuery, + nameQuery, + domainQuery, + vinQuery, + licensePlateQuery, + addressQuery, + phoneQuery, + socialQuery, + cryptoCurrencyAddressQuery, + regexMatch, + wildcardMatch, + printBalance, + credsOnly, + ) + + // Create new Dehasher + dehasher := query.NewDehasher(queryOptions) + dehasher.SetClientCredentials( + key, + ) + + // Start querying + dehasher.Start() + fmt.Println("\n[*] Completing Process") + + sqlite.StoreQueryOptions(queryOptions) + }, + } +) + +// Helper functions to get stored API credentials +func getStoredApiKey() string { + return badger.GetKey() +} diff --git a/cmd/db.go b/cmd/db.go deleted file mode 100644 index ee22ad5..0000000 --- a/cmd/db.go +++ /dev/null @@ -1,281 +0,0 @@ -package cmd - -import ( - "dehasher/internal/pretty" - "dehasher/internal/sqlite" - "encoding/json" - "fmt" - "github.com/spf13/cobra" - "go.uber.org/zap" - "os" - "strings" -) - -func init() { - // Add whois command to root command - rootCmd.AddCommand(databaseQueryCmd) - - // 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 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") -} - -var ( - dbQueryTableName string - dbQueryLimitRows int - dbQueryNotNull string - dbQueryColumns string - dbQueryUserQuery string - dbQueryRawQuery string - dbQueryListAll bool - - databaseQueryCmd = &cobra.Command{ - Use: "db", - 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 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() - - // 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] - } - 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 scanning row from 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 { - // Check if the value is a slice or array - switch v := value.(type) { - case []string: - // Join string slices with commas, no brackets - rowStrings[i] = strings.Join(v, ", ") - case []interface{}: - // Convert interface slice to strings and join - strSlice := make([]string, len(v)) - for j, item := range v { - if item == nil { - strSlice[j] = "" - } else { - strSlice[j] = fmt.Sprintf("%v", item) - } - } - rowStrings[i] = strings.Join(strSlice, ", ") - case string: - // Handle JSON strings that might be arrays - if strings.HasPrefix(v, "[") && strings.HasSuffix(v, "]") { - // Try to unmarshal JSON array - var strArray []string - if err := json.Unmarshal([]byte(v), &strArray); err == nil { - rowStrings[i] = strings.Join(strArray, ", ") - } else { - rowStrings[i] = v - } - } else { - rowStrings[i] = v - } - default: - rowStrings[i] = fmt.Sprintf("%v", v) - } - } - } - tableRows = append(tableRows, rowStrings) - } - - // Display the table - pretty.Table(headers, tableRows) -} - -func rawDBQuery() { - db := sqlite.GetDB() - rows, err := db.Raw(dbQueryRawQuery).Rows() - if err != nil { - 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() - - columns, err := rows.Columns() - if err != nil { - zap.L().Error("raw_query", - zap.String("message", "failed to get columns from raw query"), - zap.Error(err), - ) - fmt.Printf("[!] Error getting columns from raw query: %v\n", err) - } - - // 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 { - // Check if the value is a slice or array - switch v := value.(type) { - case []string: - // Join string slices with commas, no brackets - rowStrings[i] = strings.Join(v, ", ") - case []interface{}: - // Convert interface slice to strings and join - strSlice := make([]string, len(v)) - for j, item := range v { - if item == nil { - strSlice[j] = "" - } else { - strSlice[j] = fmt.Sprintf("%v", item) - } - } - rowStrings[i] = strings.Join(strSlice, ", ") - case string: - // Handle JSON strings that might be arrays - if strings.HasPrefix(v, "[") && strings.HasSuffix(v, "]") { - // Try to unmarshal JSON array - var strArray []string - if err := json.Unmarshal([]byte(v), &strArray); err == nil { - rowStrings[i] = strings.Join(strArray, ", ") - } else { - rowStrings[i] = v - } - } else { - rowStrings[i] = v - } - default: - rowStrings[i] = fmt.Sprintf("%v", v) - } - } - } - tableRows = append(tableRows, rowStrings) - } - - // Display the table - pretty.Table(headers, tableRows) -} diff --git a/cmd/export.go b/cmd/export.go index dab4bb9..ec0409d 100644 --- a/cmd/export.go +++ b/cmd/export.go @@ -20,18 +20,18 @@ func init() { 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(&exportUserQuery, "user-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") // Add mutually exclusive flags to query and raw-query // Cannot use query and raw-query at the same time - databaseQueryCmd.MarkFlagsMutuallyExclusive("query", "raw-query") + exportCmd.MarkFlagsMutuallyExclusive("user-query", "raw-query") // Raw query does not require a table - databaseQueryCmd.MarkFlagsMutuallyExclusive("query", "table") + exportCmd.MarkFlagsMutuallyExclusive("user-query", "table") // List all columns does not require a query or raw-query - databaseQueryCmd.MarkFlagsMutuallyExclusive("raw-query", "list-all") + exportCmd.MarkFlagsMutuallyExclusive("raw-query", "list-all") } // DB export command @@ -60,8 +60,8 @@ var ( } // Determine which table to query based on the tableTypeDBQuery parameter - table := GetTable(exportTableName) - if table == UnknownTable { + table := sqlite.GetTable(exportTableName) + if table == sqlite.UnknownTable { fmt.Printf("Error: Unknown table type '%s'.\n", exportTableName) cmd.Help() return @@ -74,7 +74,7 @@ var ( ) // exportTableQuery queries a table and exports the results -func exportTableQuery(table Table) { +func exportTableQuery(table sqlite.Table) { // Get the columns to query columns := []string{"*"} if exportColumns != "" { diff --git a/cmd/query.go b/cmd/query.go index 0e8eaaa..82dc6b8 100644 --- a/cmd/query.go +++ b/cmd/query.go @@ -1,126 +1,281 @@ package cmd import ( - "dehasher/internal/badger" - "dehasher/internal/query" + "dehasher/internal/pretty" "dehasher/internal/sqlite" + "encoding/json" "fmt" "github.com/spf13/cobra" + "go.uber.org/zap" + "os" + "strings" ) -var ( - // Query command flags - maxRecords int - maxRequests int - startingPage int - credsOnly bool - printBalance bool - regexMatch bool - wildcardMatch bool - outputFormat string - outputFile string - usernameQuery string - emailQuery string - ipQuery string - passwordQuery string - hashQuery string - nameQuery string - domainQuery string - vinQuery string - licensePlateQuery string - addressQuery string - phoneQuery string - socialQuery string - cryptoCurrencyAddressQuery string +func init() { + // Add whois command to root command + rootCmd.AddCommand(queryCmd) + + // Add flags specific to whois command + queryCmd.Flags().StringVarP(&dbQueryTableName, "table", "t", "", "Table to query (results, creds, whois, subdomains, history, query_options)") + queryCmd.Flags().IntVarP(&dbQueryLimitRows, "limit", "l", 100, "Limit number of results") + queryCmd.Flags().StringVarP(&dbQueryNotNull, "not-null", "n", "", "Filter for non-null values (comma-separated list, e.g., 'password,email')") + queryCmd.Flags().StringVarP(&dbQueryColumns, "columns", "c", "", "Columns to display in output (comma-separated list, e.g., 'username,email,password')") + queryCmd.Flags().StringVarP(&dbQueryUserQuery, "user-query", "q", "", "User query to execute") + queryCmd.Flags().StringVarP(&dbQueryRawQuery, "raw-query", "r", "", "Raw SQL query to execute") + queryCmd.Flags().BoolVarP(&dbQueryListAll, "list-all", "a", false, "List all columns") + + // Add mutually exclusive flags to query and raw-query + // Cannot use query and raw-query at the same time + queryCmd.MarkFlagsMutuallyExclusive("user-query", "raw-query") + // Raw query does not require a table + queryCmd.MarkFlagsMutuallyExclusive("user-query", "table") + // List all columns does not require a query or raw-query + queryCmd.MarkFlagsMutuallyExclusive("raw-query", "list-all") +} + +var ( + dbQueryTableName string + dbQueryLimitRows int + dbQueryNotNull string + dbQueryColumns string + dbQueryUserQuery string + dbQueryRawQuery string + dbQueryListAll bool - // Query command queryCmd = &cobra.Command{ Use: "query", - Short: "Query the Dehashed API", - Long: `Query the Dehashed API for emails, usernames, passwords, hashes, IP addresses, and names.`, + Short: "Query the database", + Long: `Query the database for various information.`, Run: func(cmd *cobra.Command, args []string) { - key := getStoredApiKey() - - // Validate credentials - if key == "" { - fmt.Println("API key is required. Set the key with the \"set-key\" command. [dehasher set-key ]") - return + // If Raw Query is set, execute it and return + if dbQueryRawQuery != "" { + fmt.Println("[*] Executing Raw Query...") + rawDBQuery() + os.Exit(1) } - // Create new QueryOptions - queryOptions := sqlite.NewQueryOptions( - maxRecords, - maxRequests, - startingPage, - outputFormat, - outputFile, - usernameQuery, - emailQuery, - ipQuery, - passwordQuery, - hashQuery, - nameQuery, - domainQuery, - vinQuery, - licensePlateQuery, - addressQuery, - phoneQuery, - socialQuery, - cryptoCurrencyAddressQuery, - regexMatch, - wildcardMatch, - printBalance, - credsOnly, - ) - - // Create new Dehasher - dehasher := query.NewDehasher(queryOptions) - dehasher.SetClientCredentials( - key, - ) - - // Start querying - dehasher.Start() - fmt.Println("\n[*] Completing Process") - - sqlite.StoreQueryOptions(queryOptions) + // Determine which table to query based on the tableTypeDBQuery parameter + table := sqlite.GetTable(dbQueryTableName) + if table == sqlite.UnknownTable { + fmt.Printf("Error: Unknown table type '%s'.\n", dbQueryTableName) + cmd.Help() + return + } + fmt.Println("[*] Querying Database...") + tableQuery(table) }, } ) -func init() { - // Add query command to root command - rootCmd.AddCommand(queryCmd) +func tableQuery(table sqlite.Table) { - // Add flags specific to query command - queryCmd.Flags().IntVarP(&maxRecords, "max-records", "m", 30000, "Maximum amount of records to return") - queryCmd.Flags().IntVarP(&maxRequests, "max-requests", "r", -1, "Maximum number of requests to make") - queryCmd.Flags().IntVarP(&startingPage, "starting-page", "s", 1, "Starting page for requests") - queryCmd.Flags().BoolVarP(&printBalance, "print-balance", "b", false, "Print remaining balance after requests") - queryCmd.Flags().BoolVarP(®exMatch, "regex-match", "R", false, "Use regex matching on query fields") - queryCmd.Flags().BoolVarP(&wildcardMatch, "wildcard-match", "W", false, "Use wildcard matching on query fields (Use ? to replace a single character, and * for multiple characters)") - queryCmd.Flags().BoolVarP(&credsOnly, "creds-only", "C", false, "Return credentials only") - queryCmd.Flags().StringVarP(&outputFormat, "format", "f", "json", "Output format (json, yaml, xml, txt)") - queryCmd.Flags().StringVarP(&outputFile, "output", "o", "query", "File to output results to including extension") - queryCmd.Flags().StringVarP(&usernameQuery, "username", "U", "", "Username query") - queryCmd.Flags().StringVarP(&emailQuery, "email-query", "E", "", "Email query") - queryCmd.Flags().StringVarP(&ipQuery, "ip", "I", "", "IP address query") - queryCmd.Flags().StringVarP(&domainQuery, "domain", "D", "", "Domain query") - queryCmd.Flags().StringVarP(&passwordQuery, "password", "P", "", "Password query") - queryCmd.Flags().StringVarP(&vinQuery, "vin", "V", "", "VIN query") - queryCmd.Flags().StringVarP(&licensePlateQuery, "license", "L", "", "License plate query") - queryCmd.Flags().StringVarP(&addressQuery, "address", "A", "", "Address query") - queryCmd.Flags().StringVarP(&phoneQuery, "phone", "M", "", "Phone query") - queryCmd.Flags().StringVarP(&socialQuery, "social", "S", "", "Social query") - queryCmd.Flags().StringVarP(&cryptoCurrencyAddressQuery, "crypto", "B", "", "Crypto currency address query") - queryCmd.Flags().StringVarP(&hashQuery, "hash", "Q", "", "Hashed password query") - queryCmd.Flags().StringVarP(&nameQuery, "name", "N", "", "Name query") + // Get the columns to query + columns := []string{"*"} + if dbQueryColumns != "" { + columns = strings.Split(dbQueryColumns, ",") + } - // Add mutually exclusive flags to exact match and regex match - queryCmd.MarkFlagsMutuallyExclusive("regex-match", "wildcard-match") + // 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() + + // 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] + } + 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 scanning row from 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 { + // Check if the value is a slice or array + switch v := value.(type) { + case []string: + // Join string slices with commas, no brackets + rowStrings[i] = strings.Join(v, ", ") + case []interface{}: + // Convert interface slice to strings and join + strSlice := make([]string, len(v)) + for j, item := range v { + if item == nil { + strSlice[j] = "" + } else { + strSlice[j] = fmt.Sprintf("%v", item) + } + } + rowStrings[i] = strings.Join(strSlice, ", ") + case string: + // Handle JSON strings that might be arrays + if strings.HasPrefix(v, "[") && strings.HasSuffix(v, "]") { + // Try to unmarshal JSON array + var strArray []string + if err := json.Unmarshal([]byte(v), &strArray); err == nil { + rowStrings[i] = strings.Join(strArray, ", ") + } else { + rowStrings[i] = v + } + } else { + rowStrings[i] = v + } + default: + rowStrings[i] = fmt.Sprintf("%v", v) + } + } + } + tableRows = append(tableRows, rowStrings) + } + + // Display the table + pretty.Table(headers, tableRows) } -// Helper functions to get stored API credentials -func getStoredApiKey() string { - return badger.GetKey() +func rawDBQuery() { + db := sqlite.GetDB() + rows, err := db.Raw(dbQueryRawQuery).Rows() + if err != nil { + 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() + + columns, err := rows.Columns() + if err != nil { + zap.L().Error("raw_query", + zap.String("message", "failed to get columns from raw query"), + zap.Error(err), + ) + fmt.Printf("[!] Error getting columns from raw query: %v\n", err) + } + + // 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 { + // Check if the value is a slice or array + switch v := value.(type) { + case []string: + // Join string slices with commas, no brackets + rowStrings[i] = strings.Join(v, ", ") + case []interface{}: + // Convert interface slice to strings and join + strSlice := make([]string, len(v)) + for j, item := range v { + if item == nil { + strSlice[j] = "" + } else { + strSlice[j] = fmt.Sprintf("%v", item) + } + } + rowStrings[i] = strings.Join(strSlice, ", ") + case string: + // Handle JSON strings that might be arrays + if strings.HasPrefix(v, "[") && strings.HasSuffix(v, "]") { + // Try to unmarshal JSON array + var strArray []string + if err := json.Unmarshal([]byte(v), &strArray); err == nil { + rowStrings[i] = strings.Join(strArray, ", ") + } else { + rowStrings[i] = v + } + } else { + rowStrings[i] = v + } + default: + rowStrings[i] = fmt.Sprintf("%v", v) + } + } + } + tableRows = append(tableRows, rowStrings) + } + + // Display the table + pretty.Table(headers, tableRows) } diff --git a/cmd/root.go b/cmd/root.go index 8b57bfa..0eab842 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -11,7 +11,6 @@ import ( var ( // Global Flags - useLocalDatabase bool // rootCmd is the base command for the CLI. rootCmd = &cobra.Command{ @@ -20,15 +19,15 @@ var ( Long: fmt.Sprintf( "%s\n%s", ` - ______ _______ _______ _______ _______ _______ + ______ _______ _______ _______ _______ _______ ( __ \ ( ____ \|\ /|( ___ )( ____ \|\ /|( ____ \( ____ ) | ( \ )| ( \/| ) ( || ( ) || ( \/| ) ( || ( \/| ( )| | | ) || (__ | (___) || (___) || (_____ | (___) || (__ | (____)| | | | || __) | ___ || ___ |(_____ )| ___ || __) | __) -| | ) || ( | ( ) || ( ) | ) || ( ) || ( | (\ ( +| | ) || ( | ( ) || ( ) | ) || ( ) || ( | (\ ( | (__/ )| (____/\| ) ( || ) ( |/\____) || ) ( || (____/\| ) \ \__ (______/ (_______/|/ \||/ \|\_______)|/ \|(_______/|/ \__/ - An Ar1ste1a Project + An Ar1ste1a Project `, `––•–√\/––√\/––•––––•–√\/––√\/––•––––•–√\/––√\/––•––√\/––•––––•–√\/––√\/––•–– Dehasher can query the query API for: @@ -59,12 +58,15 @@ func Execute() { } func init() { - // Attempt to retreive the useLocalDB - useLocalDatabase = badger.GetUseLocalDB() + //// Attempt to retrieve the useLocalDB + //useLocalDatabase := badger.GetUseLocalDB() // Hide the default help command rootCmd.CompletionOptions.HiddenDefaultCmd = true + //// Add global flags + //rootCmd.PersistentFlags().BoolVar(&useLocalDatabase, "local-db", useLocalDatabase, "Use local database in current directory instead of default path") + // Add subcommands rootCmd.AddCommand(setKeyCmd) rootCmd.AddCommand(setLocalDb) @@ -88,10 +90,12 @@ var setKeyCmd = &cobra.Command{ } var setLocalDb = &cobra.Command{ - Use: "set-local-db [true|false]", - Short: "Set dehasher to use a local database path instead of the default (must be unset to use default)", + Use: "local-db [true|false]", + Short: "Set dehasher to use a local database path instead of the default path", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { + var useLocalDatabase bool + useLocal := strings.ToLower(args[0]) if useLocal == "true" { diff --git a/dehasher.go b/dehasher.go index d9900fb..3478e92 100644 --- a/dehasher.go +++ b/dehasher.go @@ -24,7 +24,7 @@ func init() { basePath = filepath.Join(os.Getenv("HOME"), ".local", "share", "Dehasher") logPath = filepath.Join(basePath, "logs") storePath = filepath.Join(basePath, "keystore") - dbPath = filepath.Join(basePath, "db") + // dbPath will be set in main() after badger is initialized } func createDirectories() { @@ -74,6 +74,22 @@ func main() { zap.L().Info("creating_directories") createDirectories() + zap.L().Info("starting_badger") + db := badger.Start(storePath) + defer db.Close() + + // Set database path based on useLocalDatabase flag + useLocalDB := badger.GetUseLocalDB() + if useLocalDB { + // Use local database in current directory + dbPath = "./dehasher.sqlite" + zap.L().Info("Using local database", zap.String("path", dbPath)) + } else { + // Use default database path + dbPath = filepath.Join(basePath, "db") + zap.L().Info("Using default database path", zap.String("path", dbPath)) + } + zap.L().Info("initializing_database") _, err := sqlite.InitDB(dbPath) if err != nil { @@ -85,10 +101,6 @@ func main() { os.Exit(1) } - zap.L().Info("starting_badger") - db := badger.Start(storePath) - defer db.Close() - zap.L().Info("executing_command") cmd.Execute() } diff --git a/internal/sqlite/gorm.go b/internal/sqlite/gorm.go index c8a6240..d4cc7ca 100644 --- a/internal/sqlite/gorm.go +++ b/internal/sqlite/gorm.go @@ -15,17 +15,34 @@ import ( var DB *gorm.DB // InitDB initializes the database connection -func InitDB(dbDir string) (*gorm.DB, error) { - zap.L().Info("Initializing database") +func InitDB(dbPath string) (*gorm.DB, error) { + zap.L().Info("Initializing database", zap.String("path", dbPath)) - // Create directory if it doesn't exist - if err := os.MkdirAll(dbDir, 0755); err != nil { - zap.L().Error("Failed to create database directory", zap.Error(err)) - return nil, fmt.Errorf("failed to create database directory: %w", err) + // Check if the path is a file or directory + fileInfo, err := os.Stat(dbPath) + var finalDbPath string + + // If path doesn't exist or is a directory + if os.IsNotExist(err) || (err == nil && fileInfo.IsDir()) { + // Treat as directory path + if err := os.MkdirAll(dbPath, 0755); err != nil { + zap.L().Error("Failed to create database directory", zap.Error(err)) + return nil, fmt.Errorf("failed to create database directory: %w", err) + } + finalDbPath = filepath.Join(dbPath, "dehashed.sqlite") + } else { + // Treat as file path + // Ensure the directory exists + dir := filepath.Dir(dbPath) + if err := os.MkdirAll(dir, 0755); err != nil { + zap.L().Error("Failed to create parent directory for database", zap.Error(err)) + return nil, fmt.Errorf("failed to create parent directory for database: %w", err) + } + finalDbPath = dbPath } - dbPath := filepath.Join(dbDir, "dehashed.sqlite") - db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{ + zap.L().Info("Opening database", zap.String("finalPath", finalDbPath)) + db, err := gorm.Open(sqlite.Open(finalDbPath), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) if err != nil { diff --git a/cmd/helpers.go b/internal/sqlite/tables.go similarity index 73% rename from cmd/helpers.go rename to internal/sqlite/tables.go index 6b7075c..aea7a50 100644 --- a/cmd/helpers.go +++ b/internal/sqlite/tables.go @@ -1,9 +1,6 @@ -package cmd +package sqlite -import ( - "dehasher/internal/sqlite" - "strings" -) +import "strings" type Table int64 @@ -39,17 +36,17 @@ func GetTable(userInput string) Table { func (t Table) Object() interface{} { switch t { case ResultsTable: - return sqlite.Result{} + return Result{} case RunsTable: - return sqlite.QueryOptions{} + return QueryOptions{} case CredsTable: - return sqlite.Creds{} + return Creds{} case WhoIsTable: - return sqlite.WhoisRecord{} + return WhoisRecord{} case SubdomainsTable: - return sqlite.SubdomainRecord{} + return SubdomainRecord{} case HistoryTable: - return sqlite.HistoryRecord{} + return HistoryRecord{} default: return nil }