En este artículo veremos un sistema de búsqueda de dominios con soporte para OVH-Cloudflare-GoDaddy-DonDominio NS/Whois con una base de datos sqlite a modo de caché.
El código principal es el siguiente:
package main
// OVH-Cloudflare-GoDaddy-DonDominio NS/Whois search system
// TODO: Inline search domain editing
// go get github.com/ovh/go-ovh/ovh
// go get github.com/inancgumus/screen
// go get github.com/mattn/go-sqlite3
// go get github.com/fatih/color
// go get github.com/cloudflare/cloudflare-go
// go get github.com/oze4/godaddygo
// go get github.com/twiny/whois/v2
// go get github.com/davecgh/go-spew/spew
import (
"bufio"
"context"
"database/sql"
"encoding/json"
"errors"
"flag"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"strings"
"unicode/utf8"
"github.com/cloudflare/cloudflare-go"
"github.com/fatih/color"
"github.com/inancgumus/screen"
_ "github.com/mattn/go-sqlite3"
"github.com/ovh/go-ovh/ovh"
"github.com/oze4/godaddygo"
"github.com/twiny/whois/v2"
// "github.com/davecgh/go-spew/spew"
)
func checkFileExists(filePath string) bool {
_, error := os.Stat(filePath)
return !errors.Is(error, os.ErrNotExist)
}
// Check valid DNS
func checkDNS(name string) error {
switch {
case len(name) == 0:
return errors.New("Domain name is empty")
case len(name) > 255:
return fmt.Errorf("Domain name length is %d, can't exceed 255", len(name))
}
var l int
for i := 0; i < len(name); i++ {
b := name[i]
if b == '.' {
// check domain labels validity
switch {
case i == l:
return fmt.Errorf("Domain has invalid character '.' at offset %d, label can't begin with a period", i)
case i-l > 63:
return fmt.Errorf("Domain byte length of label '%s' is %d, can't exceed 63", name[l:i], i-l)
case name[l] == '-':
return fmt.Errorf("Domain label '%s' at offset %d begins with a hyphen", name[l:i], l)
case name[i-1] == '-':
return fmt.Errorf("Domain label '%s' at offset %d ends with a hyphen", name[l:i], l)
}
l = i + 1
continue
}
// test label character validity, note: tests are ordered by decreasing validity frequency
if !(b >= 'a' && b <= 'z' || b >= '0' && b <= '9' || b == '-' || b >= 'A' && b <= 'Z') {
// show the printable unicode character starting at byte offset i
c, _ := utf8.DecodeRuneInString(name[i:])
if c == utf8.RuneError {
return fmt.Errorf("Domain has invalid rune at offset %d", i)
}
return fmt.Errorf("Domain has invalid character '%c' at offset %d", c, i)
}
}
// check top level domain validity
switch {
case l == len(name):
return fmt.Errorf("Domain has missing top level domain, domain can't end with a period")
case len(name)-l > 63:
return fmt.Errorf("Domain's top level domain '%s' has byte length %d, can't exceed 63", name[l:], len(name)-l)
case name[l] == '-':
return fmt.Errorf("Domain's top level domain '%s' at offset %d begin with a hyphen", name[l:], l)
case name[len(name)-1] == '-':
return fmt.Errorf("Domain's top level domain '%s' at offset %d ends with a hyphen", name[l:], l)
case name[l] >= '0' && name[l] <= '9':
return fmt.Errorf("Domain's top level domain '%s' at offset %d begins with a digit", name[l:], l)
}
return nil
}
func createTable(db *sql.DB) error {
// Set default font color:
color.Set(color.FgCyan)
createTableSQL := `CREATE TABLE IF NOT EXISTS domain_list ( "id" VARCHAR(100), "realId" VARCHAR(100), "isp" VARCHAR(100), "domain" VARCHAR(100));`
statement, err := db.Prepare(createTableSQL)
if err != nil {
color.Red("++ ERROR: %s", err.Error())
return err
}
statement.Exec()
//log.Println(">> domain_list table created")
return nil
}
var getOvhDomains = func(client *ovh.Client, OVHDomainData *[]string) error {
if err := client.Get("/domain", &OVHDomainData); err != nil {
return err
}
return nil
}
var populateOvh = func(db *sql.DB) error {
fmt.Println()
fmt.Println("- Getting OVH data:")
ovhIdsFile := "configs/ovh.list"
if _, err := os.Stat(ovhIdsFile); err != nil {
color.Red("++ ERROR: File does not exist: %s", ovhIdsFile)
color.Red(" Create it with the following content syntax:")
color.Red(" ovhId:ovhKey:ovhSecret:ovhConsumer:ovhRealId")
return err
} else {
file, err := os.Open(ovhIdsFile)
if err != nil {
color.Red("++ ERROR: %s", err)
return err
}
insertSQL := `INSERT INTO domain_list(id, realId, isp, domain) VALUES (?, ?, ?, ?)`
statement, err := db.Prepare(insertSQL)
if err != nil {
color.Red("++ ERROR: %s", err.Error())
return err
}
// Parse IDs config file:
scanner := bufio.NewScanner(file)
for scanner.Scan() {
// If API access fails, color configuration is lost, reassign in each iteration
color.Set(color.FgCyan)
//fmt.Println(scanner.Text())
dataFields := strings.Split(scanner.Text(), ":")
//fmt.Println(dataFields)
ovhId := dataFields[0]
// Check comment line
if ovhId[0:1] == "#" {
continue
}
ovhKey := dataFields[1]
ovhSecret := dataFields[2]
ovhConsumer := dataFields[3]
ovhRealId := dataFields[4]
fmt.Println("-- ovhId:", ovhId)
//fmt.Println("ovhKey:", ovhKey)
//fmt.Println("ovhSecret:", ovhSecret)
//fmt.Println("ovhConsumer:", ovhConsumer)
//fmt.Println("ovhRealId:", ovhRealId)
client, _ := ovh.NewClient(
"ovh-eu",
ovhKey,
ovhSecret,
ovhConsumer,
)
// Query OVH API:
OVHDomainData := []string{}
// client.Get wrapped in order to be able to mock it
//if err := client.Get("/domain", &OVHDomainData); err != nil {
if err := getOvhDomains(client, &OVHDomainData); err != nil {
color.Red("++ ERROR: %s", err)
continue
}
//fmt.Println("OVHDomainData: ", OVHDomainData)
// Insert retrieved information to DB:
for i := 0; i < len(OVHDomainData); i++ {
//fmt.Printf("Inserting ID: %s RealID: %s, ISP: %s Domain: %s.\n", ovhId, ovhRealId, "ovh", OVHDomainData[i])
_, err = statement.Exec(ovhId, ovhRealId, "ovh", OVHDomainData[i])
if err != nil {
color.Red("++ ERROR: %s", err.Error())
return err
}
}
}
if err := scanner.Err(); err != nil {
color.Red("++ ERROR: %s", err)
return err
}
}
return nil
}
var getCloudFlareDomains = func(api *cloudflare.API) ([]cloudflare.Zone, error) {
zones, err := api.ListZones(context.Background())
if err != nil {
return nil, err
}
return zones, err
}
var populateCloudFlare = func(db *sql.DB) error {
fmt.Println()
fmt.Println("- Getting Cloudflare data:")
cloudflareIdsFile := "configs/cloudflare.list"
if _, err := os.Stat(cloudflareIdsFile); err != nil {
color.Red("++ ERROR: File does not exist: %s", cloudflareIdsFile)
color.Red(" Create it with the following content syntax:")
color.Red(" email:password")
return err
} else {
file, err := os.Open(cloudflareIdsFile)
if err != nil {
color.Red("++ ERROR: %s", err)
return err
}
insertSQL := `INSERT INTO domain_list(id, realId, isp, domain) VALUES (?, ?, ?, ?)`
statement, err := db.Prepare(insertSQL)
if err != nil {
color.Red("++ ERROR: %s", err.Error())
return err
}
// Parse IDs config file:
scanner := bufio.NewScanner(file)
for scanner.Scan() {
// If API access fails, color configuration is lost, reassign in each iteration
color.Set(color.FgCyan)
//fmt.Println(scanner.Text())
dataFields := strings.Split(scanner.Text(), ":")
//fmt.Println(dataFields)
cloudflareEmail := dataFields[0]
// Check comment line
if cloudflareEmail[0:1] == "#" {
continue
}
cloudflareApiKey := dataFields[1]
fmt.Println("-- cloudflareEmail:", cloudflareEmail)
//fmt.Println("cloudflareApiKey:", cloudflareApiKey)
api, err := cloudflare.New(cloudflareApiKey, cloudflareEmail)
if err != nil {
color.Red("++ ERROR: %s", err)
continue
}
// Fetch all zones available to this user.
//zones, err := api.ListZones(context.Background())
zones, err := getCloudFlareDomains(api)
if err != nil {
color.Red("++ ERROR: %s", err)
continue
}
for _, z := range zones {
//fmt.Println(z.Name)
_, err = statement.Exec(cloudflareEmail, cloudflareEmail, "cloudflare", z.Name)
if err != nil {
color.Red("++ ERROR: %s", err.Error())
return err
}
}
}
if err := scanner.Err(); err != nil {
color.Red("++ ERROR: %s", err)
return err
}
}
return nil
}
var getGoDaddyDomains = func(api godaddygo.API) ([]godaddygo.DomainSummary, error) {
godaddy := api.V1()
zones, err := godaddy.ListDomains(context.Background())
if err != nil {
return nil, err
}
//spew.Dump(zones)
return zones, err
}
var populateGoDaddy = func(db *sql.DB) error {
fmt.Println()
fmt.Println("- Getting GoDaddy data:")
godaddyIdsFile := "configs/godaddy.list"
if _, err := os.Stat(godaddyIdsFile); err != nil {
color.Red("++ ERROR: File does not exist: %s", godaddyIdsFile)
color.Red(" Create it with the following content syntax:")
color.Red(" ID:key:secret")
return err
} else {
file, err := os.Open(godaddyIdsFile)
if err != nil {
color.Red("++ ERROR: %s", err)
return err
}
insertSQL := `INSERT INTO domain_list(id, realId, isp, domain) VALUES (?, ?, ?, ?)`
statement, err := db.Prepare(insertSQL)
if err != nil {
color.Red("++ ERROR: %s", err.Error())
return err
}
// Parse IDs config file:
scanner := bufio.NewScanner(file)
for scanner.Scan() {
// If API access fails, color configuration is lost, reassign in each iteration
color.Set(color.FgCyan)
//fmt.Println(scanner.Text())
dataFields := strings.Split(scanner.Text(), ":")
//fmt.Println(dataFields)
godaddyId := dataFields[0]
// Check comment line
if godaddyId[0:1] == "#" {
continue
}
godaddyKey := dataFields[1]
godaddySecret := dataFields[2]
godaddyRealId := dataFields[3]
fmt.Println("-- godaddyId:", godaddyId)
//fmt.Println("godaddyKey:", godaddyKey)
//fmt.Println("godaddySecret:", godaddySecret)
//fmt.Println("godaddyRealId:", godaddyRealId)
api, err := godaddygo.NewProduction(godaddyKey, godaddySecret)
if err != nil {
color.Red("++ ERROR: %s", err.Error())
continue
}
//spew.Dump(api)
//godaddy := api.V1()
// Fetch all zones available to this user.
//zones, err := godaddy.ListDomains(context.Background())
zones, err := getGoDaddyDomains(api)
//spew.Dump(zones)
if err != nil {
color.Red("++ ERROR: %s", err)
continue
}
for _, z := range zones {
//fmt.Println(z.Domain)
_, err = statement.Exec(godaddyId, godaddyRealId, "godaddy", z.Domain)
if err != nil {
color.Red("++ ERROR: %s", err.Error())
return err
}
}
}
if err := scanner.Err(); err != nil {
color.Red("++ ERROR: %s", err)
return err
}
}
return nil
}
var getDonDominioDomains = func(client *http.Client, r *http.Request) (*http.Response, error) {
resp, err := client.Do(r)
if err != nil {
return resp, err
}
return resp, nil
}
var populateDonDominio = func(db *sql.DB) error {
// curl -d "apiuser=USERNAME&apipasswd=PASSWORD" -H "Content-Type: application/x-www-form-urlencoded" -X POST https://simple-api.dondominio.net/tool/hello/|jq
// DonDominio requires IP-API whitelisting
fmt.Println()
fmt.Println("- Getting DonDominio data:")
donDominioIdsFile := "configs/donDominio.list"
if _, err := os.Stat(donDominioIdsFile); err != nil {
color.Red("++ ERROR: File does not exist: %s", donDominioIdsFile)
color.Red(" Create it with the following content syntax:")
color.Red(" id:user:pass")
return err
} else {
file, err := os.Open(donDominioIdsFile)
if err != nil {
color.Red("++ ERROR: %s", err)
return err
}
insertSQL := `INSERT INTO domain_list(id, realId, isp, domain) VALUES (?, ?, ?, ?)`
statement, err := db.Prepare(insertSQL)
if err != nil {
color.Red("++ ERROR: %s", err.Error())
return err
}
// Parse IDs config file:
scanner := bufio.NewScanner(file)
for scanner.Scan() {
// If API access fails, color configuration is lost, reassign in each iteration
color.Set(color.FgCyan)
//fmt.Println(scanner.Text())
dataFields := strings.Split(scanner.Text(), ":")
//fmt.Println(dataFields)
donDominioId := dataFields[0]
// Check comment line
if donDominioId[0:1] == "#" {
continue
}
donDominioUser := dataFields[1]
donDominioPass := dataFields[2]
//fmt.Println("donDominioId: ", donDominioId)
fmt.Println("-- donDominioId:", donDominioId)
//fmt.Println("donDominioUser: ", donDominioUser)
//fmt.Println("donDominioPass: ", donDominioPass)
apiUrl := "https://simple-api.dondominio.net"
resource := "/domain/list/"
//resource := "/tool/hello/"
data := url.Values{}
data.Set("apiuser", donDominioUser)
data.Set("apipasswd", donDominioPass)
u, _ := url.ParseRequestURI(apiUrl)
u.Path = resource
// "https://simple-api.dondominio.net/domain/list/"
urlStr := u.String()
client := &http.Client{}
//spew.Dump(client)
r, _ := http.NewRequest(http.MethodPost, urlStr, strings.NewReader(data.Encode()))
r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
if resp, err := getDonDominioDomains(client, r); err != nil {
//if resp, err := client.Do(r); err != nil {
color.Red("++ ERROR populateDonDominio: %s", err)
continue
} else {
// Define json structs
type QueryInfo struct {
Page int `json:"page"`
PageLength int `json:"pageLength"`
Results int `json:"results"`
Total int `json:"total"`
}
type Domain struct {
Name string `json:"name"`
Status string `json:"status"`
TLD string `json:"tld"`
DomainID int `json:"domainID"`
TsExpir string `json:"tsExpir"`
}
type ResponseData struct {
QueryInfo QueryInfo `json:"queryInfo"`
Domains []Domain `json:"domains"`
}
type Response struct {
Success bool `json:"success"`
ErrorCode int `json:"errorCode"`
ErrorCodeMsg string `json:"errorCodeMsg"`
Action string `json:"action"`
Version string `json:"version"`
ResponseData ResponseData `json:"responseData"`
}
//fmt.Println(resp.Status)
respBody, _ := ioutil.ReadAll(resp.Body)
//fmt.Println(string(respBody))
var response Response
err := json.Unmarshal([]byte(string(respBody)), &response)
if err != nil {
color.Red("Error deserializing JSON, continuing: %v -> %v", err, string(respBody))
continue
}
for _, domain := range response.ResponseData.Domains {
//fmt.Println("Domain:", domain.Name)
_, err = statement.Exec(donDominioId, donDominioUser, "dondominio", domain.Name)
if err != nil {
color.Red("++ ERROR: %v", err.Error())
return err
}
}
}
}
if err := scanner.Err(); err != nil {
color.Red("++ ERROR: %s", err)
return err
}
}
return nil
}
func populateDB(db *sql.DB) error {
// Set default font color:
color.Set(color.FgCyan)
populatingError := false
fmt.Println("> Populating DB")
if err := populateOvh(db); err != nil {
//color.Red("++ ERROR populateOvh: %s", err)
populatingError = true
}
if err := populateCloudFlare(db); err != nil {
//color.Red("++ ERROR populateCloudFlare: %s", err)
populatingError = true
}
if err := populateGoDaddy(db); err != nil {
//color.Red("++ ERROR populateGoDaddy: %s", err)
populatingError = true
}
if err := populateDonDominio(db); err != nil {
//color.Red("++ ERROR populateDonDominio: %s", err)
populatingError = true
}
// If API access fails, color configuration is lost
color.Set(color.FgCyan)
fmt.Println("> Done")
if populatingError {
return fmt.Errorf("Error populating DB")
}
return nil
}
var getDnsNs = func(domainToSearch string) ([]*net.NS, error) {
ns, err := net.LookupNS(domainToSearch)
return ns, err
}
var getWhois = func(domainToSearch string) (whois.Response, error) {
var whoisResponse whois.Response
if client, err := whois.NewClient(nil); err != nil {
return whoisResponse, err
} else {
resp, err := client.Query(context.TODO(), domainToSearch)
return resp, err
}
}
func queryDB(domainToSearch string, db *sql.DB) error {
// Set default font color:
color.Set(color.FgCyan)
//fmt.Println("domainToSearch: ", domainToSearch)
// Check if domain related row exists:
row, err := db.Query("SELECT COUNT(*) FROM domain_list WHERE domain=?", domainToSearch)
if err != nil {
color.Red("++ ERROR: %s", err)
return err
}
defer row.Close()
for row.Next() {
var n int
row.Scan(&n)
//fmt.Printf("N: %d\n", n)
if n == 0 {
color.Yellow(" NOT FOUND")
// NS lookup:
ns, err := getDnsNs(domainToSearch)
if err != nil {
color.Red("++ ERROR NS: Couldnt query NS servers: %s", err)
} else {
color.Set(color.FgCyan)
fmt.Println("------------")
color.Yellow(" NS servers:")
for _, v := range ns {
color.Set(color.FgGreen)
fmt.Println(" ", v.Host)
}
}
// WHOIS lookup
resp, err := getWhois(domainToSearch)
if err != nil {
color.Red("++ ERROR WHOIS: %s", err)
} else {
// Print the response
color.Set(color.FgCyan)
fmt.Println("------------")
color.Yellow(" WHOIS Info:")
color.Set(color.FgGreen)
fmt.Printf("%+v\n", resp)
color.Set(color.FgCyan)
fmt.Println("------------")
}
fmt.Println("")
return nil
}
}
// Query DB for domain data:
row, err = db.Query("SELECT * FROM domain_list WHERE domain=?", domainToSearch)
if err != nil {
color.Red("++ ERROR: %s", err)
return err
}
defer row.Close()
fmt.Println("------------")
for row.Next() {
var id string
var realId string
var isp string
var domain string
row.Scan(&id, &realId, &isp, &domain)
color.Green(" ID: %s\n", id)
color.Green(" REALID: %s\n", realId)
color.Green(" ISP: %s\n", isp)
color.Green(" DOMAIN: %s\n", domain)
color.Set(color.FgCyan)
fmt.Println("------------")
}
fmt.Println("")
return nil
}
var checkPopulatedDb = func(db *sql.DB) error {
// Set default font color:
color.Set(color.FgCyan)
row, err := db.Query("SELECT COUNT(*) FROM domain_list")
if err != nil {
color.Red("++ ERROR: %s", err)
return err
}
defer row.Close()
for row.Next() {
var n int
row.Scan(&n)
//fmt.Printf("N: %d\n", n)
if n == 0 {
return fmt.Errorf("Error: DB not populated")
}
}
return nil
}
func regenerateDb(dbFile string) error {
//fmt.Println("Executing: regenerateDb")
// Remove DB:
err := os.Remove(dbFile)
fileNotFoundError := "remove " + dbFile + ": no such file or directory"
if err != nil && err.Error() != fileNotFoundError {
color.Red("++ ERROR: %s", err)
return err
}
// Create DB:
file, err := os.Create(dbFile)
if err != nil {
color.Red("++ ERROR: %s", err.Error())
return err
} else {
fmt.Println("> DB file created successfully")
}
file.Close()
// Open DB:
sqliteDatabase, _ := sql.Open("sqlite3", dbFile)
defer sqliteDatabase.Close()
// Create table:
if err := createTable(sqliteDatabase); err != nil {
color.Red("++ ERROR creating DB table")
return err
} else {
fmt.Println("> DB table created successfully")
}
// Populate DB:
if err := populateDB(sqliteDatabase); err != nil {
color.Red("++ ERROR populating DB")
return err
} else {
if err := checkPopulatedDb(sqliteDatabase); err != nil {
color.Red("++ ERROR: Empty DB or not populated correctly")
return err
} else {
fmt.Println("> DB populated successfully")
}
}
return nil
}
func searchCLI(db *sql.DB, oneSearch bool) {
// Search domain:
fmt.Println("")
var domainToSearch string
for {
// Fix: Strange behaviour related to colors when returning from queryDB function, this color reassigment fixes it
color.Set(color.FgCyan)
fmt.Printf("> Domain to search: ")
color.Set(color.FgWhite)
fmt.Scanln(&domainToSearch)
color.Set(color.FgCyan)
// Check correct domain syntax
//fmt.Printf("domainToSearch: %s\n", domainToSearch)
//fmt.Printf("len(domainToSearch): %i\n", len(domainToSearch))
if domainToSearch != "" {
if len(domainToSearch) < 100 {
if err := checkDNS(domainToSearch); err != nil {
//fmt.Printf("err: %v\n", err)
color.Yellow(" Invalid domain")
} else {
//fmt.Printf("err: %v\n", err)
if err := queryDB(domainToSearch, db); err != nil {
color.Red("++ ERROR: %s", err)
}
}
domainToSearch = ""
} else {
color.Yellow(" Invalid domain")
}
}
if oneSearch {
return
}
}
}
func main() {
dbFile := "domain_list.db"
// Set default font color:
color.Set(color.FgCyan)
//fmt.Print("\033[H\033[2J")
// Portable clear screen version
screen.MoveTopLeft()
screen.Clear()
fmt.Println("#############################################################################")
fmt.Println("| OVH-Cloudflare-GoDaddy-DonDominio NS/Whois search system: Ctrl+c -> Exit |")
fmt.Printf("| v0.7-sqlite: %s - coded by Kr0m: alfaexploit.com |\n", dbFile)
fmt.Println("#############################################################################")
fmt.Println("")
// -regenerateDB command:
regenerateDBPtr := flag.Bool("regenerateDB", false, "Force DB regeneration.")
exitPtr := flag.Bool("exit", false, "Exit without waiting for user input, useful combined with -regenerateDB. Also useful for unit-testing.")
flag.Parse()
//fmt.Println("regenerateDB:", *regenerateDBPtr)
//fmt.Println("exit:", *exitPtr)
fmt.Printf("> Checking if previous %s file exists\n", dbFile)
sqliteBbExists := checkFileExists(dbFile)
if sqliteBbExists {
fmt.Printf(" DB: %s FOUND\n", dbFile)
// Regenerate DB arg:
if *regenerateDBPtr {
fmt.Println("> Regenerating DB.")
if err := regenerateDb(dbFile); err != nil {
os.Exit(1)
}
// Exit arg
if *exitPtr {
return
}
} else {
// Open DB:
sqliteDatabase, _ := sql.Open("sqlite3", dbFile)
defer sqliteDatabase.Close()
// Check if DB is populated
if err := checkPopulatedDb(sqliteDatabase); err != nil {
fmt.Println("> DB is not populated")
if err := regenerateDb(dbFile); err != nil {
os.Exit(1)
}
}
// Exit arg
if *exitPtr {
return
}
}
} else {
fmt.Printf(" DB: %s file NOT FOUND, creating it\n", dbFile)
if err := regenerateDb(dbFile); err != nil {
os.Exit(1)
}
// Exit arg
if *exitPtr {
return
}
}
// Open DB:
db, _ := sql.Open("sqlite3", dbFile)
defer db.Close()
searchCLI(db, false)
}
A continuación dejo los tests unitarios:
package main
import (
"bufio"
"bytes"
"database/sql"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"os"
"strings"
"testing"
"time"
"github.com/cloudflare/cloudflare-go"
"github.com/fatih/color"
_ "github.com/mattn/go-sqlite3"
"github.com/ovh/go-ovh/ovh"
"github.com/oze4/godaddygo"
"github.com/twiny/whois/v2"
)
// Test checkFileExists
func TestCheckFileExists(t *testing.T) {
// Create temp file
tmpfile, err := os.CreateTemp("", "example")
if err != nil {
t.Fatal(err)
}
// Remove test file en end of test function execution
defer os.Remove(tmpfile.Name())
// Check file exists
if !checkFileExists(tmpfile.Name()) {
t.Fatalf("Expected file %s to exist, but it does not", tmpfile.Name())
}
// Check inexistent file exists
if checkFileExists("nonexistentfile.txt") {
t.Fatalf("Expected nonexistentfile.txt to not exist, but it does")
}
}
// Test checkDNS
func TestCheckDNS(t *testing.T) {
// Valid domain
validDomain := "alfaexploit.com"
if err := checkDNS(validDomain); err != nil {
t.Errorf("Expected domain %s to be valid, but got error: %v", validDomain, err)
}
// Empty domain
invalidDomain := ""
if err := checkDNS(invalidDomain); err == nil {
t.Errorf("Expected domain %s to be invalid, but got no error", invalidDomain)
}
// len(name) > 255 domain
invalidDomain = "KpYTnQSWGuQ5pm4bQyx9rluKU4q8qLj1QNTd4wcT4OzBgJwQo1BGskbctE1mabrGOUCESgFBeTqEHVbhXEVDJM4rgR56CXDFoWTPIwlM9MTMR09B3fwkUY4GzO2bl35cMpVRL1cYcNJMU98oh0l7KBiBzA6eKHkXdoagQbuuT1KS4OovGAa5JH2TxmbEPSGynT2p3JhDTGVm0ZHRfBly5HharptauKdqVNeZegzlVofJ4D1FxpjOBzqSAeO1VAs.com"
if err := checkDNS(invalidDomain); err == nil {
t.Errorf("Expected domain %s to be invalid, but got no error", invalidDomain)
}
// Invalid domains
invalidDomain = "ex*ample.com"
if err := checkDNS(invalidDomain); err == nil {
t.Errorf("Expected domain %s to be invalid, but got no error", invalidDomain)
}
invalidDomain = ".example.com"
if err := checkDNS(invalidDomain); err == nil {
t.Errorf("Expected domain %s to be invalid, but got no error", invalidDomain)
}
invalidDomain = "-example.com"
if err := checkDNS(invalidDomain); err == nil {
t.Errorf("Expected domain %s to be invalid, but got no error", invalidDomain)
}
invalidDomain = "example.com-"
if err := checkDNS(invalidDomain); err == nil {
t.Errorf("Expected domain %s to be invalid, but got no error", invalidDomain)
}
invalidDomain = "ex~ample.com-"
if err := checkDNS(invalidDomain); err == nil {
t.Errorf("Expected domain %s to be invalid, but got no error", invalidDomain)
}
}
// Test createTable
func TestCreateTable(t *testing.T) {
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Errorf("Failed to open database: %v", err)
}
defer db.Close()
// Create table
err = createTable(db)
if err != nil {
t.Errorf("Expected no error, but got: %v", err)
}
// Check if table exists
_, err = db.Exec("SELECT 1 FROM domain_list LIMIT 1;")
if err != nil {
t.Errorf("Expected table domain_list to exist, but got error: %v", err)
}
}
func TestPopulateOvh(t *testing.T) {
// Copy original functions content
getOvhDomainsOri := getOvhDomains
// unmock functions content
defer func() {
getOvhDomains = getOvhDomainsOri
}()
getOvhDomains = func(client *ovh.Client, OVHDomainData *[]string) error {
*OVHDomainData = append(*OVHDomainData, "testdomain1.com")
return nil
}
// Create memory database
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("Failed to open database: %v", err)
}
defer db.Close()
// Create table
err = createTable(db)
if err != nil {
t.Fatalf("Failed to create table: %v", err)
}
if err := populateOvh(db); err != nil {
t.Errorf("Expected no error when checking populateOvh, but got: %v", err)
}
}
func TestPopulateCloudFlare(t *testing.T) {
// Copy original functions content
getCloudFlareDomainsOri := getCloudFlareDomains
// unmock functions content
defer func() {
getCloudFlareDomains = getCloudFlareDomainsOri
}()
getCloudFlareDomains = func(api *cloudflare.API) ([]cloudflare.Zone, error) {
zone := cloudflare.Zone{
ID: "1234567890abcdef1234567890abcdef",
Name: "example.com",
DevMode: 0,
OriginalNS: []string{"ns1.example.com", "ns2.example.com"},
OriginalRegistrar: "Example Registrar",
OriginalDNSHost: "Example DNS Host",
CreatedOn: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC),
ModifiedOn: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC),
NameServers: []string{"ns-cloud-a1.googledomains.com", "ns-cloud-a2.googledomains.com"},
Owner: cloudflare.Owner{
ID: "owner123",
Email: "owner@example.com",
Name: "Example Owner",
OwnerType: "user",
},
Permissions: []string{
"#dns_records:edit",
"#dns_records:read",
},
Plan: cloudflare.ZonePlan{
ZonePlanCommon: cloudflare.ZonePlanCommon{
ID: "free",
Name: "Free Plan",
Price: 0,
Currency: "USD",
Frequency: "monthly",
},
LegacyID: "legacy123",
IsSubscribed: true,
CanSubscribe: true,
LegacyDiscount: false,
ExternallyManaged: false,
},
PlanPending: cloudflare.ZonePlan{
ZonePlanCommon: cloudflare.ZonePlanCommon{
ID: "",
Name: "",
Price: 0,
Currency: "",
Frequency: "",
},
LegacyID: "",
IsSubscribed: false,
CanSubscribe: false,
LegacyDiscount: false,
ExternallyManaged: false,
},
Status: "active",
Paused: false,
Type: "full",
Host: struct {
Name string
Website string
}{
Name: "Example Host",
Website: "https://www.example.com",
},
VanityNS: nil,
Betas: nil,
DeactReason: "",
Meta: cloudflare.ZoneMeta{
PageRuleQuota: 3,
WildcardProxiable: false,
PhishingDetected: false,
},
Account: cloudflare.Account{
ID: "account123",
Name: "Example Account",
Type: "standard",
CreatedOn: time.Date(2022, 12, 25, 10, 0, 0, 0, time.UTC),
Settings: nil,
},
VerificationKey: "verificationkey123",
}
zones := []cloudflare.Zone{zone}
return zones, nil
}
// Create memory database
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("Failed to open database: %v", err)
}
defer db.Close()
// Create table
err = createTable(db)
if err != nil {
t.Fatalf("Failed to create table: %v", err)
}
if err := populateCloudFlare(db); err != nil {
t.Errorf("Expected no error when checking populateCloudFlare, but got: %v", err)
}
}
func TestPopulateGoDaddy(t *testing.T) {
// Copy original functions content
getGoDaddyDomainsOri := getGoDaddyDomains
// unmock functions content
defer func() {
getGoDaddyDomains = getGoDaddyDomainsOri
}()
getGoDaddyDomains = func(api godaddygo.API) ([]godaddygo.DomainSummary, error) {
expiration, _ := time.Parse(time.RFC3339, "2025-01-01T00:00:00Z")
created, _ := time.Parse(time.RFC3339, "2020-01-01T00:00:00Z")
zone := godaddygo.DomainSummary{
Domain: "example.com",
Status: "ACTIVE",
Expires: expiration,
CreatedAt: created,
}
zones := []godaddygo.DomainSummary{zone}
return zones, nil
}
// Create memory database
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("Failed to open database: %v", err)
}
defer db.Close()
// Create table
err = createTable(db)
if err != nil {
t.Fatalf("Failed to create table: %v", err)
}
if err := populateGoDaddy(db); err != nil {
t.Errorf("Expected no error when checking populateGoDaddy, but got: %v", err)
}
}
// populateDonDominio(db)
func TestPopulateDonDominio(t *testing.T) {
// Copy original functions content
getDonDominioDomainsOri := getDonDominioDomains
// unmock functions content
defer func() {
getDonDominioDomains = getDonDominioDomainsOri
}()
getDonDominioDomains = func(client *http.Client, r *http.Request) (*http.Response, error) {
//fmt.Println("-- Executing mocked getDonDominioDomains function")
type QueryInfo struct {
Page int `json:"page"`
PageLength int `json:"pageLength"`
Results int `json:"results"`
Total int `json:"total"`
}
type Domain struct {
Name string `json:"name"`
Status string `json:"status"`
TLD string `json:"tld"`
DomainID int `json:"domainID"`
TsExpir string `json:"tsExpir"`
}
type ResponseData struct {
QueryInfo QueryInfo `json:"queryInfo"`
Domains []Domain `json:"domains"`
}
type Response struct {
Success bool `json:"success"`
ErrorCode int `json:"errorCode"`
ErrorCodeMsg string `json:"errorCodeMsg"`
Action string `json:"action"`
Version string `json:"version"`
ResponseData ResponseData `json:"responseData"`
}
responseData := Response{
Success: true,
ErrorCode: 0,
ErrorCodeMsg: "",
Action: "domain/list",
Version: "1.0.20",
ResponseData: ResponseData{
QueryInfo: QueryInfo{
Page: 1,
PageLength: 1000,
Results: 122,
Total: 122,
},
Domains: []Domain{
{
Name: "example.com",
Status: "active",
TLD: "com",
DomainID: 123456,
TsExpir: "2025-01-01",
},
},
},
}
responseJSON, _ := json.Marshal(responseData)
resp := &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewBuffer(responseJSON)),
Header: make(http.Header),
}
// Configurar encabezados HTTP si es necesario
resp.Header.Set("Content-Type", "application/json")
return resp, nil
}
// Create memory database
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("Failed to open database: %v", err)
}
defer db.Close()
// Create table
err = createTable(db)
if err != nil {
t.Fatalf("Failed to create table: %v", err)
}
if err := populateDonDominio(db); err != nil {
t.Errorf("Expected no error when checking populateDonDominio, but got: %v", err)
}
}
// Test populateDB
func TestPopulateDB(t *testing.T) {
// Copy original functions content
populateOvhOri := populateOvh
populateCloudFlareOri := populateCloudFlare
populateGoDaddyOri := populateGoDaddy
populateDonDominioOri := populateDonDominio
// unmock functions content
defer func() {
populateOvh = populateOvhOri
populateCloudFlare = populateCloudFlareOri
populateGoDaddy = populateGoDaddyOri
populateDonDominio = populateDonDominioOri
}()
populateOvh = func(db *sql.DB) error {
return nil
}
populateCloudFlare = func(db *sql.DB) error {
return nil
}
populateGoDaddy = func(db *sql.DB) error {
return nil
}
populateDonDominio = func(db *sql.DB) error {
return nil
}
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Errorf("Failed to open database: %v", err)
}
defer db.Close()
if err := populateDB(db); err != nil {
t.Errorf("Expected no error when populating db, but got: %v", err)
}
populateOvh = func(db *sql.DB) error {
return errors.New("populateOvh error")
}
populateCloudFlare = func(db *sql.DB) error {
return errors.New("populateCloudFlare error")
}
populateGoDaddy = func(db *sql.DB) error {
return errors.New("populateGoDaddy error")
}
populateDonDominio = func(db *sql.DB) error {
return errors.New("populateDonDominio error")
}
if err := populateDB(db); err == nil {
t.Errorf("Expected error when populating db, but got: %v", err)
}
}
// Test getDnsNs
func TestGetDnsNs(t *testing.T) {
ns, err := getDnsNs("alfaexploit.com")
if err != nil {
t.Errorf("Expected no error in getDnsNs, but got: %v", err)
} else {
for _, v := range ns {
//fmt.Println(" ", v.Host)
if v.Host != "dns200.anycast.me." && v.Host != "ns200.anycast.me." {
t.Errorf("Expected dns200.anycast.me or ns200.anycast.me, but got: %v", v.Host)
}
}
}
}
// Test getWhois
func TestGetWhois(t *testing.T) {
_, err := getWhois("alfaexploit.com")
if err != nil {
t.Errorf("Expected no error in getWhois, but got: %v", err)
}
}
// Test queryDB
func TestQueryDB(t *testing.T) {
// Mock getDnsNs function in order to speed up tests execution
getDnsNsOri := getDnsNs
// unmock functions content
defer func() {
getDnsNs = getDnsNsOri
}()
getDnsNs = func(domainToSearch string) ([]*net.NS, error) {
//fmt.Println("-- Executing mocked getDnsNs function, domain: ", domainToSearch)
switch domainToSearch {
case "alfaexploit.com":
ns := make([]*net.NS, 2)
ns[0] = &net.NS{
Host: "nstest1.example.com.",
}
ns[1] = &net.NS{
Host: "nstest2.example.com.",
}
return ns, nil
default:
return nil, nil
}
}
// Mock getWhois function in order to speed up tests execution
getWhoisOri := getWhois
// unmock functions content
defer func() {
getWhois = getWhoisOri
}()
getWhois = func(domainToSearch string) (whois.Response, error) {
//fmt.Println("-- Executing mocked getWhois function, domain: ", domainToSearch)
return whois.Response{
Domain: domainToSearch,
Name: domainToSearch,
TLD: "test",
WHOISHost: "whois.test.com",
WHOISRaw: "testWHOIS",
}, nil
}
// Create memory database
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("Failed to open database: %v", err)
}
defer db.Close()
// Create table
err = createTable(db)
if err != nil {
t.Fatalf("Failed to create table: %v", err)
}
// Insert test domain
_, err = db.Exec(`INSERT INTO domain_list (id, realId, isp, domain) VALUES ("1", "realId", "ovh", "example.com")`)
if err != nil {
t.Fatalf("Failed to insert domain: %v", err)
}
// Search domain
err = queryDB("example.com", db)
if err != nil {
t.Errorf("Expected no error when querying existing domain, but got: %v", err)
}
// Search non-db domain
err = queryDB("alfaexploit.com", db)
if err != nil {
t.Errorf("Expected no error when querying non-db domain, but got: %v", err)
}
// Search inexistent domain
err = queryDB("nonexistent.com", db)
if err != nil {
t.Errorf("Expected no error when querying nonexistent domain, but got: %v", err)
}
}
// Test checkPopulatedDb
func TestCheckPopulateDB(t *testing.T) {
// Create memory database
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("Failed to open database: %v", err)
}
defer db.Close()
// Create table
err = createTable(db)
if err != nil {
t.Fatalf("Failed to create table: %v", err)
}
// Insert test domain
_, err = db.Exec(`INSERT INTO domain_list (id, realId, isp, domain) VALUES ("1", "realId", "ovh", "example.com")`)
if err != nil {
t.Fatalf("Failed to insert domain: %v", err)
}
// Search inexistent domain
err = checkPopulatedDb(db)
if err != nil {
t.Errorf("Expected no error when checking databse population, but got: %v", err)
}
}
// Test regenerateDb
func TestRegenerateDb(t *testing.T) {
// Copy original functions content
populateOvhOri := populateOvh
populateCloudFlareOri := populateCloudFlare
populateGoDaddyOri := populateGoDaddy
populateDonDominioOri := populateDonDominio
checkPopulatedDbOri := checkPopulatedDb
// unmock functions content
defer func() {
populateOvh = populateOvhOri
populateCloudFlare = populateCloudFlareOri
populateGoDaddy = populateGoDaddyOri
populateDonDominio = populateDonDominioOri
checkPopulatedDb = checkPopulatedDbOri
}()
populateOvh = func(db *sql.DB) error {
return nil
}
populateCloudFlare = func(db *sql.DB) error {
return nil
}
populateGoDaddy = func(db *sql.DB) error {
return nil
}
populateDonDominio = func(db *sql.DB) error {
return nil
}
checkPopulatedDb = func(db *sql.DB) error {
return nil
}
dbFile := "/tmp/testDb.db"
if err := regenerateDb(dbFile); err != nil {
t.Errorf("Expected no error when checking TestRegenerateDb, but got: %v", err)
}
checkPopulatedDb = func(db *sql.DB) error {
return fmt.Errorf("Error populating DB")
}
if err := regenerateDb(dbFile); err == nil {
t.Errorf("Expected error when checking TestRegenerateDb, but got: %v", err)
}
}
// Test main
func TestMain(t *testing.T) {
// Copy original functions content
populateOvhOri := populateOvh
populateCloudFlareOri := populateCloudFlare
populateGoDaddyOri := populateGoDaddy
populateDonDominioOri := populateDonDominio
// unmock functions content
defer func() {
populateOvh = populateOvhOri
populateCloudFlare = populateCloudFlareOri
populateGoDaddy = populateGoDaddyOri
populateDonDominio = populateDonDominioOri
}()
populateOvh = func(db *sql.DB) error {
return nil
}
populateCloudFlare = func(db *sql.DB) error {
return nil
}
populateGoDaddy = func(db *sql.DB) error {
return nil
}
populateDonDominio = func(db *sql.DB) error {
return nil
}
checkPopulatedDb = func(db *sql.DB) error {
return nil
}
// Save original Args and restore on exit function
oldArgs := os.Args
defer func() {
os.Args = oldArgs
}()
// Args reset
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
// Configure new Args
os.Args = []string{"cmd", "-exit"}
// Copy original functions content
// We cant unmock it using defer because maybe we need to make some prints in console for debugging
osStdoutOri := os.Stdout
osStderrOri := os.Stderr
colorOutputOri := color.Output
colorErrorOri := color.Error
// All content written to w pipe, will be copied automatically to r pipe
r, w, _ := os.Pipe()
// Make Stdout/Stderr to be written to w pipe
// Color module defines other Stdout/Stderr, so pipe them to w pipe too
os.Stdout = w
os.Stderr = w
color.Output = w
color.Error = w
main()
// Close w pipe
w.Close()
// Restore Stdout/Stderr to normal output
os.Stdout = osStdoutOri
os.Stderr = osStderrOri
color.Output = colorOutputOri
color.Error = colorErrorOri
// Read all r pipe content
out, _ := io.ReadAll(r)
//fmt.Println("--- out ---")
//fmt.Println(out)
scanner := bufio.NewScanner(bytes.NewReader(out))
bannerFound := false
for scanner.Scan() {
line := scanner.Text()
//fmt.Println("-- LINE: ", line)
if strings.Contains(line, "coded by Kr0m: alfaexploit.com") {
bannerFound = true
break
}
}
if !bannerFound {
t.Fatalf(`TestMain: No banner found`)
}
}
// Test main -regenerateDB
func TestMainDbFileRegenerateDB(t *testing.T) {
dbFile := "/tmp/testDb.db"
// Remove DB:
err := os.Remove(dbFile)
fileNotFoundError := "remove " + dbFile + ": no such file or directory"
if err != nil && err.Error() != fileNotFoundError {
t.Fatalf(`Error deleting DB file: %s`, dbFile)
}
// Create DB:
file, err := os.Create(dbFile)
if err != nil {
t.Fatalf(`Error TestMainDbFileRegenerateDB: %v`, err)
}
file.Close()
// Copy original functions content
populateOvhOri := populateOvh
populateCloudFlareOri := populateCloudFlare
populateGoDaddyOri := populateGoDaddy
populateDonDominioOri := populateDonDominio
// unmock functions content
defer func() {
populateOvh = populateOvhOri
populateCloudFlare = populateCloudFlareOri
populateGoDaddy = populateGoDaddyOri
populateDonDominio = populateDonDominioOri
}()
populateOvh = func(db *sql.DB) error {
return nil
}
populateCloudFlare = func(db *sql.DB) error {
return nil
}
populateGoDaddy = func(db *sql.DB) error {
return nil
}
populateDonDominio = func(db *sql.DB) error {
return nil
}
checkPopulatedDb = func(db *sql.DB) error {
return nil
}
// Save original Args and restore on exit function
oldArgs := os.Args
defer func() {
os.Args = oldArgs
}()
// Args reset
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
// Configure new Args
os.Args = []string{"cmd", "-regenerateDB", "-exit"}
// Copy original functions content
// We cant unmock it using defer because maybe we need to make some prints in console for debugging
osStdoutOri := os.Stdout
osStderrOri := os.Stderr
colorOutputOri := color.Output
colorErrorOri := color.Error
// All content written to w pipe, will be copied automatically to r pipe
r, w, _ := os.Pipe()
// Make Stdout/Stderr to be written to w pipe
// Color module defines other Stdout/Stderr, so pipe them to w pipe too
os.Stdout = w
os.Stderr = w
color.Output = w
color.Error = w
main()
// Close w pipe
w.Close()
// Restore Stdout/Stderr to normal output
os.Stdout = osStdoutOri
os.Stderr = osStderrOri
color.Output = colorOutputOri
color.Error = colorErrorOri
// Read all r pipe content
out, _ := io.ReadAll(r)
//fmt.Println("--- out ---")
//fmt.Println(out)
scanner := bufio.NewScanner(bytes.NewReader(out))
lineFound := false
for scanner.Scan() {
line := scanner.Text()
//fmt.Println("-- LINE: ", line)
if strings.Contains(line, "> Regenerating DB.") {
lineFound = true
break
}
}
if !lineFound {
t.Fatalf(`TestMainDbFileRegenerateDB: '> Regenerating DB.' line not found`)
}
}
// Test searchCLI correct query
func TestSearchCLICorrectQuery(t *testing.T) {
// Args reset
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
// Create memory database
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("Failed to open database: %v", err)
}
defer db.Close()
// Create table
err = createTable(db)
if err != nil {
t.Fatalf("Failed to create table: %v", err)
}
// Insert test domain
_, err = db.Exec(`INSERT INTO domain_list (id, realId, isp, domain) VALUES ("1", "realId", "ovh", "example.com")`)
if err != nil {
t.Fatalf("Failed to insert domain: %v", err)
}
// Save original os.Stdout, os.Stderr, and os.Stdin
osStdoutOri := os.Stdout
osStderrOri := os.Stderr
colorOutputOri := color.Output
colorErrorOri := color.Error
osStdinOri := os.Stdin
// Create pipes for capturing output
rOut, wOut, _ := os.Pipe()
rIn, wIn, _ := os.Pipe()
// Redirect os.Stdout and os.Stderr to our pipe
os.Stdout = wOut
os.Stderr = wOut
color.Output = wOut
color.Error = wOut
// Redirect os.Stdin to our input pipe
os.Stdin = rIn
// Simulate user input by writing to the input pipe
input := "example.com\n"
io.WriteString(wIn, input)
wIn.Close()
// Run main function
searchCLI(db, true)
// Close output pipe to signal that we are done writing
wOut.Close()
// Restore os.Stdout, os.Stderr, and os.Stdin to their original state
os.Stdout = osStdoutOri
os.Stderr = osStderrOri
color.Output = colorOutputOri
color.Error = colorErrorOri
os.Stdin = osStdinOri
// Read all output
out, _ := io.ReadAll(rOut)
// Scan output and check if expected line is present
scanner := bufio.NewScanner(bytes.NewReader(out))
lineFound := false
for scanner.Scan() {
line := scanner.Text()
//fmt.Println("LINE: ", line)
if strings.Contains(line, "DOMAIN: example.com") {
lineFound = true
break
}
}
if !lineFound {
t.Fatalf(`TestSearchCLICorrectQuery: 'DOMAIN: example.com' line not found`)
}
}
// Test searchCLI incorrect query1
func TestSearchCLIIncorrectQuery1(t *testing.T) {
// Args reset
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
// Create memory database
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("Failed to open database: %v", err)
}
defer db.Close()
// Create table
err = createTable(db)
if err != nil {
t.Fatalf("Failed to create table: %v", err)
}
// Insert test domain
_, err = db.Exec(`INSERT INTO domain_list (id, realId, isp, domain) VALUES ("1", "realId", "ovh", "example.com")`)
if err != nil {
t.Fatalf("Failed to insert domain: %v", err)
}
// Save original os.Stdout, os.Stderr, and os.Stdin
osStdoutOri := os.Stdout
osStderrOri := os.Stderr
colorOutputOri := color.Output
colorErrorOri := color.Error
osStdinOri := os.Stdin
// Create pipes for capturing output
rOut, wOut, _ := os.Pipe()
rIn, wIn, _ := os.Pipe()
// Redirect os.Stdout and os.Stderr to our pipe
os.Stdout = wOut
os.Stderr = wOut
color.Output = wOut
color.Error = wOut
// Redirect os.Stdin to our input pipe
os.Stdin = rIn
// Simulate user input by writing to the input pipe
input := "KpYTnQSWGuQ5pm4bQyx9rluKU4q8qLj1QNTd4wcT4OzBgJwQo1BGskbctE1mabrGOUCESgFBeTqEHVbhXEVDJM4rgR56CXDFoWTPIwlM9MTMR09B3fwkUY4GzO2bl35cMpVRL1cYcNJMU98oh0l7KBiBzA6eKHkXdoagQbuuT1KS4OovGAa5JH2TxmbEPSGynT2p3JhDTGVm0ZHRfBly5HharptauKdqVNeZegzlVofJ4D1FxpjOBzqSAeO1VAs.com\n"
io.WriteString(wIn, input)
wIn.Close()
// Run main function
searchCLI(db, true)
// Close output pipe to signal that we are done writing
wOut.Close()
// Restore os.Stdout, os.Stderr, and os.Stdin to their original state
os.Stdout = osStdoutOri
os.Stderr = osStderrOri
color.Output = colorOutputOri
color.Error = colorErrorOri
os.Stdin = osStdinOri
// Read all output
out, _ := io.ReadAll(rOut)
// Scan output and check if expected line is present
scanner := bufio.NewScanner(bytes.NewReader(out))
lineFound := false
for scanner.Scan() {
line := scanner.Text()
//fmt.Println("LINE: ", line)
if strings.Contains(line, "Invalid domain") {
lineFound = true
break
}
}
if !lineFound {
t.Fatalf(`TestSearchCLIIncorrectQuery1: 'Invalid domain' line not found`)
}
}
// Test searchCLI incorrect query2
func TestSearchCLIIncorrectQuery2(t *testing.T) {
// Args reset
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
// Create memory database
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("Failed to open database: %v", err)
}
defer db.Close()
// Create table
err = createTable(db)
if err != nil {
t.Fatalf("Failed to create table: %v", err)
}
// Insert test domain
_, err = db.Exec(`INSERT INTO domain_list (id, realId, isp, domain) VALUES ("1", "realId", "ovh", "example.com")`)
if err != nil {
t.Fatalf("Failed to insert domain: %v", err)
}
// Save original os.Stdout, os.Stderr, and os.Stdin
osStdoutOri := os.Stdout
osStderrOri := os.Stderr
colorOutputOri := color.Output
colorErrorOri := color.Error
osStdinOri := os.Stdin
// Create pipes for capturing output
rOut, wOut, _ := os.Pipe()
rIn, wIn, _ := os.Pipe()
// Redirect os.Stdout and os.Stderr to our pipe
os.Stdout = wOut
os.Stderr = wOut
color.Output = wOut
color.Error = wOut
// Redirect os.Stdin to our input pipe
os.Stdin = rIn
// Simulate user input by writing to the input pipe
input := "*.asd.com\n"
io.WriteString(wIn, input)
wIn.Close()
// Run main function
searchCLI(db, true)
// Close output pipe to signal that we are done writing
wOut.Close()
// Restore os.Stdout, os.Stderr, and os.Stdin to their original state
os.Stdout = osStdoutOri
os.Stderr = osStderrOri
color.Output = colorOutputOri
color.Error = colorErrorOri
os.Stdin = osStdinOri
// Read all output
out, _ := io.ReadAll(rOut)
// Scan output and check if expected line is present
scanner := bufio.NewScanner(bytes.NewReader(out))
lineFound := false
for scanner.Scan() {
line := scanner.Text()
//fmt.Println("LINE: ", line)
if strings.Contains(line, "Invalid domain") {
lineFound = true
break
}
}
if !lineFound {
t.Fatalf(`TestSearchCLIIncorrectQuery2: 'Invalid domain' line not found`)
}
}
Los tests no cubren el 100% del código pero casi todo:
go test -coverprofile=coverage.out && go tool cover -func=coverage.out
ok domainSearcher 0.555s
domainSearcher/domainSearcher.go:41: checkFileExists 100.0%
domainSearcher/domainSearcher.go:47: checkDNS 73.1%
domainSearcher/domainSearcher.go:99: createTable 75.0%
domainSearcher/domainSearcher.go:507: populateDB 100.0%
domainSearcher/domainSearcher.go:560: queryDB 88.5%
domainSearcher/domainSearcher.go:664: regenerateDb 68.0%
domainSearcher/domainSearcher.go:712: searchCLI 94.4%
domainSearcher/domainSearcher.go:748: main 68.4%
total: (statements) 80.5%
Antes de ejecutar el código debemos crear el directorio configs con las credenciales de acceso de cada proveedor:
mkdir configs
vi configs/cloudflare.list:
EMAIL:APIKEY
vi configs/donDominio.list:
NAME:ID:PASS
vi configs/godaddy.list:
NAME:KEY:SECRET:ID
vi configs/ovh.list:
NAME:KEY:SECRET:CONSUMER:ID
Luego instalamos las dependencias de Go:
go mod tidy
Y ya estaríamos listos para ejecutar el programa:
go run domainSearcher.go -h
También dejo el código en GitHub donde estará el código actualizado a la última versión.