Updates to allow for enhanced debugging.

Added structs for whois calls.

Added ability to write WhoIs to file.

Added structured output for Whois Records.

Added String Method for WhoIsRecord and WhoIsHistory Records.
This commit is contained in:
Evan Hosinski
2025-05-16 15:33:29 -04:00
parent ef5a8149e1
commit 65c4ea6a15
13 changed files with 1869 additions and 301 deletions
+346 -4
View File
@@ -1,6 +1,10 @@
package sqlite
import "gorm.io/gorm"
import (
"fmt"
"gorm.io/gorm"
"strings"
)
type WhoIsLookupResult struct {
RemainingCredits int `json:"remaining_credits"`
@@ -17,7 +21,7 @@ type WhoisRecord struct {
ContactEmail string `json:"contactEmail"`
CreatedDate string `json:"createdDate"`
CreatedDateNormalized string `json:"createdDateNormalized"`
DomainName string `json:"domainName"`
DomainName string `json:"domainName" gorm:"unique"`
DomainNameExt string `json:"domainNameExt"`
EstimatedDomainAge int `json:"estimatedDomainAge"`
ExpiresDate string `json:"expiresDate"`
@@ -38,6 +42,142 @@ type WhoisRecord struct {
UpdatedDateNormalized string `json:"updatedDateNormalized"`
}
func (w WhoisRecord) String() string {
var sb strings.Builder
// Main domain information
sb.WriteString(fmt.Sprintf("Domain Name: %s\n", w.DomainName))
sb.WriteString(fmt.Sprintf("Domain Name Ext: %s\n", w.DomainNameExt))
sb.WriteString(fmt.Sprintf("Registrar Name: %s\n", w.RegistrarName))
sb.WriteString(fmt.Sprintf("Registrar IANA ID: %s\n", w.RegistrarIANAID))
sb.WriteString(fmt.Sprintf("Contact Email: %s\n", w.ContactEmail))
sb.WriteString(fmt.Sprintf("Estimated Domain Age: %d days\n", w.EstimatedDomainAge))
// Dates
sb.WriteString(fmt.Sprintf("Created Date: %s (Normalized: %s)\n", w.CreatedDate, w.CreatedDateNormalized))
sb.WriteString(fmt.Sprintf("Updated Date: %s (Normalized: %s)\n", w.UpdatedDate, w.UpdatedDateNormalized))
sb.WriteString(fmt.Sprintf("Expires Date: %s (Normalized: %s)\n", w.ExpiresDate, w.ExpiresDateNormalized))
// Status
sb.WriteString(fmt.Sprintf("Status: %s\n", w.Status))
// Parse code
sb.WriteString(fmt.Sprintf("Parse Code: %d\n", w.ParseCode))
// Audit information
sb.WriteString("\nAudit Information:\n")
sb.WriteString(fmt.Sprintf(" Created Date: %s\n", w.Audit.CreatedDate))
sb.WriteString(fmt.Sprintf(" Updated Date: %s\n", w.Audit.UpdatedDate))
// Name servers
sb.WriteString("\nName Servers:\n")
if len(w.NameServers.HostNames) > 0 {
for i, ns := range w.NameServers.HostNames {
ip := ""
if i < len(w.NameServers.IPs) {
ip = fmt.Sprintf(" (%s)", w.NameServers.IPs[i])
}
sb.WriteString(fmt.Sprintf(" %d. %s%s\n", i+1, ns, ip))
}
} else {
sb.WriteString(" None listed\n")
}
if w.NameServers.RawText != "" {
sb.WriteString(fmt.Sprintf(" Raw Text: %s\n", w.NameServers.RawText))
}
// Contact information
sb.WriteString("\nRegistrant Contact:\n")
formatWhoisContact(&sb, w.Registrant, " ")
sb.WriteString("\nTechnical Contact:\n")
formatWhoisContact(&sb, w.TechnicalContact, " ")
// Registry Data
sb.WriteString("\nRegistry Data:\n")
if w.RegistryData.DomainName != "" {
sb.WriteString(fmt.Sprintf(" Domain Name: %s\n", w.RegistryData.DomainName))
sb.WriteString(fmt.Sprintf(" Registrar Name: %s\n", w.RegistryData.RegistrarName))
sb.WriteString(fmt.Sprintf(" Registrar IANA ID: %s\n", w.RegistryData.RegistrarIANAID))
sb.WriteString(fmt.Sprintf(" Whois Server: %s\n", w.RegistryData.WhoisServer))
sb.WriteString(fmt.Sprintf(" Status: %s\n", w.RegistryData.Status))
// Registry dates
sb.WriteString(fmt.Sprintf(" Created Date: %s (Normalized: %s)\n",
w.RegistryData.CreatedDate, w.RegistryData.CreatedDateNormalized))
sb.WriteString(fmt.Sprintf(" Updated Date: %s (Normalized: %s)\n",
w.RegistryData.UpdatedDate, w.RegistryData.UpdatedDateNormalized))
sb.WriteString(fmt.Sprintf(" Expires Date: %s (Normalized: %s)\n",
w.RegistryData.ExpiresDate, w.RegistryData.ExpiresDateNormalized))
// Registry nameservers
sb.WriteString(" Name Servers:\n")
if len(w.RegistryData.NameServers.HostNames) > 0 {
for i, ns := range w.RegistryData.NameServers.HostNames {
ip := ""
if i < len(w.RegistryData.NameServers.IPs) {
ip = fmt.Sprintf(" (%s)", w.RegistryData.NameServers.IPs[i])
}
sb.WriteString(fmt.Sprintf(" %d. %s%s\n", i+1, ns, ip))
}
} else {
sb.WriteString(" None listed\n")
}
// Registry audit
sb.WriteString(" Audit Information:\n")
sb.WriteString(fmt.Sprintf(" Created Date: %s\n", w.RegistryData.Audit.CreatedDate))
sb.WriteString(fmt.Sprintf(" Updated Date: %s\n", w.RegistryData.Audit.UpdatedDate))
} else {
sb.WriteString(" No registry data available\n")
}
// Header and footer
if w.Header != "" {
headerPreview := w.Header
if len(headerPreview) > 100 {
headerPreview = headerPreview[:100] + "... [truncated]"
}
sb.WriteString("\nHeader:\n")
sb.WriteString(headerPreview)
sb.WriteString("\n")
}
if w.Footer != "" {
footerPreview := w.Footer
if len(footerPreview) > 100 {
footerPreview = footerPreview[:100] + "... [truncated]"
}
sb.WriteString("\nFooter:\n")
sb.WriteString(footerPreview)
sb.WriteString("\n")
}
// Raw text (truncated if too long)
if w.RawText != "" {
rawTextPreview := w.RawText
if len(rawTextPreview) > 500 {
rawTextPreview = rawTextPreview[:500] + "... [truncated]"
}
sb.WriteString("\nRaw Text:\n")
sb.WriteString(rawTextPreview)
sb.WriteString("\n")
}
if w.StrippedText != "" {
strippedTextPreview := w.StrippedText
if len(strippedTextPreview) > 500 {
strippedTextPreview = strippedTextPreview[:500] + "... [truncated]"
}
sb.WriteString("\nStripped Text:\n")
sb.WriteString(strippedTextPreview)
sb.WriteString("\n")
}
return sb.String()
}
func (WhoisRecord) TableName() string {
return "whois"
}
@@ -104,7 +244,7 @@ type ScanResult struct {
type SubdomainRecord struct {
gorm.Model
Domain string `json:"domain"`
Domain string `json:"domain" gorm:"unique"`
FirstSeen int64 `json:"firstSeen"`
LastSeen int64 `json:"lastSeen"`
}
@@ -131,7 +271,7 @@ type HistoryRecord struct {
CleanText string `json:"cleanText"`
CreatedDateISO8601 string `json:"createdDateISO8601"`
CreatedDateRaw string `json:"createdDateRaw"`
DomainName string `json:"domainName"`
DomainName string `json:"domainName" gorm:"unique"`
DomainType string `json:"domainType"`
ExpiresDateISO8601 string `json:"expiresDateISO8601"`
ExpiresDateRaw string `json:"expiresDateRaw"`
@@ -147,6 +287,131 @@ type HistoryRecord struct {
ZoneContact ContactInfo `json:"zoneContact" gorm:"serializer:json"`
}
func (h HistoryRecord) String() string {
var sb strings.Builder
// Main domain information
sb.WriteString(fmt.Sprintf("Domain Name: %s\n", h.DomainName))
sb.WriteString(fmt.Sprintf("Domain Type: %s\n", h.DomainType))
sb.WriteString(fmt.Sprintf("Registrar Name: %s\n", h.RegistrarName))
sb.WriteString(fmt.Sprintf("Whois Server: %s\n", h.WhoisServer))
// Dates
sb.WriteString(fmt.Sprintf("Created Date: %s (Raw: %s)\n", h.CreatedDateISO8601, h.CreatedDateRaw))
sb.WriteString(fmt.Sprintf("Updated Date: %s (Raw: %s)\n", h.UpdatedDateISO8601, h.UpdatedDateRaw))
sb.WriteString(fmt.Sprintf("Expires Date: %s (Raw: %s)\n", h.ExpiresDateISO8601, h.ExpiresDateRaw))
// Status
sb.WriteString("Status: ")
if len(h.Status) > 0 {
sb.WriteString(strings.Join(h.Status, ", "))
} else {
sb.WriteString("N/A")
}
sb.WriteString("\n")
// Audit information
sb.WriteString("\nAudit Information:\n")
sb.WriteString(fmt.Sprintf(" Created Date: %s\n", h.Audit.CreatedDate))
sb.WriteString(fmt.Sprintf(" Updated Date: %s\n", h.Audit.UpdatedDate))
// Name servers
sb.WriteString("\nName Servers:\n")
if len(h.NameServers) > 0 {
for i, ns := range h.NameServers {
sb.WriteString(fmt.Sprintf(" %d. %s\n", i+1, ns))
}
} else {
sb.WriteString(" None listed\n")
}
// Contact information
sb.WriteString("\nRegistrant Contact:\n")
formatContact(&sb, h.RegistrantContact, " ")
sb.WriteString("\nAdministrative Contact:\n")
formatContact(&sb, h.AdministrativeContact, " ")
sb.WriteString("\nTechnical Contact:\n")
formatContact(&sb, h.TechnicalContact, " ")
sb.WriteString("\nBilling Contact:\n")
formatContact(&sb, h.BillingContact, " ")
sb.WriteString("\nZone Contact:\n")
formatContact(&sb, h.ZoneContact, " ")
// Raw text (truncated if too long)
if len(h.RawText) > 0 {
rawTextPreview := h.RawText
if len(rawTextPreview) > 500 {
rawTextPreview = rawTextPreview[:500] + "... [truncated]"
}
sb.WriteString("\nRaw Text:\n")
sb.WriteString(rawTextPreview)
sb.WriteString("\n")
}
if len(h.CleanText) > 0 {
cleanTextPreview := h.CleanText
if len(cleanTextPreview) > 500 {
cleanTextPreview = cleanTextPreview[:500] + "... [truncated]"
}
sb.WriteString("\nClean Text:\n")
sb.WriteString(cleanTextPreview)
sb.WriteString("\n")
}
return sb.String()
}
// Helper function to format contact information
func formatContact(sb *strings.Builder, contact ContactInfo, indent string) {
if contact.Name == "" && contact.Organization == "" && contact.Email == "" {
sb.WriteString(indent + "No contact information available\n")
return
}
if contact.Name != "" {
sb.WriteString(indent + "Name: " + contact.Name + "\n")
}
if contact.Organization != "" {
sb.WriteString(indent + "Organization: " + contact.Organization + "\n")
}
if contact.Email != "" {
sb.WriteString(indent + "Email: " + contact.Email + "\n")
}
if contact.Street != "" {
sb.WriteString(indent + "Street: " + contact.Street + "\n")
}
if contact.City != "" {
sb.WriteString(indent + "City: " + contact.City + "\n")
}
if contact.State != "" {
sb.WriteString(indent + "State: " + contact.State + "\n")
}
if contact.PostalCode != "" {
sb.WriteString(indent + "Postal Code: " + contact.PostalCode + "\n")
}
if contact.Country != "" {
sb.WriteString(indent + "Country: " + contact.Country + "\n")
}
if contact.Telephone != "" {
phone := contact.Telephone
if contact.TelephoneExt != "" {
phone += " ext. " + contact.TelephoneExt
}
sb.WriteString(indent + "Telephone: " + phone + "\n")
}
if contact.Fax != "" {
fax := contact.Fax
if contact.FaxExt != "" {
fax += " ext. " + contact.FaxExt
}
sb.WriteString(indent + "Fax: " + fax + "\n")
}
}
func (HistoryRecord) TableName() string {
return "history"
}
@@ -170,3 +435,80 @@ type ContactInfo struct {
type WhoIsCredits struct {
WhoisCredits int `json:"whois_credits"`
}
type WhoIsIPLookup struct {
RemainingCredits int `json:"remaining_credits"`
Data IPData `json:"data"`
}
type WhoIsMXLookup struct {
RemainingCredits int `json:"remaining_credits"`
Data IPData `json:"data"`
}
type WhoIsNSLookup struct {
RemainingCredits int `json:"remaining_credits"`
Data IPData `json:"data"`
}
type IPData struct {
CurrentPage string `json:"current_page"`
Result []LookupResult `json:"result"`
Size int `json:"size"`
}
type LookupResult struct {
gorm.Model
FirstSeen int64 `json:"first_seen"`
LastVisit int64 `json:"last_visit"`
Name string `json:"name" gorm:"unique"`
SearchTerm string `json:"search_term,omitempty"` // For storing the IP address this domain is associated with
Type string `json:"type,omitempty"` // For storing the MX address this domain is associated with
}
func (LookupResult) TableName() string {
return "lookup"
}
// Helper function to format contact information for WhoisRecord
func formatWhoisContact(sb *strings.Builder, contact Contact, indent string) {
if contact.Name == "" && contact.Organization == "" {
sb.WriteString(indent + "No contact information available\n")
return
}
if contact.Name != "" {
sb.WriteString(indent + "Name: " + contact.Name + "\n")
}
if contact.Organization != "" {
sb.WriteString(indent + "Organization: " + contact.Organization + "\n")
}
if contact.Street1 != "" {
sb.WriteString(indent + "Street: " + contact.Street1 + "\n")
}
if contact.City != "" {
sb.WriteString(indent + "City: " + contact.City + "\n")
}
if contact.State != "" {
sb.WriteString(indent + "State: " + contact.State + "\n")
}
if contact.PostalCode != "" {
sb.WriteString(indent + "Postal Code: " + contact.PostalCode + "\n")
}
if contact.Country != "" {
sb.WriteString(indent + "Country: " + contact.Country + "\n")
}
if contact.CountryCode != "" {
sb.WriteString(indent + "Country Code: " + contact.CountryCode + "\n")
}
if contact.Telephone != "" {
sb.WriteString(indent + "Telephone: " + contact.Telephone + "\n")
}
if contact.RawText != "" {
rawTextPreview := contact.RawText
if len(rawTextPreview) > 100 {
rawTextPreview = rawTextPreview[:100] + "... [truncated]"
}
sb.WriteString(indent + "Raw Text: " + rawTextPreview + "\n")
}
}