Consultando información de Whois con PHP

Camilo Herrera
winkhostingla
Published in
12 min readFeb 28, 2023
Photo by Jametlene Reskp on Unsplash

El maravilloso mundo del Whois!, si no sabes qué es o para que sirve, probablemente esta no sea la publicación para ti. Pero si sabes de que hablo, entonces te va a emocionar saber que puedes dejar de usar servicios de terceros para consultar esta información, con un poco de esfuerzo y amor por la programación puedes crear tu propio servicio! (estoy tratando de ponerle emoción a una consulta de información que, por más necesaria, es bastante corriente).

Vamos!, acompáñame de nuevo a recorrer el camino de ladrillos amarill… bueno ya.

Photo by Akshay Nanavati on Unsplash

Qué es el Whois?

El Whois es un servicio público que te permite consultar quién es el dueño de un dominio, su estado, fechas de vencimiento, renovación y otros datos de interés.

Puedes usar una consulta Whois para determinar si un dominio está disponible para ser comprado, por ejemplo. (Advertencia: Tu kilometraje puede variar, el whois generalmente es bastante confiable pero puede producir resultados que no esperas), úsalo con precaución.

Dónde consultar los servidores Whois de un TLD?

IANA (la entidad encargada de regular la asignación de direcciones IP y DNS raíz para resolución de direcciones) cuenta con una lista en el siguiente enlace:

Esta lista nos servirá más adelante para hacer scraping y extraer información necesaria para nuestro script PHP de consulta de Whois.

Advertencia: El scraping en si mismo no es mal visto, pero mal utilizado le arruina la fiesta a todos, siempre sé muy responsable y respetuoso con respecto a la información que consultas usando esta técnica.

Estructura de nuestra solución

Nuestro directorio y archivos de solución serán los siguientes:

/whois           # directorio raíz de la solución (tu servidor web debe tener
# acceso a él.
/cache # este directorio guardará archivos json de cache con los
# resultados de consultas de servidores whois en iana.org
- index.php # punto de entrada de nuestra solución e interfaz web
# para consulta.
- getwhois.php # script que recibirá las consultas desde index.php y
# retornará el resultado.
- Whois.php # archivo con la definición de nuestra clase lista para usar
# con las rutinas para consultar el whois.

Recuerda que debes tener un entorno de desarrollo web con PHP ó por lo menos que puedas ejecutar scripts php a través de una consola de comandos. En esta oportunidad usaremos PHP 8.2 en un servidor web Apache.

Plantearemos nuestro código a partir de tres grandes bloques así:

  • Scraping
  • Consulta de Whois
  • Interfaz y presentación de resultados

1. Scraping

Vamos a hacer un poco de scraping para extraer el servidor Whois de cada tipo de dominio (TLD) disponible en https://www.iana.org/domains/root/db. Lo haremos “on demand”, es decir, que sólo realizaremos el scraping de ser necesario, es decir, cuando no tengamos un archivo de cache previo para el TLD consultado, así reduciremos el tráfico, respetaremos el sitio de donde viene la información y los tiempos de respuesta serán menores.

Por ejemplo, vamos a visitar la información del TLD .com, el URL es https://www.iana.org/domains/root/db/com.html, será mostrada información general de contacto y en la parte inferior el servidor Whois para este tipo de dominios, así:

iana.org whois

La dirección junto al texto “WHOIS Server” será el dato de interés para nuestro proceso de scrape.

El primer paso será realizar una petición al sitio web que contienen la información requerida y capturar el HTML de la respuesta. Esto podemos hacerlo con nuestro querido amigo cURL así:

    /**
* Esta función descarga el contenido HTML a partir del URL de iana.org
* específico para el TLD consultado.
*
* @param string $url URL en iana.org a ser consultado con la información
* de servidores de whois del TLD.
*
* @return string|bool HTML recibido en la respuesta del sitio web de
* iana.org, si se presenta un error retornará false
*/
function curlDownload(string $url): string|bool
{

$curl = curl_init();

curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 60);
curl_setopt($curl, CURLOPT_HEADER, 0);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "GET");
curl_setopt($curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36");
curl_setopt($curl, CURLOPT_URL, $url);

$html = curl_exec($curl);
curl_close($curl);

return $html;
}

En el header User-Agent, fijamos un valor que simule una visita desde un navegador de escritorio, acontinuación, es necesario extraer del HTML recibido la dirección del servidor de whois, esto podemos hacerlo con una herramienta muy útil, pero que a muchos les genera pánico o un odio inmenso en lo más profundo de su ser, expresiones regulares.

Sin importar cómo te sientas con respecto a ellas siempre procura recordar las sabias palabras de “Macho man” Randy Savage:

You may not like it, but accept it! — “Macho Man” Randy Savage wisdom

Vamos a revisar el contenido y a crear la expresión regular, ten presente que no vamos a cubrir en detalle su funcionamiento, cubriremos lo importante, que funcione para nuestro objetivo presente.

Veamos el código fuente de la página donde se encuentra el nombre del servidor whois. La sección que nos interesa tiene esta estructura:

    <p>

<b>URL for registration services:</b> <a href="http://www.verisigninc.com">http://www.verisigninc.com</a><br/>


<b>WHOIS Server:</b> whois.verisign-grs.com

</p>

Ahora vamos a crear una regex para extraer justo el texto “whois.verisign-grs.com”, será algo como esto:

$this->regexWhoisServer = '#(?s)(?<=\<b\>WHOIS Server\:\<\/b\>)(.+?)(?=\<\/p\>)#';

En términos generales, lo que hace esta expresión es buscar el texto entre el patrón de texto “<b>WHOIS Server:</b>” y la primera coincidencia con el texto “</p>”, usando PHP podemos capturar el valor retornado y guardarlo en una variable para usarlo posteriormente en nuestra consulta de whois.

Nuestro código de ejemplo para entender este concepto se verá así:

//Arreglo que almacenará los resultados del regex.
$matchesWhois = array();

//Usamos nuestra función de descarga del html del URL
$html = curlDownload("https://www.iana.org/domains/root/db/com.html");
//Usamos nuestra regex para extraer el nombre del servidor y lo guardamos en $resWhois
$resWhois = preg_match("#(?s)(?<=\<b\>WHOIS Server\:\<\/b\>)(.+?)(?=\<\/p\>)#", $html, $matchesWhois, PREG_OFFSET_CAPTURE);

//Quitamos espacios en blanco al inicio y final del resultado, en este caso
//usamos la coincidencia guardada en la posición [0][0] del arreglo de resultados
//del regex.
$matchesWhois[0][0] = trim($matchesWhois[0][0]);

//Asignamos el nombre del servidor encontrado a una variable con un nombre más apropiado.
$whoisServer = $matchesWhois[0][0];

2. Consulta de Whois

Ya logramos consultar el nombre del servidor al que le realizaremos la petición de whois, ahora resta implementar el código para hacer la consulta, para esto abrimos una conexión a través de sockets y enviamos el nombre del dominio para recibir una respuesta con los datos de whois, así:

//Abrimos la conexión al servidor whois en el puerto 43 con 20 segundos de timeout.
$whoisSock = @fsockopen("whois.verisign-grs.com", 43, $errno, $errstr, 20);
//Esta variable será usada para almacenar la respuesta del servidor.
$whoisQueryResult = "";

//Enviamos el nombre del dominio y finalizamos con un salto de línea.
fputs($whoisSock, "mytestdomain.com" . "\r\n");

$content = "";

//Leemos la respuesta del servidor.
while (!feof($whoisSock)) {
$content .= fgets($whoisSock);
}

//Cerramos el socket.
fclose($whoisSock);

//Convertimos la respuesta del servidor en un arreglo de cadenas de texto
//separadas por salto de línea.
$arrResponseLines = explode("\n", $content);

foreach ($arrResponseLines as $line) {

$line = trim($line);

//Si la línea está vacía, inicia con # ó % la ignoramos.
if (($line != '') && (!str_starts_with($line, '#')) && (!str_starts_with($line, '%'))) {
//Guardamos la línea en la variable designada para el resultado de la consulta.
$whoisQueryResult .= $line . PHP_EOL;
}
}

//Mostramos en pantalla el resultado.
echo $whoisQueryResult;

Ahora que contamos con el resultado, vamos a generar el código para consultar y mostrar el resultado en web.

3. Interfaz y presentación de resultados

Para consultar y presentar resultados vamos a crear la clase de Whois que integra los conceptos mostrados previamente, un archivo para recibir las peticiones de consulta y la interfaz web para consultar y mostrar los resultados. Iniciemos con nuestra clase, la llamaremos Whois.php y tiene la siguiente estructura:

<?php

class Whois
{
//Coincidencias de regex para dominio de consulta whois
private array $matchesWhois;
//Ruta de archivos de cache para las consultas de whois
private string $_CACHE_PATH;
//Expresión regular para detectar el servidor whois de un TLD
private string $regexWhoisServer;
//Extensión de los archivos de cache de whois
private string $_FILE_EXT;
//True = indica que se está usando la información de servidores de un archivo de cache para realizar la consulta de whois
private bool $usingCache;
//Nombre del dominio a consultar
private string $domain;
//Extensión del dominio consultado
private string $tld;
//Nombre del archivo de cache usado para determinar el servidor de whois a consultar
private string $cacheFile;
//URL de consulta (scrape) de la información de servidores de whois de un dominio
private string $urlWhoisDB;
//Arreglo con la respuesta y errores reportados durante el proceso de consulta
private array $response;
//Arreglo que contiene los datos de servidores de nombre y de whois de un dominio
private array $whoisInfo;
//Tag a ser sustituído dentro del URL de consulta de información del servidor de whois en iana.org
private string $tldUrlTag;
//Puerto de conexión para la consulta de WhoIs, por defecto 43
private int $_WHOIS_PORT;
//Tiempo de espera de consulta de información de whois de un dominio, por defecto 20
private int $_WHOIS_TIMEOUT;
//User Agent a usar en las cabeceras de conexión de cURL (simulamos ser un navegador para hacer el scrape)
private string $_CURL_USER_AGENT;


/**
* Constructor de la clase, aquí fijamos los valores por defecto para
* nuestras variables.
*/
public function __construct()
{
$this->matchesWhois = array();
$this->whoisInfo = array();
$this->_CACHE_PATH = __DIR__ . "/cache/";
$this->regexWhoisServer = '#(?s)(?<=\<b\>WHOIS Server\:\<\/b\>)(.+?)(?=\<\/p\>)#';
$this->_FILE_EXT = ".json";
$this->usingCache = false;
$this->domain = "";
$this->tld = "";
$this->cacheFile = "";
$this->tldUrlTag = "[TLD]";
$this->urlWhoisDB = "https://www.iana.org/domains/root/db/{$this->tldUrlTag}.html";
$this->response = array();
$this->_WHOIS_PORT = 43;
$this->_WHOIS_TIMEOUT = 20;
$this->_CURL_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36";
}


/**
* Esta función permite determinar si el dominio cumple con la estructura de nombre de una dirección con su respectivo TLD.
*
* @param string $domain nombre completo del dominio a verificar.
*
* @return bool
*/
public function isDomain(string $domain): bool
{
return filter_var($domain, FILTER_VALIDATE_DOMAIN);
}


/**
* Permite extraer la extensión (TLD) de un dominio.
*
* @param mixed $domain Nombre de dominio al que se le extraerá la extensión (TLD)
*
* @return string
*/
private function extractTld($domain): string
{
$arrDomain = explode(".", $domain);

return end($arrDomain);
}

/**
* Esta función fija el nombre del archivo de cache de información de los servidores de whois encontrados,
* también determina si el archivo ya existe y carga la información para usarla al hacer la consulta.
*
* @param mixed $tld Extensión de dominio (TLD), ej. "com", "net", "org".
*
* @return void
*/
private function setCacheFileName($tld): void
{
$this->cacheFile = $this->_CACHE_PATH . $tld . $this->_FILE_EXT;

if (file_exists($this->cacheFile)) {

$tmpCache = file_get_contents($this->cacheFile);
$this->whoisInfo = json_decode($tmpCache, true);

$this->usingCache = true;
}
}

/**
* Indica si se presentaron errores en la consulta realizada.
*
* @return bool true = se presentaron errores, false = no hay errores
*/
public function hasErrors(): bool
{
return isset($this->response["errors"]);
}

/**
* @param bool $json Permite solicitar la información en formato JSON o arreglo. true = la información es
* retornada en formato JSON, false = arreglo.
*
* @return array|string
*/
public function getResponse(bool $json = false): array|string
{
return ($json) ? json_encode($this->response) : $this->response;
}

/**
* Esta función descarga el contenido HTML a partir del URL de iana.org específico para el TLD consultado.
*
* @param string $url URL en iana.org a ser consultado con la información de servidores de whois del TLD.
*
* @return string|bool HTML recibido en la respuesta del sitio web de iana.org, si se presenta un error retornará false
*/
private function curlDownload(string $url): string|bool
{

$curl = curl_init();

curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 60);
curl_setopt($curl, CURLOPT_HEADER, 0);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "GET");
curl_setopt($curl, CURLOPT_USERAGENT, $this->_CURL_USER_AGENT);
curl_setopt($curl, CURLOPT_URL, $url);

$html = curl_exec($curl);
curl_close($curl);

return $html;
}

/**
* Punto de entrada de la consulta de whois, recibe un nombre de dominio y consulta los servidores a usar y a continuación
* la información de whois.
*
* @param string $domain Cadena de texto que contiene el nombre de dominio a consultar.
*
* @return void
*/
public function getWhoisServerDetails(string $domain): void
{

$this->domain = $domain;
$this->tld = $this->extractTld($domain);
$this->setCacheFileName($this->tld);

if (!$this->usingCache) {

$urlWhoisDB = str_replace($this->tldUrlTag, $this->tld, $this->urlWhoisDB);
$html = $this->curlDownload($urlWhoisDB);

$resWhois = preg_match($this->regexWhoisServer, $html, $this->matchesWhois, PREG_OFFSET_CAPTURE);

if ($resWhois != 1) {

$this->response["errors"][] = array(
"error" => "TLD '{$this->tld}' not found!",
"domain" => $domain
);

return;
}

$this->matchesWhois[0][0] = trim($this->matchesWhois[0][0]);
$this->whoisInfo["whois"] = $this->matchesWhois[0][0];

file_put_contents($this->_CACHE_PATH . $this->tld . $this->_FILE_EXT, json_encode($this->whoisInfo, JSON_UNESCAPED_UNICODE));
}

if (!isset($this->whoisInfo["whois"])) {

$this->response["errors"][] = array(
"error" => "WhoIs Server for TLD {$this->tld} not found!.",
"domain" => $domain
);

return;
}

$whoisSock = @fsockopen($this->whoisInfo["whois"], $this->_WHOIS_PORT, $errno, $errstr, $this->_WHOIS_TIMEOUT);
$whoisQueryResult = "";

if (!$whoisSock) {

$this->response["errors"][] = array(
"error" => "{$errstr} ({$errno})",
"domain" => $domain
);

return;
}

fputs($whoisSock, $this->domain . "\r\n");

$content = "";

while (!feof($whoisSock)) {
$content .= fgets($whoisSock);
}

fclose($whoisSock);

if ((strpos(strtolower($content), "error") === false) && (strpos(strtolower($content), "not allocated") === false)) {

$arrResponseLines = explode("\n", $content);

foreach ($arrResponseLines as $line) {

$line = trim($line);

if (($line != '') && (!str_starts_with($line, '#')) && (!str_starts_with($line, '%'))) {
$whoisQueryResult .= $line . PHP_EOL;
}
}
}

$this->response["whoisinfo"] = $whoisQueryResult;
}
}

En esta clase tendremos todas las funciones principales para scrape, extracción de nombre del servidor whois, procesamiento del nombre de dominio y consulta de whois final. Ten presente que se añadieron rutinas de verificación y control de errores, así como funciones para automatizar el scrape a partir del tipo de dominio (TLD) consultado y la estrategia de caché para evitar hacer más consultas de las necesarias a iana.org.

Ahora vamos a crear nuestro archivo que recibirá las peticiones de consulta, lo llamaremos getwhois.php y tendrá el siguiente contenido:

<?php

//Se incluye la definición de la clase Whois a usar para nuestra consulta.
require("Whois.php");

//Decodificamos los parámetros recibidos desde el archivo index.html y los almacenamos en el arreglo $paramsFetch
$paramsFetch = json_decode(
file_get_contents("php://input"),
true
);

//instanciamos nuestra clase
$whoisObj = new Whois();
//Realizamos la consulta de WhoIs
$whoisObj->getWhoisServerDetails($paramsFetch["domain"]);

//A continuación retornamos la respuesta en formato JSON y finalizamos la ejecución.
echo $whoisObj->getResponse(true);
exit;

Es bastante simple, incluye nuestra clase Whois.php, captura los parámetros recibidos desde un formulario HTML que usa la función fetch de javascript para enviar la petición con el nombre del dominio, crea una instancia de nuestra clase y hace la consulta de información de whois, a continuación retorna el resultado en formato JSON y finaliza la ejecución.

Ahora vamos al archivo index.html, este será nuestra interfaz gráfica y el punto de acceso a la consulta y visualización de resultados. Uso Bulma CSS para los controles html, es bastante sencilla, no es intrusiva y puedes generar resultados rápidamente. El archivo quedará así:

<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Whois</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
<script type="module">
window.addEventListener('load', (event) => {
//Event Handler para el evento click del botón de búsqueda.
document.querySelector(".search").addEventListener('click', (event) => {
//Mostramos que la consulta esá en ejecución
event.currentTarget.classList.add('is-loading');
//Bloqueamos el botón
event.currentTarget.disabled = true;

//Ocultamos las secciones de resultados
document.querySelector(".result").parentElement.classList.add("is-hidden");
document.querySelector(".error").parentElement.classList.add("is-hidden");

//Preparamos el payload para hacer la consulta.
const payload = JSON.stringify({
"domain": document.querySelector(".domain").value
});

//Enviamos nuestra consulta al script getwhois.php
fetch('getwhois.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: payload,
})
.then(response => response.json())
.then(data => {
//Procesamos la respuesta recibida.
if (data.errors != undefined) {
document.querySelector(".error").parentElement.classList.remove("is-hidden");

for (const item in data.errors) {
document.querySelector(".error").innerText = data.errors[item].error + "\n";
}

} else {

document.querySelector(".result").parentElement.classList.remove("is-hidden");
document.querySelector(".result").innerText = data.whoisinfo;
}

})
.catch((error) => {
document.querySelector(".error").parentElement.classList.remove("is-hidden");
document.querySelector(".error").innerText = error;
console.error('Error:', error);
}).finally(() => {
document.querySelector(".search").classList.remove('is-loading');
document.querySelector(".search").disabled = false;
});
});

});
</script>
</head>

<body>
<section class="section">
<div class="container">
<div class="columns">
<div class="column">
<div class="columns">
<div class="column"></div>
<div class="column has-text-centered">
<h1 class="title">
WhoIs Lookup
</h1>
</div>
<div class="column"></div>
</div>
<div class="columns">
<div class="column"></div>
<div class="column has-text-centered">
<div class="field is-grouped is-grouped-centered">
<div class="control">
<input class="input domain" type="text" placeholder="Domain">
</div>
<div class="control">
<button class="button is-info search">
Search
</button>
</div>
</div>
</div>
<div class="column"></div>
</div>
</div>
</div>
<div class="columns box is-hidden">
<div class="column result"></div>
</div>
<div class="columns box is-hidden">
<div class="column notification is-danger error has-text-centered">
</div>
</div>
</div>
</section>
</body>

</html>

Pruebas

Para realizar pruebas sólo es necesario ingresar con el navegador a la ruta donde se encuentran nuestros scripts, en mi caso http://localhost/whois, al ingresar será mostrado el campo para escribir el nombre de dominio y el botón “Search” para solicitar la información de Whois. puedes probar con un dominio popular como “google.com”, el resultado se verá así:

Al hacer una consulta, notarás que en el directorio /cache que definimos en la estructura de nuestra solución, será creado un archivo con el TLD, por ejemplo “com.json” y contendrá el nombre del servidor whois correspondiente, esto nos permitirá evitar consultar y hacer nuevamente el scrape de un tipo de dominio que consultamos previamente.

Y es todo, nunca olvides que aquel que es valiente es realmente libre, toma mucho líquido, haz deporte y duerme lo suficiente.

En Winkhosting.co somos mucho más que hosting. Si necesitas alojamiento compartido, dominios o servidores, pasa a visitarnos.

--

--