whoissdk/whois.go

259 lines
5.4 KiB
Go
Raw Normal View History

2024-08-15 08:38:28 +08:00
package whois
import (
"errors"
"fmt"
"io"
"net"
"strconv"
"strings"
"time"
"golang.org/x/net/proxy"
)
const (
defWhoisServer = "whois.iana.org"
defWhoisPort = "43"
defTimeout = 30 * time.Second
asnPrefix = "AS"
)
// DefaultClient is default whois client
var DefaultClient = NewClient()
// Client is whois client
type Client struct {
dialer proxy.Dialer
timeout time.Duration
elapsed time.Duration
disableReferral bool
}
type PersonalInfo struct {
FirstName string
LastName string
Name string
Org string
Fax string
FaxExt string
Addr string
City string
State string
Country string
Zip string
Phone string
PhoneExt string
Email string
}
type Result struct {
exists bool
domain string
domainID string
rawData string
registar string
registerDate time.Time
updateDate time.Time
expireDate time.Time
statusRaw string
nsServers []string
dnssec string
whoisSer string
ianaID string
registerInfo PersonalInfo
adminInfo PersonalInfo
techInfo PersonalInfo
}
// NewClient returns new whois client
func NewClient() *Client {
return &Client{
dialer: &net.Dialer{
Timeout: defTimeout,
},
timeout: defTimeout,
}
}
func (c *Client) Whois(domain string, servers ...string) (string, error) {
data, err := c.whois(domain)
if err != nil {
return data, err
}
parse(domain, data)
return "", err
}
func (c *Client) whois(domain string, servers ...string) (result string, err error) {
start := time.Now()
defer func() {
result = strings.TrimSpace(result)
if result != "" {
result = fmt.Sprintf("%s\n\n%% Query time: %d msec\n%% WHEN: %s\n",
result, time.Since(start).Milliseconds(), start.Format("Mon Jan 02 15:04:05 MST 2006"),
)
}
}()
domain = strings.Trim(strings.TrimSpace(domain), ".")
if domain == "" {
return "", errors.New("whois: domain is empty")
}
isASN := IsASN(domain)
if isASN {
if !strings.HasPrefix(strings.ToUpper(domain), asnPrefix) {
domain = asnPrefix + domain
}
}
if !strings.Contains(domain, ".") && !strings.Contains(domain, ":") && !isASN {
return c.rawQuery(domain, defWhoisServer, defWhoisPort)
}
var server, port string
if len(servers) > 0 && servers[0] != "" {
server = strings.ToLower(servers[0])
port = defWhoisPort
} else {
ext := getExtension(domain)
result, err := c.rawQuery(ext, defWhoisServer, defWhoisPort)
if err != nil {
return "", fmt.Errorf("whois: query for whois server failed: %w", err)
}
server, port = getServer(result)
if server == "" {
return "", fmt.Errorf("%w: %s", errors.New("whois server not found"), domain)
}
}
result, err = c.rawQuery(domain, server, port)
if err != nil {
return
}
if c.disableReferral {
return
}
refServer, refPort := getServer(result)
if refServer == "" || refServer == server {
return
}
data, err := c.rawQuery(domain, refServer, refPort)
if err == nil {
result += data
}
return
}
// SetDialer set query net dialer
func (c *Client) SetDialer(dialer proxy.Dialer) *Client {
c.dialer = dialer
return c
}
// SetTimeout set query timeout
func (c *Client) SetTimeout(timeout time.Duration) *Client {
c.timeout = timeout
return c
}
// rawQuery do raw query to the server
func (c *Client) rawQuery(domain, server, port string) (string, error) {
c.elapsed = 0
start := time.Now()
if server == "whois.arin.net" {
if IsASN(domain) {
domain = "a + " + domain
} else {
domain = "n + " + domain
}
}
conn, err := c.dialer.Dial("tcp", net.JoinHostPort(server, port))
if err != nil {
return "", fmt.Errorf("whois: connect to whois server failed: %w", err)
}
defer conn.Close()
c.elapsed = time.Since(start)
_ = conn.SetWriteDeadline(time.Now().Add(c.timeout - c.elapsed))
_, err = conn.Write([]byte(domain + "\r\n"))
if err != nil {
return "", fmt.Errorf("whois: send to whois server failed: %w", err)
}
c.elapsed = time.Since(start)
_ = conn.SetReadDeadline(time.Now().Add(c.timeout - c.elapsed))
buffer, err := io.ReadAll(conn)
if err != nil {
return "", fmt.Errorf("whois: read from whois server failed: %w", err)
}
c.elapsed = time.Since(start)
return string(buffer), nil
}
// getExtension returns extension of domain
func getExtension(domain string) string {
ext := domain
if net.ParseIP(domain) == nil {
domains := strings.Split(domain, ".")
ext = domains[len(domains)-1]
}
if strings.Contains(ext, "/") {
ext = strings.Split(ext, "/")[0]
}
return ext
}
// getServer returns server from whois data
func getServer(data string) (string, string) {
tokens := []string{
"Registrar WHOIS Server: ",
"whois: ",
"ReferralServer: ",
"refer: ",
}
for _, token := range tokens {
start := strings.Index(data, token)
if start != -1 {
start += len(token)
end := strings.Index(data[start:], "\n")
server := strings.TrimSpace(data[start : start+end])
server = strings.TrimPrefix(server, "http:")
server = strings.TrimPrefix(server, "https:")
server = strings.TrimPrefix(server, "whois:")
server = strings.TrimPrefix(server, "rwhois:")
server = strings.Trim(server, "/")
port := defWhoisPort
if strings.Contains(server, ":") {
v := strings.Split(server, ":")
server, port = v[0], v[1]
}
return server, port
}
}
return "", ""
}
// IsASN returns if s is ASN
func IsASN(s string) bool {
s = strings.ToUpper(s)
s = strings.TrimPrefix(s, asnPrefix)
_, err := strconv.Atoi(s)
return err == nil
}