Skip to content

Instantly share code, notes, and snippets.

@tomasbasham
Created September 5, 2025 18:40
Show Gist options
  • Select an option

  • Save tomasbasham/d8f4e2fcc17844a0e66a5b89bbde464c to your computer and use it in GitHub Desktop.

Select an option

Save tomasbasham/d8f4e2fcc17844a0e66a5b89bbde464c to your computer and use it in GitHub Desktop.
Naive implementation of a RDAP client.
#!/usr/bin/env bash
set -euo pipefail
# The general approach is outlined in the following StackOverflow approach
# https://stackoverflow.com/a/60584381/1613695
# This script was generated with AI to demonstrate the approach. It is certainly
# not producton ready.
# Configuration
IANA_RDAP_URL="http://data.iana.org/rdap/dns.json"
CACHE_FILE="/tmp/rdap_dns_cache.json"
CACHE_DURATION=3600 # 1 hour in seconds
# Colours for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Colour
usage() {
cat << EOF
Usage: $0 <domain> [tld]
Perform RDAP lookup for a domain by finding the authoritative RDAP server.
Arguments:
domain The domain name to lookup (e.g., example.com or just example)
tld The TLD (e.g., com, org.uk) - optional if domain includes TLD
Examples:
$0 example.com
$0 example com
$0 example.co.uk
$0 example co.uk
The script will automatically attempt to extract the TLD from the domain
if only one argument is provided.
EOF
}
log() {
echo -e "${BLUE}[INFO]${NC} $1" >&2
}
warn() {
echo -e "${YELLOW}[WARN]${NC} $1" >&2
}
error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
}
success() {
echo -e "${GREEN}[SUCCESS]${NC} $1" >&2
}
# Function to extract TLD from domain using common patterns
# This is a simplified approach - not perfect but covers most cases
extract_tld() {
local domain="$1"
# Handle common multi-part TLDs first
if [[ "$domain" =~ \.(co\.uk|org\.uk|me\.uk|ltd\.uk|plc\.uk|net\.uk)$ ]]; then
echo "${BASH_REMATCH[1]}"
return
elif [[ "$domain" =~ \.(com\.au|net\.au|org\.au|edu\.au|gov\.au|asn\.au|id\.au)$ ]]; then
echo "${BASH_REMATCH[1]}"
return
elif [[ "$domain" =~ \.(co\.nz|net\.nz|org\.nz|govt\.nz|mil\.nz|iwi\.nz|geek\.nz|gen\.nz|kiwi\.nz|maori\.nz|school\.nz)$ ]]; then
echo "${BASH_REMATCH[1]}"
return
elif [[ "$domain" =~ \.(com\.br|net\.br|org\.br|gov\.br|edu\.br|mil\.br)$ ]]; then
echo "${BASH_REMATCH[1]}"
return
fi
# Handle single-part TLDs
if [[ "$domain" =~ \.([a-zA-Z0-9-]+)$ ]]; then
echo "${BASH_REMATCH[1]}"
return
fi
# If no TLD found, return empty
echo ""
}
# Function to get domain name without TLD
get_domain_name() {
local full_domain="$1"
local tld="$2"
# Remove the TLD from the end
echo "${full_domain%.$tld}"
}
# Function to fetch and cache RDAP DNS data
get_rdap_data() {
local cache_file="$1"
# Check if cache exists and is fresh
if [[ -f "$cache_file" ]]; then
local cache_age=$(($(date +%s) - $(stat -c %Y "$cache_file" 2>/dev/null || stat -f %m "$cache_file" 2>/dev/null || echo 0)))
if [[ $cache_age -lt $CACHE_DURATION ]]; then
log "Using cached RDAP data"
cat "$cache_file"
return
fi
fi
log "Fetching RDAP data from IANA..."
if curl -sf "$IANA_RDAP_URL" > "$cache_file"; then
cat "$cache_file"
else
error "Failed to fetch RDAP data from IANA"
exit 1
fi
}
# Function to find RDAP server for a given TLD
find_rdap_server() {
local tld="$1"
local rdap_data="$2"
log "Looking for RDAP server for TLD: $tld"
# Use jq to find the RDAP server for the given TLD
local server=$(echo "$rdap_data" | jq -r --arg tld "$tld" '
.services[] |
select(.[0][] | test("^" + $tld + "$"; "i")) |
.[1][0]
')
if [[ -n "$server" && "$server" != "null" ]]; then
echo "$server"
else
echo ""
fi
}
# Function to extract related links from RDAP response
extract_related_links() {
local rdap_response="$1"
echo "$rdap_response" | jq -r '
.links[]? |
select(.rel == "related") |
.href
' 2>/dev/null || echo ""
}
# Function to merge RDAP responses
merge_rdap_responses() {
local base_response="$1"
local additional_response="$2"
# Simple merge - just combine the objects, additional data wins for conflicts
echo "$base_response $additional_response" | jq -s '.[0] * .[1]'
}
# Function to perform recursive RDAP lookup
perform_recursive_rdap_lookup() {
local domain="$1"
local rdap_server="$2"
local visited_urls="$3"
local depth="${4:-0}"
local max_depth=5
# Prevent infinite recursion
if [[ $depth -ge $max_depth ]]; then
warn "Maximum recursion depth ($max_depth) reached, stopping"
echo "{}"
return 0
fi
# Construct the RDAP URL
local rdap_url="${rdap_server%/}/domain/$domain"
# Check if we've already visited this URL to prevent loops
if [[ "$visited_urls" == *"$rdap_url"* ]]; then
warn "Already visited $rdap_url, skipping to prevent loop"
echo "{}"
return 0
fi
# Add current URL to visited list
visited_urls="$visited_urls $rdap_url"
log "Performing RDAP lookup (depth $depth): $rdap_url"
# Perform the RDAP lookup
local rdap_response
if rdap_response=$(curl -sf -H "Accept: application/rdap+json" "$rdap_url" 2>/dev/null); then
log "RDAP lookup successful at depth $depth"
# Extract related links for recursive lookups
local related_links
related_links=$(extract_related_links "$rdap_response")
# Start with the base response
local merged_response="$rdap_response"
# Perform recursive lookups for related links
if [[ -n "$related_links" ]]; then
while IFS= read -r link; do
if [[ -n "$link" && "$link" != "null" ]]; then
log "Following related link at depth $depth: $link"
# Perform recursive lookup
local recursive_response
if recursive_response=$(curl -sf -H "Accept: application/rdap+json" "$link" 2>/dev/null); then
log "Successfully retrieved data from related link"
# Merge the responses
merged_response=$(merge_rdap_responses "$merged_response" "$recursive_response")
# Check for further related links in the recursive response
local further_links
further_links=$(extract_related_links "$recursive_response")
if [[ -n "$further_links" ]]; then
while IFS= read -r further_link; do
if [[ -n "$further_link" && "$further_link" != "null" && "$visited_urls" != *"$further_link"* ]]; then
log "Following further related link at depth $((depth + 1)): $further_link"
local further_response
if further_response=$(curl -sf -H "Accept: application/rdap+json" "$further_link" 2>/dev/null); then
merged_response=$(merge_rdap_responses "$merged_response" "$further_response")
visited_urls="$visited_urls $further_link"
else
warn "Failed to retrieve data from further link: $further_link"
fi
fi
done <<< "$further_links"
fi
else
warn "Failed to retrieve data from related link: $link"
fi
fi
done <<< "$related_links"
fi
echo "$merged_response"
return 0
else
error "RDAP lookup failed for $domain at $rdap_url"
return 1
fi
}
# Function to perform RDAP lookup (wrapper for backward compatibility)
perform_rdap_lookup() {
local domain="$1"
local rdap_server="$2"
local result
if result=$(perform_recursive_rdap_lookup "$domain" "$rdap_server" "" 0); then
echo "$result"
success "RDAP lookup completed successfully with all related data"
return 0
else
return 1
fi
}
# Main function
main() {
local domain_input="$1"
local tld_input="${2:-}"
local full_domain=""
local tld=""
local domain_name=""
# Determine domain and TLD
if [[ -n "$tld_input" ]]; then
# TLD provided separately
domain_name="$domain_input"
tld="$tld_input"
full_domain="$domain_name.$tld"
else
# Extract TLD from domain
full_domain="$domain_input"
tld=$(extract_tld "$full_domain")
if [[ -z "$tld" ]]; then
error "Could not extract TLD from '$full_domain'. Please provide TLD as second argument."
usage
exit 1
fi
domain_name=$(get_domain_name "$full_domain" "$tld")
fi
log "Domain: $domain_name"
log "TLD: $tld"
log "Full domain: $full_domain"
# Get RDAP data
local rdap_data=$(get_rdap_data "$CACHE_FILE")
# Find RDAP server
local rdap_server=$(find_rdap_server "$tld" "$rdap_data")
if [[ -z "$rdap_server" ]]; then
error "No RDAP server found for TLD: $tld"
exit 1
fi
success "Found RDAP server: $rdap_server"
# Perform RDAP lookup
perform_rdap_lookup "$full_domain" "$rdap_server"
}
# Check dependencies
for cmd in curl jq; do
if ! command -v "$cmd" &> /dev/null; then
error "Required command '$cmd' not found. Please install it."
exit 1
fi
done
# Parse arguments
if [[ $# -eq 0 || "$1" == "-h" || "$1" == "--help" ]]; then
usage
exit 0
fi
if [[ $# -gt 2 ]]; then
error "Too many arguments provided"
usage
exit 1
fi
# Run main function
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment