first commit

This commit is contained in:
Ar1ste1a
2025-05-14 22:00:38 -04:00
commit a4dffe61bf
27 changed files with 4742 additions and 0 deletions
+174
View File
@@ -0,0 +1,174 @@
package badger
import (
"crypto/sha256"
"github.com/dgraph-io/badger/v4"
"go.uber.org/zap"
"log"
"os"
"os/user"
"path/filepath"
"runtime"
"strings"
"sync"
)
var (
encryptionKey []byte // must be 32 bytes
db *badger.DB
rootDir string
once sync.Once
)
func GetHardwareEntropy() []byte {
// Get hostname
hostname, err := os.Hostname()
if err != nil {
hostname = "unknown-host"
log.Printf("Error getting hostname: %v", err)
}
// Get username
currentUser, err := user.Current()
username := "unknown-user"
if err == nil && currentUser != nil {
username = currentUser.Username
}
// Get OS and architecture info
osInfo := runtime.GOOS + "-" + runtime.GOARCH
// Combine all information for a unique but consistent fingerprint
fingerprint := strings.Join([]string{
hostname,
username,
osInfo,
// You could add a static salt here for additional security
"Dehasher-static-salt-value",
}, ":")
// Hash the fingerprint to get a 32-byte key
sum := sha256.Sum256([]byte(fingerprint))
return sum[:]
}
func Start(dirPath string) *badger.DB {
var err error
zap.L().Info("Starting Badger DB", zap.String("directory", dirPath))
zap.L().Info("Badger DB Directory Path", zap.String("directory", dirPath))
once.Do(func() {
if !strings.HasSuffix(dirPath, "db") {
dirPath = filepath.Join(dirPath, "db")
}
rootDir = dirPath
encryptionKey = GetHardwareEntropy()
if err != nil {
zap.L().Fatal("get_encryption_key",
zap.String("message", "failed to get encryption key"),
zap.Error(err),
)
}
badgerDB := filepath.Join(rootDir, "badger.db")
opts := badger.DefaultOptions(badgerDB).
WithEncryptionKey(encryptionKey).
WithIndexCacheSize(10 << 20). // 10MB
WithLoggingLevel(badger.ERROR)
db, err = badger.Open(opts)
if err != nil {
zap.L().Fatal("new_badger_db",
zap.String("message", "failed to open badger database"),
zap.Error(err),
)
}
})
return db
}
func Close() {
err := db.Close()
if err != nil {
zap.L().Fatal("new_badger_db",
zap.String("message", "failed to close badger database"),
zap.Error(err),
)
}
}
func GetKey() string {
var apiKey string
err := db.View(func(txn *badger.Txn) error {
item, err := txn.Get([]byte("cfg:api_key"))
if err != nil {
return err // could be ErrKeyNotFound
}
return item.Value(func(val []byte) error {
apiKey = string(val)
return nil
})
})
if err != nil {
zap.L().Error("get_api_key",
zap.String("message", "failed to get api_key"),
zap.Error(err),
)
}
return apiKey
}
func GetEmail() string {
var email string
err := db.View(func(txn *badger.Txn) error {
item, err := txn.Get([]byte("cfg:email"))
if err != nil {
return err // could be ErrKeyNotFound
}
return item.Value(func(val []byte) error {
email = string(val)
return nil
})
})
if err != nil {
zap.L().Error("get_email",
zap.String("message", "failed to get email"),
zap.Error(err),
)
}
return email
}
func StoreKey(apiKey string) error {
err := db.Update(func(txn *badger.Txn) error {
return txn.Set([]byte("cfg:api_key"), []byte(apiKey))
})
if err != nil {
zap.L().Error("set_api_key",
zap.String("message", "failed to set api_key"),
zap.Error(err),
)
}
return err
}
func StoreEmail(email string) error {
err := db.Update(func(txn *badger.Txn) error {
return txn.Set([]byte("cfg:email"), []byte(email))
})
if err != nil {
zap.L().Error("set_email",
zap.String("message", "failed to set email"),
zap.Error(err),
)
}
return err
}
+77
View File
@@ -0,0 +1,77 @@
package export
import (
"dehasher/internal/files"
"dehasher/internal/sqlite"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"gopkg.in/yaml.v3"
"io/ioutil"
"os"
"strings"
)
func WriteCredsToFile(creds []sqlite.Creds, outputFile string, fileType files.FileType) error {
var data []byte
var err error
switch fileType {
case files.JSON:
data, err = json.MarshalIndent(creds, "", " ")
case files.XML:
data, err = xml.MarshalIndent(creds, "", " ")
case files.YAML:
data, err = yaml.Marshal(creds)
case files.TEXT:
var outStrings []string
for _, c := range creds {
outStrings = append(outStrings, c.ToString()+"\n")
}
data = []byte(strings.Join(outStrings, ""))
default:
return errors.New("unsupported file type")
}
if err != nil {
return err
}
filePath := fmt.Sprintf("%s.%s", outputFile, fileType.String())
return os.WriteFile(filePath, data, 0644)
}
func WriteToFile(results sqlite.DehashedResults, outputFile string, fileType files.FileType) error {
var data []byte
var err error
result := results.Results
switch fileType {
case files.JSON:
data, err = json.MarshalIndent(result, "", " ")
case files.XML:
data, err = xml.MarshalIndent(result, "", " ")
case files.YAML:
data, err = yaml.Marshal(result)
case files.TEXT:
var outStrings []string
for _, r := range result {
out := fmt.Sprintf(
"Id: %s\nEmail: %s\nIpAddress: %s\nUsername: %s\nPassword: %s\nHashedPassword: %s\nHashType: %s\nName: %s\nVin: %s\nAddress: %s\nPhone: %s\nDatabaseName: %s\n\n",
r.DehashedId, r.Email, r.IpAddress, r.Username, r.Password, r.HashedPassword, r.HashType, r.Name, r.Vin, r.Address, r.Phone, r.DatabaseName)
outStrings = append(outStrings, out)
}
data = []byte(strings.Join(outStrings, ""))
default:
return errors.New("unsupported file type")
}
if err != nil {
return err
}
filePath := fmt.Sprintf("%s.%s", outputFile, fileType)
return ioutil.WriteFile(filePath, data, 0644)
}
+44
View File
@@ -0,0 +1,44 @@
package files
type FileType int32
const (
JSON FileType = iota
XML
YAML
TEXT
)
func GetFileType(filetype string) FileType {
switch filetype {
case "json":
return JSON
case "xml":
return XML
case "yaml":
return YAML
case "txt":
return TEXT
default:
return JSON
}
}
func (ft FileType) String() string {
switch ft {
case JSON:
return "json"
case XML:
return "xml"
case YAML:
return "yaml"
case TEXT:
return "txt"
default:
return "json"
}
}
func (ft FileType) Extension() string {
return "." + ft.String()
}
+48
View File
@@ -0,0 +1,48 @@
package pretty
import (
"fmt"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/table"
)
var (
rows = [][]string{
{"Chinese", "您好", "你好"},
{"Japanese", "こんにちは", "やあ"},
{"Arabic", "أهلين", "أهلا"},
{"Russian", "Здравствуйте", "Привет"},
{"Spanish", "Hola", "¿Qué tal?"},
}
purple = lipgloss.Color("99")
gray = lipgloss.Color("245")
lightGray = lipgloss.Color("241")
headerStyle = lipgloss.NewStyle().Foreground(purple).Bold(true).Align(lipgloss.Center)
cellStyle = lipgloss.NewStyle().Padding(0, 1)
oddRowStyle = cellStyle.Foreground(gray)
evenRowStyle = cellStyle.Foreground(lightGray)
)
func Table(headers []string, rows [][]string) {
t := table.New().
Border(lipgloss.NormalBorder()).
BorderStyle(lipgloss.NewStyle().Foreground(purple)).
StyleFunc(func(row, col int) lipgloss.Style {
switch {
case row == table.HeaderRow:
return headerStyle
case row%2 == 0:
return evenRowStyle
default:
return oddRowStyle
}
}).
Headers(headers...).
Rows(rows...)
// You can also add tables row-by-row
//t.Row("English", "You look absolutely fabulous.", "How's it going?")
fmt.Println(t)
}
+111
View File
@@ -0,0 +1,111 @@
package query
import (
"dehasher/internal/sqlite"
"fmt"
"log"
"net/http"
"net/url"
"os"
"strings"
)
type DehashedClient struct {
key string
email string
results []sqlite.Result
client *http.Client
query string
params string
printBal bool
total int
balance int
}
var baseUrl = "https://api.dehashed.com/v2/search"
func NewDehashedClient(key, email string, printBal bool) *DehashedClient {
return &DehashedClient{key: key, email: email, results: make([]sqlite.Result, 0), client: &http.Client{}, printBal: printBal}
}
func (dc *DehashedClient) getKey() string {
return dc.key
}
func (dc *DehashedClient) getEmail() string {
return dc.email
}
func (dc *DehashedClient) GetResults() sqlite.DehashedResults {
return sqlite.DehashedResults{Results: dc.results}
}
func (dc *DehashedClient) buildQuery(params map[string]string) {
urlParams := url.Values{}
urlString := baseUrl
if len(params) > 0 {
urlString += "?query="
for k, v := range params {
if len(v) > 0 {
urlParams.Add(k, v)
}
}
}
tmp, _ := url.QueryUnescape(urlParams.Encode())
tmp2 := strings.Replace(tmp, "=", ":", -1)
dc.params = tmp2
urlString += dc.params
dc.query = urlString
}
func (dc *DehashedClient) setResults(results int) {
dc.query = fmt.Sprintf("%s?query=%s&size=%d", baseUrl, dc.params, results)
}
func (dc *DehashedClient) setPage(page int) {
dc.query = fmt.Sprintf("%s&nextPage=%d", dc.query, page)
}
func (dc *DehashedClient) Do() int {
fmt.Printf("\n\t[*] Performing Request...")
req, err := http.NewRequest("GET", dc.query, nil)
if err != nil {
fmt.Printf("[!] Error constructing request: %v", err)
os.Exit(-1)
}
dc.setAuth(req)
req.Header.Add("Dehashed-Api-Key", dc.getKey())
req.Header.Add("Accept", "application/json")
resp, err := dc.client.Do(req)
if err != nil {
fmt.Printf("[!] Error performing request: %s\n%v", dc.query, err)
os.Exit(-1)
}
if resp.StatusCode != 200 {
dhErr := GetDehashedError(resp.StatusCode)
fmt.Println()
log.Fatal(dhErr.Error())
}
entries, balance, total := sqlite.NewDehashedResults(resp.Body)
dc.results = append(dc.results, entries...)
dc.balance = balance
dc.total += total
if dc.printBal {
fmt.Printf("\n\t\t[*] Balance Remaining: %d", balance)
}
return total
}
func (dc *DehashedClient) setAuth(r *http.Request) {
r.SetBasicAuth(dc.email, dc.key)
}
func (dc *DehashedClient) GetDomains() int {
return dc.balance
}
+193
View File
@@ -0,0 +1,193 @@
package query
import (
"bytes"
"crypto/sha256"
"dehasher/internal/sqlite"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"go.uber.org/zap"
"io"
"net/http"
"strings"
)
type DehashedParameter string
const (
Username DehashedParameter = "username"
Email DehashedParameter = "email"
Password DehashedParameter = "password"
HashedPassword DehashedParameter = "hashed_password"
Name DehashedParameter = "name"
IpAddress DehashedParameter = "ip_address"
Domain DehashedParameter = "domain"
Vin DehashedParameter = "vin"
LicensePlate DehashedParameter = "license_plate"
Address DehashedParameter = "address"
Phone DehashedParameter = "phone"
Social DehashedParameter = "social"
CryptoAddress DehashedParameter = "cryptocurrency_address"
)
func (dp DehashedParameter) GetArgumentString(arg string) string {
return fmt.Sprintf("%s:%s", string(dp), arg)
}
type DehashedSearchRequest struct {
ForcePlaintext bool `json:"-"`
Page int `json:"page"`
Query string `json:"query"`
Size int `json:"size"`
Wildcard bool `json:"wildcard"`
Regex bool `json:"regex"`
DeDupe bool `json:"de_dupe"`
}
func NewDehashedSearchRequest(page, size int, wildcard, regex, forcePlaintext bool) *DehashedSearchRequest {
return &DehashedSearchRequest{Page: page, Query: "", Size: size, Wildcard: wildcard, Regex: regex, DeDupe: true, ForcePlaintext: forcePlaintext}
}
func (dsr *DehashedSearchRequest) buildQuery(query string, param DehashedParameter) {
if len(dsr.Query) > 0 {
dsr.Query = fmt.Sprintf("%s&%s", strings.TrimSpace(dsr.Query), strings.TrimSpace(query))
} else {
dsr.Query = query
}
}
func (dsr *DehashedSearchRequest) AddUsernameQuery(query string) {
query = strings.TrimSpace(query)
dsr.buildQuery(Username.GetArgumentString(query), Username)
}
func (dsr *DehashedSearchRequest) AddEmailQuery(query string) {
query = strings.TrimSpace(query)
dsr.buildQuery(Email.GetArgumentString(query), Email)
}
func (dsr *DehashedSearchRequest) AddIpAddressQuery(query string) {
query = strings.TrimSpace(query)
dsr.buildQuery(IpAddress.GetArgumentString(query), IpAddress)
}
func (dsr *DehashedSearchRequest) AddDomainQuery(query string) {
query = strings.TrimSpace(query)
dsr.buildQuery(Domain.GetArgumentString(query), Domain)
}
func (dsr *DehashedSearchRequest) AddPasswordQuery(query string) {
if dsr.ForcePlaintext {
dsr.buildQuery(Password.GetArgumentString(query), Password)
return
}
hash := sha256.Sum256([]byte(query))
query = hex.EncodeToString(hash[:])
dsr.AddHashedPasswordQuery(query)
}
func (dsr *DehashedSearchRequest) AddVinQuery(query string) {
query = strings.TrimSpace(query)
dsr.buildQuery(Vin.GetArgumentString(query), Vin)
}
func (dsr *DehashedSearchRequest) AddLicensePlateQuery(query string) {
query = strings.TrimSpace(query)
dsr.buildQuery(LicensePlate.GetArgumentString(query), LicensePlate)
}
func (dsr *DehashedSearchRequest) AddAddressQuery(query string) {
query = strings.TrimSpace(query)
dsr.buildQuery(Address.GetArgumentString(query), Address)
}
func (dsr *DehashedSearchRequest) AddPhoneQuery(query string) {
query = strings.TrimSpace(query)
dsr.buildQuery(Phone.GetArgumentString(query), Phone)
}
func (dsr *DehashedSearchRequest) AddSocialQuery(query string) {
query = strings.TrimSpace(query)
dsr.buildQuery(Social.GetArgumentString(query), Social)
}
func (dsr *DehashedSearchRequest) AddCryptoAddressQuery(query string) {
query = strings.TrimSpace(query)
dsr.buildQuery(CryptoAddress.GetArgumentString(query), CryptoAddress)
}
func (dsr *DehashedSearchRequest) AddHashedPasswordQuery(query string) {
dsr.buildQuery(HashedPassword.GetArgumentString(query), HashedPassword)
}
func (dsr *DehashedSearchRequest) AddNameQuery(query string) {
query = strings.TrimSpace(query)
dsr.buildQuery(Name.GetArgumentString(query), Name)
}
type DehashedClientV2 struct {
apiKey string
results []sqlite.Result
}
func NewDehashedClientV2(apiKey string) *DehashedClientV2 {
return &DehashedClientV2{apiKey: apiKey}
}
func (dcv2 *DehashedClientV2) Search(searchRequest DehashedSearchRequest) (int, error) {
reqBody, _ := json.Marshal(searchRequest)
req, err := http.NewRequest("POST", "https://api.dehashed.com/v2/search", bytes.NewReader(reqBody))
if err != nil {
return -1, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Dehashed-Api-Key", dcv2.apiKey)
res, err := http.DefaultClient.Do(req)
if res != nil {
defer res.Body.Close()
}
if err != nil {
zap.L().Error("v2_search",
zap.String("message", "failed to perform request"),
zap.Error(err),
)
return -1, err
}
if res == nil {
zap.L().Error("v2_search",
zap.String("message", "response was nil"),
)
return -1, errors.New("response was nil")
}
b, err := io.ReadAll(res.Body)
if err != nil {
zap.L().Error("v2_search",
zap.String("message", "failed to read response body"),
zap.Error(err),
)
return -1, err
}
var responseResults sqlite.DehashedResponse
err = json.Unmarshal(b, &responseResults)
if err != nil {
zap.L().Error("v2_search",
zap.String("message", "failed to unmarshal response body"),
zap.Error(err),
)
return -1, err
}
dcv2.results = append(dcv2.results, responseResults.Entries...)
return responseResults.TotalResults, nil
}
func (dcv2 *DehashedClientV2) GetResults() sqlite.DehashedResults {
return sqlite.DehashedResults{Results: dcv2.results}
}
func (dcv2 *DehashedClientV2) GetTotalResults() int {
return len(dcv2.results)
}
+206
View File
@@ -0,0 +1,206 @@
package query
import (
"dehasher/internal/export"
"dehasher/internal/sqlite"
"encoding/json"
"fmt"
"go.uber.org/zap"
"os"
)
// Dehasher is a struct for querying the Dehashed API
type Dehasher struct {
options sqlite.QueryOptions
nextPage int
request *DehashedSearchRequest
client *DehashedClientV2
}
// NewDehasher creates a new Dehasher
func NewDehasher(options *sqlite.QueryOptions) *Dehasher {
dh := &Dehasher{
options: *options,
nextPage: options.StartingPage + 1,
}
dh.setQueries()
dh.request = NewDehashedSearchRequest(dh.options.StartingPage, dh.options.MaxRecords, dh.options.WildcardMatch, dh.options.RegexMatch, false)
dh.buildRequest()
return dh
}
// SetClientCredentials sets the client credentials for the dehasher
func (dh *Dehasher) SetClientCredentials(key string) {
dh.client = NewDehashedClientV2(key)
}
func (dh *Dehasher) getNextPage() int {
nextPage := dh.nextPage
dh.nextPage += 1
return nextPage
}
// setQueries sets the number of queries to make based on the number of records and requests
func (dh *Dehasher) setQueries() {
var numQueries int
switch {
case dh.options.MaxRequests == 0:
zap.L().Error("max requests cannot be zero")
fmt.Println("[!] Max Requests cannot be zero")
os.Exit(1)
case dh.options.MaxRecords <= 10000 || dh.options.MaxRequests == 1:
numQueries = 1
if dh.options.MaxRecords > 10000 {
dh.options.MaxRecords = 10000
}
zap.L().Info("max requests set to 1", zap.Int("max_records", dh.options.MaxRecords))
case dh.options.MaxRequests < 0 && dh.options.MaxRecords > 20000:
numQueries = 3
dh.options.MaxRecords = 10000
zap.L().Info("max requests set to 3", zap.Int("max_records", dh.options.MaxRecords))
case dh.options.MaxRequests < 0 && dh.options.MaxRecords > 10000:
numQueries = 2
dh.options.MaxRecords = 10000
zap.L().Info("max requests set to 2", zap.Int("max_records", dh.options.MaxRecords))
case dh.options.MaxRecords < 0 && dh.options.MaxRecords < 10000:
numQueries = 1
zap.L().Info("max requests set to 1", zap.Int("max_records", dh.options.MaxRecords))
case dh.options.MaxRequests == 2 && dh.options.MaxRecords > 20000:
numQueries = 2
dh.options.MaxRecords = 10000
zap.L().Info("max requests set to 2", zap.Int("max_records", dh.options.MaxRecords))
case dh.options.MaxRequests == 2 && dh.options.MaxRecords <= 10000:
numQueries = 1
zap.L().Info("max requests set to 1", zap.Int("max_records", dh.options.MaxRecords))
default:
numQueries = 3
dh.options.MaxRecords = 10000
zap.L().Info("max requests set to 3", zap.Int("max_records", dh.options.MaxRecords))
}
dh.options.MaxRequests = numQueries
fmt.Printf("Making %d Requests for %d Records (%d Total)\n", dh.options.MaxRequests, dh.options.MaxRecords, dh.options.MaxRequests*dh.options.MaxRecords)
}
// Start starts the querying process
func (dh *Dehasher) Start() {
fmt.Println("[*] Querying Dehashed API...")
for i := 0; i < dh.options.MaxRequests; i++ {
fmt.Printf("\n\t[*] Performing Request...")
count, err := dh.client.Search(*dh.request)
if err != nil {
fmt.Printf("[!] Error performing request: %v", err)
os.Exit(-1)
}
if count < dh.options.MaxRecords {
fmt.Printf("\n\t\t[+] Retrieved %d Records", count)
fmt.Printf("\n[-] Not Enough Entries, ending queries")
break
} else {
fmt.Printf("\n\t\t[+] Retrieved %d Records", dh.options.MaxRecords)
}
dh.request.Page = dh.getNextPage()
}
dh.parseResults()
}
// buildRequest constructs the query map
func (dh *Dehasher) buildRequest() {
if len(dh.options.UsernameQuery) > 0 {
dh.request.AddUsernameQuery(dh.options.UsernameQuery)
}
if len(dh.options.EmailQuery) > 0 {
dh.request.AddEmailQuery(dh.options.EmailQuery)
}
if len(dh.options.IpQuery) > 0 {
dh.request.AddIpAddressQuery(dh.options.IpQuery)
}
if len(dh.options.HashQuery) > 0 {
dh.request.AddHashedPasswordQuery(dh.options.HashQuery)
}
if len(dh.options.PassQuery) > 0 {
dh.request.AddPasswordQuery(dh.options.PassQuery)
}
if len(dh.options.NameQuery) > 0 {
dh.request.AddNameQuery(dh.options.NameQuery)
}
if len(dh.options.DomainQuery) > 0 {
dh.request.AddDomainQuery(dh.options.DomainQuery)
}
if len(dh.options.VinQuery) > 0 {
dh.request.AddVinQuery(dh.options.VinQuery)
}
if len(dh.options.LicensePlateQuery) > 0 {
dh.request.AddLicensePlateQuery(dh.options.LicensePlateQuery)
}
if len(dh.options.AddressQuery) > 0 {
dh.request.AddAddressQuery(dh.options.AddressQuery)
}
if len(dh.options.PhoneQuery) > 0 {
dh.request.AddPhoneQuery(dh.options.PhoneQuery)
}
if len(dh.options.SocialQuery) > 0 {
dh.request.AddSocialQuery(dh.options.SocialQuery)
}
if len(dh.options.CryptoAddressQuery) > 0 {
dh.request.AddCryptoAddressQuery(dh.options.CryptoAddressQuery)
}
}
// parseResults parses the results and writes them to a file
func (dh *Dehasher) parseResults() {
var data []byte
zap.L().Info("extracting_credentials")
results := dh.client.GetResults()
creds := results.ExtractCredentials()
fmt.Printf("\n\t[*] Discovered %d Credentials", len(creds))
err := sqlite.StoreCreds(creds)
if err != nil {
zap.L().Error("store_creds",
zap.String("message", "failed to store creds"),
zap.Error(err),
)
}
zap.L().Info("creds_stored", zap.Int("count", len(creds)))
zap.L().Info("storing_results")
err = sqlite.StoreResults(results)
if err != nil {
zap.L().Error("store_results",
zap.String("message", "failed to store results"),
zap.Error(err),
)
}
zap.L().Info("results_stored", zap.Int("count", len(results.Results)))
if len(results.Results) > 0 {
fmt.Printf("\n\t[*] Writing entries to file: %s.%s", dh.options.OutputFile, dh.options.OutputFormat.String())
if !dh.options.CredsOnly {
err := export.WriteToFile(results, dh.options.OutputFile, dh.options.OutputFormat)
if err != nil {
fmt.Printf("\n[!] Error Writing to file: %v\n\tOutputting to terminal.", err)
data, err = json.MarshalIndent(results, "", " ")
fmt.Println(string(data))
os.Exit(0)
} else {
fmt.Println("\n\t\t[*] Success\n")
}
} else {
creds := results.ExtractCredentials()
err := export.WriteCredsToFile(creds, dh.options.OutputFile, dh.options.OutputFormat)
if err != nil {
fmt.Printf("\n[!] Error Writing to file: %v\n\tOutputting to terminal.", err)
data, err = json.MarshalIndent(creds, "", " ")
fmt.Println(string(data))
os.Exit(0)
} else {
fmt.Println("\n\t\t[*] Success\n")
}
}
}
}
+33
View File
@@ -0,0 +1,33 @@
package query
type DehashError struct {
Message string
Code int
}
type DehashResponseError struct {
HttpResponse int `json:"HTTP Response Code"`
}
func (de *DehashError) Error() string {
return de.Message
}
func GetDehashedError(c int) DehashError {
switch c {
case 400:
return DehashError{Code: 400, Message: "There is an issue with authentication. Please check your API key and email. If you haven't, refresh your API Key "}
case 401:
return DehashError{Code: 401, Message: "You need a search subscription and API credits to use the API, please purchase a search subscription and add credits to your account."}
case 403:
return DehashError{Code: 403, Message: "Insufficient Credits"}
case 404:
return DehashError{Code: 404, Message: "Method not permitted"}
case 429:
return DehashError{Code: 420, Message: "Rate Limited"}
case 302:
return DehashError{Code: 302, Message: "Invalid/Missing Query"}
default:
return DehashError{Code: -1, Message: "An unknown error has occurred"}
}
}
+365
View File
@@ -0,0 +1,365 @@
package sqlite
import (
"fmt"
"go.uber.org/zap"
"gorm.io/gorm"
"time"
)
// QueryResults queries the database for results based on the provided options
func QueryResults(options *DBOptions) ([]Result, error) {
db := GetDB()
var results []Result
query := db.Model(&Result{})
// Apply filters based on the provided options
query = applyFilters(query, options)
// Apply limit
if options.Limit > 0 {
query = query.Limit(options.Limit)
}
// Execute the query
if err := query.Find(&results).Error; err != nil {
zap.L().Error("query_results",
zap.String("message", "failed to query results"),
zap.Error(err),
)
return nil, fmt.Errorf("failed to query results: %w", err)
}
return results, nil
}
// applyFilters applies filters to the query based on the provided options
func applyFilters(query *gorm.DB, options *DBOptions) *gorm.DB {
// Helper function to apply filter based on exact match setting
applyFilter := func(field, value string) *gorm.DB {
if value == "" {
return query
}
if options.ExactMatch {
return query.Where(field+" = ?", value)
} else {
return query.Where(field+" LIKE ?", "%"+value+"%")
}
}
// Apply filters for each field if provided
if options.Email != "" {
query = applyFilter("email", options.Email)
}
if options.Username != "" {
query = applyFilter("username", options.Username)
}
if options.IPAddress != "" {
query = applyFilter("ip_address", options.IPAddress)
}
if options.Password != "" {
query = applyFilter("password", options.Password)
}
if options.HashedPassword != "" {
query = applyFilter("hashed_password", options.HashedPassword)
}
if options.Name != "" {
query = applyFilter("name", options.Name)
}
if options.Vin != "" {
query = applyFilter("vin", options.Vin)
}
if options.LicensePlate != "" {
query = applyFilter("license_plate", options.LicensePlate)
}
if options.Address != "" {
query = applyFilter("address", options.Address)
}
if options.Phone != "" {
query = applyFilter("phone", options.Phone)
}
if options.Social != "" {
query = applyFilter("social", options.Social)
}
if options.CryptoCurrencyAddress != "" {
query = applyFilter("cryptocurrency_address", options.CryptoCurrencyAddress)
}
if options.Domain != "" {
query = applyFilter("url", options.Domain)
}
// Apply non-empty field filters
for _, field := range options.NonEmptyFields {
switch field {
case "username":
query = query.Where("JSON_ARRAY_LENGTH(username) > 0")
case "email":
query = query.Where("JSON_ARRAY_LENGTH(email) > 0")
case "ip_address", "ipaddress", "ip":
query = query.Where("JSON_ARRAY_LENGTH(ip_address) > 0")
case "password":
query = query.Where("JSON_ARRAY_LENGTH(password) > 0")
case "hashed_password", "hash":
query = query.Where("JSON_ARRAY_LENGTH(hashed_password) > 0")
case "name":
query = query.Where("JSON_ARRAY_LENGTH(name) > 0")
case "vin":
query = query.Where("JSON_ARRAY_LENGTH(vin) > 0")
case "license_plate", "license":
query = query.Where("JSON_ARRAY_LENGTH(license_plate) > 0")
case "address":
query = query.Where("JSON_ARRAY_LENGTH(address) > 0")
case "phone":
query = query.Where("JSON_ARRAY_LENGTH(phone) > 0")
case "social":
query = query.Where("JSON_ARRAY_LENGTH(social) > 0")
case "cryptocurrency_address", "crypto":
query = query.Where("JSON_ARRAY_LENGTH(cryptocurrency_address) > 0")
case "url", "domain":
query = query.Where("JSON_ARRAY_LENGTH(url) > 0")
}
}
return query
}
// GetResultsCount returns the count of results matching the provided options
func GetResultsCount(options *DBOptions) (int64, error) {
db := GetDB()
var count int64
query := db.Model(&Result{})
// Apply filters based on the provided options
query = applyFilters(query, options)
// Count the results
if err := query.Count(&count).Error; err != nil {
zap.L().Error("get_results_count",
zap.String("message", "failed to count results"),
zap.Error(err),
)
return 0, fmt.Errorf("failed to count results: %w", err)
}
return count, nil
}
// QueryRuns queries the database for previous query runs (QueryOptions) based on the provided filters
func QueryRuns(limit, lastXRuns int, startDate, endDate time.Time, containsQuery string) ([]QueryOptions, error) {
db := GetDB()
var runs []QueryOptions
query := db.Model(&QueryOptions{})
// Apply date range filter if provided
if lastXRuns > 0 {
query = query.Order("created_at DESC").Limit(lastXRuns)
} else if !startDate.IsZero() && !endDate.IsZero() {
query = query.Where("created_at BETWEEN ? AND ?", startDate, endDate)
} else if !startDate.IsZero() {
query = query.Where("created_at >= ?", startDate)
} else if !endDate.IsZero() {
query = query.Where("created_at <= ?", endDate)
}
// Apply query filter if provided
if containsQuery != "" {
// Search in all query fields
query = query.Where(
"username_query LIKE ? OR "+
"email_query LIKE ? OR "+
"ip_query LIKE ? OR "+
"pass_query LIKE ? OR "+
"hash_query LIKE ? OR "+
"name_query LIKE ? OR "+
"domain_query LIKE ? OR "+
"vin_query LIKE ? OR "+
"license_plate_query LIKE ? OR "+
"address_query LIKE ? OR "+
"phone_query LIKE ? OR "+
"social_query LIKE ? OR "+
"crypto_address_query LIKE ?",
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
"%"+containsQuery+"%",
)
}
// Apply limit
if limit > 0 {
query = query.Limit(limit)
}
// Order by most recent first
query = query.Order("created_at DESC")
// Execute the query
if err := query.Find(&runs).Error; err != nil {
zap.L().Error("query_runs",
zap.String("message", "failed to query runs"),
zap.Error(err),
)
return nil, fmt.Errorf("failed to query runs: %w", err)
}
return runs, nil
}
// GetRunsCount returns the count of runs matching the provided filters
func GetRunsCount(lastXRuns int, startDate, endDate time.Time, containsQuery string) (int64, error) {
db := GetDB()
var count int64
query := db.Model(&QueryOptions{})
// Apply date range filter if provided
if lastXRuns > 0 {
query = query.Order("created_at DESC").Limit(lastXRuns)
} else if !startDate.IsZero() && !endDate.IsZero() {
query = query.Where("created_at BETWEEN ? AND ?", startDate, endDate)
} else if !startDate.IsZero() {
query = query.Where("created_at >= ?", startDate)
} else if !endDate.IsZero() {
query = query.Where("created_at <= ?", endDate)
}
// Apply query filter if provided
if containsQuery != "" {
// Search in all query fields
query = query.Where(
"username_query LIKE ? OR "+
"email_query LIKE ? OR "+
"ip_query LIKE ? OR "+
"pass_query LIKE ? OR "+
"hash_query LIKE ? OR "+
"name_query LIKE ? OR "+
"domain_query LIKE ? OR "+
"vin_query LIKE ? OR "+
"license_plate_query LIKE ? OR "+
"address_query LIKE ? OR "+
"phone_query LIKE ? OR "+
"social_query LIKE ? OR "+
"crypto_address_query LIKE ?",
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
"%"+containsQuery+"%", "%"+containsQuery+"%", "%"+containsQuery+"%",
"%"+containsQuery+"%",
)
}
// Count the results
if err := query.Count(&count).Error; err != nil {
zap.L().Error("get_runs_count",
zap.String("message", "failed to count runs"),
zap.Error(err),
)
return 0, fmt.Errorf("failed to count runs: %w", err)
}
return count, nil
}
// QueryCreds queries the database for credentials based on the provided filters
func QueryCreds(options *DBOptions) ([]Creds, error) {
db := GetDB()
var creds []Creds
query := db.Model(&Creds{})
// Apply filters based on the provided options
if options.Username != "" {
if options.ExactMatch {
query = query.Where("username = ?", options.Username)
} else {
query = query.Where("username LIKE ?", "%"+options.Username+"%")
}
}
if options.Email != "" {
if options.ExactMatch {
query = query.Where("email = ?", options.Email)
} else {
query = query.Where("email LIKE ?", "%"+options.Email+"%")
}
}
if options.Password != "" {
if options.ExactMatch {
query = query.Where("password = ?", options.Password)
} else {
query = query.Where("password LIKE ?", "%"+options.Password+"%")
}
}
// Apply limit
if options.Limit > 0 {
query = query.Limit(options.Limit)
}
// Execute the query
if err := query.Find(&creds).Error; err != nil {
zap.L().Error("query_creds",
zap.String("message", "failed to query credentials"),
zap.Error(err),
)
return nil, fmt.Errorf("failed to query credentials: %w", err)
}
return creds, nil
}
// GetCredsCount returns the count of credentials matching the provided filters
func GetCredsCount(options *DBOptions) (int64, error) {
db := GetDB()
var count int64
query := db.Model(&Creds{})
// Apply filters based on the provided options
if options.Username != "" {
if options.ExactMatch {
query = query.Where("username = ?", options.Username)
} else {
query = query.Where("username LIKE ?", "%"+options.Username+"%")
}
}
if options.Email != "" {
if options.ExactMatch {
query = query.Where("email = ?", options.Email)
} else {
query = query.Where("email LIKE ?", "%"+options.Email+"%")
}
}
if options.Password != "" {
if options.ExactMatch {
query = query.Where("password = ?", options.Password)
} else {
query = query.Where("password LIKE ?", "%"+options.Password+"%")
}
}
// Count the results
if err := query.Count(&count).Error; err != nil {
zap.L().Error("get_creds_count",
zap.String("message", "failed to count credentials"),
zap.Error(err),
)
return 0, fmt.Errorf("failed to count credentials: %w", err)
}
return count, nil
}
+209
View File
@@ -0,0 +1,209 @@
package sqlite
import (
"fmt"
"go.uber.org/zap"
"gorm.io/gorm/clause"
"os"
"path/filepath"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
var DB *gorm.DB
// InitDB initializes the database connection
func InitDB(dbDir string) (*gorm.DB, error) {
zap.L().Info("Initializing database")
// 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)
}
dbPath := filepath.Join(dbDir, "dehashed.sqlite")
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err != nil {
zap.L().Error("Failed to connect to database", zap.Error(err))
return nil, fmt.Errorf("failed to connect to database: %w", err)
}
// Auto migrate your models
err = db.AutoMigrate(&Result{}, &Creds{}, QueryOptions{}, Creds{}, WhoisRecord{}, SubdomainRecord{}, HistoryRecord{})
if err != nil {
zap.L().Error("Failed to migrate database", zap.Error(err))
return nil, fmt.Errorf("failed to migrate database: %w", err)
}
DB = db
return db, nil
}
// GetDB returns the database connection
func GetDB() *gorm.DB {
if DB == nil {
zap.L().Error("database not initialized")
fmt.Println("sqlite database not initialized")
os.Exit(1)
}
return DB
}
func StoreResults(results DehashedResults) error {
if len(results.Results) == 0 {
return nil
}
zap.L().Info("Storing results", zap.Int("count", len(results.Results)))
db := GetDB()
// Use batch insert with conflict handling
const batchSize = 100
var lastErr error
// Extract the slice of results
resultSlice := results.Results
for i := 0; i < len(resultSlice); i += batchSize {
end := i + batchSize
if end > len(resultSlice) {
end = len(resultSlice)
}
batch := resultSlice[i:end]
// Use Clauses with OnConflict DoNothing to skip conflicts
err := db.Clauses(clause.OnConflict{DoNothing: true}).CreateInBatches(&batch, batchSize).Error
if err != nil {
zap.L().Warn("Error storing some results", zap.Error(err))
lastErr = err
// Continue with next batch despite error
}
}
return lastErr
}
func StoreCreds(creds []Creds) error {
if len(creds) == 0 {
return nil
}
zap.L().Info("Storing credentials", zap.Int("count", len(creds)))
db := GetDB()
// Use batch insert with conflict handling
// This will insert records in batches and continue even if some fail
const batchSize = 100
var lastErr error
for i := 0; i < len(creds); i += batchSize {
end := i + batchSize
if end > len(creds) {
end = len(creds)
}
batch := creds[i:end]
// Use Clauses with OnConflict DoNothing to skip conflicts
err := db.Clauses(clause.OnConflict{DoNothing: true}).CreateInBatches(&batch, batchSize).Error
if err != nil {
zap.L().Warn("Error storing some credentials", zap.Error(err))
lastErr = err
// Continue with next batch despite error
}
}
return lastErr
}
func StoreQueryOptions(queryOptions *QueryOptions) error {
db := GetDB()
return db.Create(queryOptions).Error
}
func StoreWhoisRecord(whoisRecord WhoisRecord) error {
// Create a pointer to the record to make it addressable
recordPtr := &whoisRecord
zap.L().Info("Storing WHOIS record",
zap.String("domain", whoisRecord.DomainName))
db := GetDB()
// Use OnConflict clause to handle duplicates
err := db.Clauses(clause.OnConflict{DoNothing: true}).Create(recordPtr).Error
if err != nil {
zap.L().Error("store_whois_record",
zap.String("message", "failed to store whois record"),
zap.Error(err))
return err
}
return nil
}
func StoreSubdomainRecord(subdomainRecords []SubdomainRecord) error {
if len(subdomainRecords) == 0 {
return nil
}
zap.L().Info("Storing subdomain records", zap.Int("count", len(subdomainRecords)))
db := GetDB()
// Use batch insert with conflict handling
const batchSize = 100
var lastErr error
for i := 0; i < len(subdomainRecords); i += batchSize {
end := i + batchSize
if end > len(subdomainRecords) {
end = len(subdomainRecords)
}
batch := subdomainRecords[i:end]
// Use Clauses with OnConflict DoNothing to skip conflicts
err := db.Clauses(clause.OnConflict{DoNothing: true}).CreateInBatches(&batch, batchSize).Error
if err != nil {
zap.L().Warn("Error storing some subdomain records", zap.Error(err))
lastErr = err
// Continue with next batch despite error
}
}
return lastErr
}
func StoreHistoryRecord(historyRecords []HistoryRecord) error {
if len(historyRecords) == 0 {
return nil
}
zap.L().Info("Storing history records", zap.Int("count", len(historyRecords)))
db := GetDB()
// Use batch insert with conflict handling
const batchSize = 100
var lastErr error
for i := 0; i < len(historyRecords); i += batchSize {
end := i + batchSize
if end > len(historyRecords) {
end = len(historyRecords)
}
batch := historyRecords[i:end]
// Use Clauses with OnConflict DoNothing to skip conflicts
err := db.Clauses(clause.OnConflict{DoNothing: true}).CreateInBatches(&batch, batchSize).Error
if err != nil {
zap.L().Warn("Error storing some history records", zap.Error(err))
lastErr = err
// Continue with next batch despite error
}
}
return lastErr
}
+20
View File
@@ -0,0 +1,20 @@
package sqlite
type DehashedSearchRequest struct {
Page int `json:"page"`
Query string `json:"query"`
Size int `json:"size"`
Wildcard bool `json:"wildcard"`
Regex bool `json:"regex"`
DeDupe bool `json:"de_dupe"`
}
func NewDehashedSearchRequest(size int, wildcard, regex bool) *DehashedSearchRequest {
return &DehashedSearchRequest{
Page: 0,
Size: size,
Wildcard: false,
Regex: false,
DeDupe: true,
}
}
+90
View File
@@ -0,0 +1,90 @@
package sqlite
import (
"encoding/json"
"fmt"
"go.uber.org/zap"
"gorm.io/gorm"
"io"
"os"
)
type DehashedResponse struct {
Balance int `json:"balance"`
Entries []Result `json:"entries"`
Success bool `json:"success"`
Took string `json:"took"`
TotalResults int `json:"total"`
}
type Result struct {
gorm.Model
DehashedId string `json:"id" xml:"id" yaml:"id" gorm:"uniqueIndex"`
Email []string `json:"email,omitempty" xml:"email,omitempty" yaml:"email,omitempty" gorm:"serializer:json"`
IpAddress []string `json:"ip_address,omitempty" xml:"ip_address,omitempty" yaml:"ip_address,omitempty" gorm:"serializer:json"`
Username []string `json:"username,omitempty" xml:"username,omitempty" yaml:"username,omitempty" gorm:"serializer:json"`
Password []string `json:"password,omitempty" xml:"password,omitempty" yaml:"password,omitempty" gorm:"serializer:json"`
HashedPassword []string `json:"hashed_password,omitempty" xml:"hashed_password,omitempty" yaml:"hashed_password,omitempty" gorm:"serializer:json"`
HashType string `json:"hash_type,omitempty" xml:"hash_type,omitempty" yaml:"hash_type,omitempty"`
Name []string `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty" gorm:"serializer:json"`
Vin []string `json:"vin,omitempty" xml:"vin,omitempty" yaml:"vin,omitempty" gorm:"serializer:json"`
LicensePlate []string `json:"license_plate,omitempty" xml:"license_plate,omitempty" yaml:"license_plate,omitempty" gorm:"serializer:json"`
Url []string `json:"url,omitempty" xml:"url,omitempty" yaml:"url,omitempty" gorm:"serializer:json"`
Social []string `json:"social,omitempty" xml:"social,omitempty" yaml:"social,omitempty" gorm:"serializer:json"`
CryptoCurrencyAddress []string `json:"cryptocurrency_address,omitempty" xml:"cryptocurrency_address,omitempty" yaml:"cryptocurrency_address,omitempty" gorm:"serializer:json"`
Address []string `json:"address,omitempty" xml:"address,omitempty" yaml:"address,omitempty" gorm:"serializer:json"`
Phone []string `json:"phone,omitempty" xml:"phone,omitempty" yaml:"phone,omitempty" gorm:"serializer:json"`
Company []string `json:"company,omitempty" xml:"company,omitempty" yaml:"company,omitempty" gorm:"serializer:json"`
DatabaseName string `json:"database_name,omitempty" xml:"database_name,omitempty" yaml:"database_name,omitempty"`
}
type DehashedResults struct {
Results []Result `json:"results"`
}
func (dr *DehashedResults) ExtractCredentials() []Creds {
var creds []Creds
results := dr.Results
for _, r := range results {
if len(r.Password) > 0 {
// Get first email if available
email := ""
if len(r.Email) > 0 {
email = r.Email[0]
}
// Get first password
password := r.Password[0]
cred := Creds{Email: email, Password: password}
creds = append(creds, cred)
}
}
go func() {
err := StoreCreds(creds)
if err != nil {
zap.L().Error("store_creds",
zap.String("message", "failed to store creds"),
zap.Error(err),
)
fmt.Printf("Error Storing Results: %v", err)
}
}()
return creds
}
func NewDehashedResults(body io.Reader) ([]Result, int, int) {
var response DehashedResponse
err := json.NewDecoder(body).Decode(&response)
if err != nil {
fmt.Printf("Error Parsing Response Body: %v", err)
os.Exit(-1)
}
return response.Entries, response.Balance, response.TotalResults
}
+108
View File
@@ -0,0 +1,108 @@
package sqlite
import (
"dehasher/internal/files"
"fmt"
"gorm.io/gorm"
)
type DBOptions struct {
Username string
Email string
IPAddress string
Password string
HashedPassword string
Name string
Vin string
LicensePlate string
Address string
Phone string
Social string
CryptoCurrencyAddress string
Domain string
Limit int
ExactMatch bool
NonEmptyFields []string // Fields that should not be empty
DisplayFields []string // Fields to display in output
}
func NewDBOptions() *DBOptions {
return &DBOptions{
Limit: 100, // Default limit
ExactMatch: false,
NonEmptyFields: []string{},
DisplayFields: []string{},
}
}
func (o *DBOptions) Empty() bool {
return o.Username == "" && o.Email == "" && o.IPAddress == "" &&
o.Password == "" && o.HashedPassword == "" && o.Name == "" &&
o.Vin == "" && o.LicensePlate == "" && o.Address == "" &&
o.Phone == "" && o.Social == "" && o.CryptoCurrencyAddress == "" && o.Domain == "" &&
len(o.NonEmptyFields) == 0
}
type QueryOptions struct {
gorm.Model
MaxRecords int `json:"max_records"`
MaxRequests int `json:"max_requests"`
StartingPage int `json:"starting_page"`
OutputFormat files.FileType `json:"output_format"`
OutputFile string `json:"output_file"`
RegexMatch bool `json:"regex_match"`
WildcardMatch bool `json:"wildcard_match"`
UsernameQuery string `json:"username_query"`
EmailQuery string `json:"email_query"`
IpQuery string `json:"ip_query"`
PassQuery string `json:"pass_query"`
HashQuery string `json:"hash_query"`
NameQuery string `json:"name_query"`
DomainQuery string `json:"domain_query"`
VinQuery string `json:"vin_query"`
LicensePlateQuery string `json:"license_plate_query"`
AddressQuery string `json:"address_query"`
PhoneQuery string `json:"phone_query"`
SocialQuery string `json:"social_query"`
CryptoAddressQuery string `json:"crypto_address_query"`
PrintBalance bool `json:"print_balance"`
CredsOnly bool `json:"creds_only"`
}
func NewQueryOptions(maxRecords, maxRequests, startingPage int, outputFormat, outputFile, usernameQuery, emailQuery, ipQuery, passQuery, hashQuery, nameQuery, domainQuery, vinQuery, licensePlateQuery, addressQuery, phoneQuery, socialQuery, cryptoAddressQuery string, regexMatch, wildcardMatch, printBalance, credsOnly bool) *QueryOptions {
return &QueryOptions{
MaxRecords: maxRecords,
MaxRequests: maxRequests,
StartingPage: startingPage,
OutputFormat: files.GetFileType(outputFormat),
OutputFile: outputFile,
PrintBalance: printBalance,
CredsOnly: credsOnly,
RegexMatch: regexMatch,
WildcardMatch: wildcardMatch,
UsernameQuery: usernameQuery,
EmailQuery: emailQuery,
IpQuery: ipQuery,
PassQuery: passQuery,
HashQuery: hashQuery,
NameQuery: nameQuery,
DomainQuery: domainQuery,
VinQuery: vinQuery,
LicensePlateQuery: licensePlateQuery,
AddressQuery: addressQuery,
PhoneQuery: phoneQuery,
SocialQuery: socialQuery,
CryptoAddressQuery: cryptoAddressQuery,
}
}
type Creds struct {
gorm.Model
Email string `json:"email" yaml:"email" xml:"email"`
Username string `json:"username" yaml:"username" xml:"username"`
Password string `json:"password" yaml:"password" xml:"password"`
}
func (c Creds) ToString() string {
return fmt.Sprintf("%s%s%s", c.Username, "%", c.Password)
}
+160
View File
@@ -0,0 +1,160 @@
package sqlite
import "gorm.io/gorm"
type WhoIsLookupResult struct {
RemainingCredits int `json:"remaining_credits"`
Data Data `json:"data"`
}
type Data struct {
WhoisRecord WhoisRecord `json:"WhoisRecord"`
}
type WhoisRecord struct {
gorm.Model
Audit Audit `json:"audit" gorm:"serializer:json"`
ContactEmail string `json:"contactEmail"`
CreatedDate string `json:"createdDate"`
CreatedDateNormalized string `json:"createdDateNormalized"`
DomainName string `json:"domainName"`
DomainNameExt string `json:"domainNameExt"`
EstimatedDomainAge int `json:"estimatedDomainAge"`
ExpiresDate string `json:"expiresDate"`
ExpiresDateNormalized string `json:"expiresDateNormalized"`
Footer string `json:"footer"`
Header string `json:"header"`
NameServers NameServers `json:"nameServers" gorm:"serializer:json"`
ParseCode int `json:"parseCode"`
RawText string `json:"rawText"`
Registrant Contact `json:"registrant" gorm:"serializer:json"`
RegistrarIANAID string `json:"registrarIANAID"`
RegistrarName string `json:"registrarName"`
RegistryData RegistryData `json:"registryData" gorm:"serializer:json"`
Status string `json:"status"`
StrippedText string `json:"strippedText"`
TechnicalContact Contact `json:"technicalContact" gorm:"serializer:json"`
UpdatedDate string `json:"updatedDate"`
UpdatedDateNormalized string `json:"updatedDateNormalized"`
}
type Audit struct {
CreatedDate string `json:"createdDate"`
UpdatedDate string `json:"updatedDate"`
}
type NameServers struct {
HostNames []string `json:"hostNames"`
IPs []string `json:"ips"`
RawText string `json:"rawText"`
}
type Contact struct {
City string `json:"city"`
Country string `json:"country"`
CountryCode string `json:"countryCode"`
Name string `json:"name"`
Organization string `json:"organization"`
PostalCode string `json:"postalCode"`
RawText string `json:"rawText"`
State string `json:"state"`
Street1 string `json:"street1"`
Telephone string `json:"telephone"`
}
type RegistryData struct {
Audit Audit `json:"audit"`
CreatedDate string `json:"createdDate"`
CreatedDateNormalized string `json:"createdDateNormalized"`
DomainName string `json:"domainName"`
ExpiresDate string `json:"expiresDate"`
ExpiresDateNormalized string `json:"expiresDateNormalized"`
Footer string `json:"footer"`
Header string `json:"header"`
NameServers NameServers `json:"nameServers"`
ParseCode int `json:"parseCode"`
RawText string `json:"rawText"`
RegistrarIANAID string `json:"registrarIANAID"`
RegistrarName string `json:"registrarName"`
Status string `json:"status"`
StrippedText string `json:"strippedText"`
UpdatedDate string `json:"updatedDate"`
UpdatedDateNormalized string `json:"updatedDateNormalized"`
WhoisServer string `json:"whoisServer"`
}
type WhoIsSubdomainScan struct {
RemainingCredits int `json:"remaining_credits"`
Data ScanData `json:"data"`
}
type ScanData struct {
Result ScanResult `json:"result"`
Search string `json:"search"`
}
type ScanResult struct {
Count int `json:"count"`
Records []SubdomainRecord `json:"records"`
}
type SubdomainRecord struct {
gorm.Model
Domain string `json:"domain"`
FirstSeen int64 `json:"firstSeen"`
LastSeen int64 `json:"lastSeen"`
}
type WhoIsHistory struct {
RemainingCredits int `json:"remaining_credits"`
Data HistoryData `json:"data"`
}
type HistoryData struct {
Records []HistoryRecord `json:"records"`
RecordsCount int `json:"recordsCount"`
}
type HistoryRecord struct {
gorm.Model
AdministrativeContact ContactInfo `json:"administrativeContact" gorm:"serializer:json"`
Audit Audit `json:"audit" gorm:"serializer:json"`
BillingContact ContactInfo `json:"billingContact" gorm:"serializer:json"`
CleanText string `json:"cleanText"`
CreatedDateISO8601 string `json:"createdDateISO8601"`
CreatedDateRaw string `json:"createdDateRaw"`
DomainName string `json:"domainName"`
DomainType string `json:"domainType"`
ExpiresDateISO8601 string `json:"expiresDateISO8601"`
ExpiresDateRaw string `json:"expiresDateRaw"`
NameServers []string `json:"nameServers" gorm:"serializer:json"`
RawText string `json:"rawText"`
RegistrantContact ContactInfo `json:"registrantContact" gorm:"serializer:json"`
RegistrarName string `json:"registrarName"`
Status []string `json:"status" gorm:"serializer:json"`
TechnicalContact ContactInfo `json:"technicalContact" gorm:"serializer:json"`
UpdatedDateISO8601 string `json:"updatedDateISO8601"`
UpdatedDateRaw string `json:"updatedDateRaw"`
WhoisServer string `json:"whoisServer"`
ZoneContact ContactInfo `json:"zoneContact" gorm:"serializer:json"`
}
type ContactInfo struct {
City string `json:"city"`
Country string `json:"country"`
Email string `json:"email"`
Fax string `json:"fax"`
FaxExt string `json:"faxExt"`
Name string `json:"name"`
Organization string `json:"organization"`
PostalCode string `json:"postalCode"`
RawText string `json:"rawText"`
State string `json:"state"`
Street string `json:"street"`
Telephone string `json:"telephone"`
TelephoneExt string `json:"telephoneExt"`
}
type WhoIsCredits struct {
WhoisCredits int `json:"whois_credits"`
}
+319
View File
@@ -0,0 +1,319 @@
package whois
import (
"bytes"
"dehasher/internal/sqlite"
"encoding/json"
"errors"
"fmt"
"go.uber.org/zap"
"io"
"net/http"
)
type DehashedWHOISSearchRequest struct {
Include []string `json:"include,omitempty"`
Exclude []string `json:"exclude,omitempty"`
IPAddress string `json:"ip_address,omitempty"`
ReverseType string `json:"reverse_type,omitempty"`
Domain string `json:"domain,omitempty"`
MXAddress string `json:"mx_address,omitempty"`
NSAddress string `json:"ns_address,omitempty"`
SearchType string `json:"search_type,omitempty"`
}
func WhoisSearch(domain, apiKey string) (sqlite.WhoIsLookupResult, error) {
var whois sqlite.WhoIsLookupResult
whoisSearchRequest := DehashedWHOISSearchRequest{
Domain: domain,
SearchType: "whois",
}
reqBody, _ := json.Marshal(whoisSearchRequest)
req, err := http.NewRequest("POST", "https://api.dehashed.com/v2/whois/search", bytes.NewReader(reqBody))
if err != nil {
return whois, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Dehashed-Api-Key", apiKey)
res, err := http.DefaultClient.Do(req)
if res != nil {
defer res.Body.Close()
}
if err != nil {
return whois, err
}
if res == nil {
return whois, errors.New("response was nil")
}
b, err := io.ReadAll(res.Body)
if err != nil {
return whois, err
}
err = json.Unmarshal(b, &whois)
if err != nil {
zap.L().Error("whois_search",
zap.String("message", "failed to unmarshal response body"),
zap.Error(err),
)
fmt.Println("Error unmarshalling response body:", err)
fmt.Println("Response body:", string(b))
return whois, err
}
return whois, nil
}
func WhoisHistory(domain, apiKey string) (sqlite.WhoIsHistory, error) {
var whois sqlite.WhoIsHistory
whoisSearchRequest := DehashedWHOISSearchRequest{
Domain: domain,
SearchType: "whois-history",
}
reqBody, _ := json.Marshal(whoisSearchRequest)
req, err := http.NewRequest("POST", "https://api.dehashed.com/v2/whois/search", bytes.NewReader(reqBody))
if err != nil {
return whois, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Dehashed-Api-Key", apiKey)
res, err := http.DefaultClient.Do(req)
if res != nil {
zap.L().Info("whois_history",
zap.String("message", "response was not nil"),
)
defer res.Body.Close()
}
if err != nil {
zap.L().Error("whois_history",
zap.String("message", "failed to perform request"),
zap.Error(err),
)
return whois, err
}
if res == nil {
zap.L().Error("whois_history",
zap.String("message", "response was nil"),
)
return whois, errors.New("response was nil")
}
b, err := io.ReadAll(res.Body)
if err != nil {
zap.L().Error("whois_history",
zap.String("message", "failed to read response body"),
zap.Error(err),
)
return whois, err
}
err = json.Unmarshal(b, &whois)
if err != nil {
zap.L().Error("whois_history",
zap.String("message", "failed to unmarshal response body"),
zap.Error(err),
)
fmt.Println("Error unmarshalling response body:", err)
fmt.Println("Response body:", string(b))
return whois, err
}
return whois, nil
}
func ReverseWHOIS(include []string, exclude []string, reverseType, apiKey string) (string, error) {
whoisSearchRequest := DehashedWHOISSearchRequest{
Include: include,
Exclude: exclude,
ReverseType: reverseType,
SearchType: "reverse-whois",
}
reqBody, _ := json.Marshal(whoisSearchRequest)
req, err := http.NewRequest("POST", "https://api.dehashed.com/v2/whois/search", bytes.NewReader(reqBody))
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Dehashed-Api-Key", apiKey)
res, err := http.DefaultClient.Do(req)
if res != nil {
defer res.Body.Close()
}
if err != nil {
return "", err
}
if res == nil {
return "", errors.New("response was nil")
}
b, err := io.ReadAll(res.Body)
if err != nil {
return "", err
}
return string(b), nil
}
func WhoisIP(ipAddress, apiKey string) ([]byte, error) {
whoisSearchRequest := DehashedWHOISSearchRequest{
IPAddress: ipAddress,
SearchType: "reverse-ip",
}
reqBody, _ := json.Marshal(whoisSearchRequest)
req, err := http.NewRequest("POST", "https://api.dehashed.com/v2/whois/search", bytes.NewReader(reqBody))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Dehashed-Api-Key", apiKey)
res, err := http.DefaultClient.Do(req)
if res != nil {
defer res.Body.Close()
}
if err != nil {
return nil, err
}
if res == nil {
return nil, errors.New("response was nil")
}
b, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
}
return b, nil
}
func WhoisMX(mxAddress, apiKey string) (string, error) {
whoisSearchRequest := DehashedWHOISSearchRequest{
MXAddress: mxAddress,
SearchType: "reverse-mx",
}
reqBody, _ := json.Marshal(whoisSearchRequest)
req, err := http.NewRequest("POST", "https://api.dehashed.com/v2/whois/search", bytes.NewReader(reqBody))
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Dehashed-Api-Key", apiKey)
res, err := http.DefaultClient.Do(req)
if res != nil {
defer res.Body.Close()
}
if err != nil {
return "", err
}
if res == nil {
return "", errors.New("response was nil")
}
b, err := io.ReadAll(res.Body)
if err != nil {
return "", err
}
return string(b), nil
}
func WhoisNS(nsAddress, apiKey string) (string, error) {
whoisSearchRequest := DehashedWHOISSearchRequest{
NSAddress: nsAddress,
SearchType: "reverse-ns",
}
reqBody, _ := json.Marshal(whoisSearchRequest)
req, err := http.NewRequest("POST", "https://api.dehashed.com/v2/whois/search", bytes.NewReader(reqBody))
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Dehashed-Api-Key", apiKey)
res, err := http.DefaultClient.Do(req)
if res != nil {
defer res.Body.Close()
}
if err != nil {
return "", err
}
if res == nil {
return "", errors.New("response was nil")
}
b, err := io.ReadAll(res.Body)
if err != nil {
return "", err
}
return string(b), nil
}
func WhoisSubdomainScan(domain, apiKey string) (sqlite.WhoIsSubdomainScan, error) {
var whois sqlite.WhoIsSubdomainScan
whoisSearchRequest := DehashedWHOISSearchRequest{
Domain: domain,
SearchType: "subdomain-scan",
}
reqBody, _ := json.Marshal(whoisSearchRequest)
req, err := http.NewRequest("POST", "https://api.dehashed.com/v2/whois/search", bytes.NewReader(reqBody))
if err != nil {
return whois, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Dehashed-Api-Key", apiKey)
res, err := http.DefaultClient.Do(req)
if res != nil {
defer res.Body.Close()
}
if err != nil {
return whois, err
}
if res == nil {
return whois, errors.New("response was nil")
}
b, err := io.ReadAll(res.Body)
if err != nil {
return whois, err
}
err = json.Unmarshal(b, &whois)
if err != nil {
zap.L().Error("whois_subdomain_scan",
zap.String("message", "failed to unmarshal response body"),
zap.Error(err),
)
fmt.Println("Error unmarshalling response body:", err)
fmt.Println("Response body:", string(b))
return whois, err
}
return whois, nil
}
func GetWHOISCredits(apiKey string) (sqlite.WhoIsCredits, error) {
var whoisCredits sqlite.WhoIsCredits
req, err := http.NewRequest("GET", "https://api.dehashed.com/v2/whois/credits", nil)
if err != nil {
return whoisCredits, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Dehashed-Api-Key", apiKey)
res, err := http.DefaultClient.Do(req)
if res != nil {
defer res.Body.Close()
}
if err != nil {
return whoisCredits, err
}
if res == nil {
return whoisCredits, errors.New("response was nil")
}
b, err := io.ReadAll(res.Body)
if err != nil {
return whoisCredits, err
}
err = json.Unmarshal(b, &whoisCredits)
if err != nil {
zap.L().Error("get_whois_credits",
zap.String("message", "failed to unmarshal response body"),
zap.Error(err),
)
fmt.Println("Error unmarshalling response body:", err)
fmt.Println("Response body:", string(b))
return whoisCredits, err
}
return whoisCredits, nil
}