Esta pagina se ve mejor con JavaScript habilitado

OVH-Cloudflare-GoDaddy-DonDominio NS/Whois search system

 ·  🎃 kr0m

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.

Si te ha gustado el artículo puedes invitarme a un RedBull aquí