first commit
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user