Retrieving the expiration date of a domain using RDAP

One of the most common problems with domain names is accidental name expiration. "We forgot to renew it" and paf nothing works anymore. However, the date on which the domain expires is publicly announced, via protocols like whois or RDAP. You will learn how to use RDAP to retrieve this date in a program, for example an alert program that will warn if the expiration is approaching.

A little background first; a number (but not all) of domain name registries require periodic name renewal, with payment and sometimes acceptance of new terms. Forgetting this renewal is a very common gag. We do not pay, and, at the expiration date, the domain disappears or is put on hold, no longer published in the DNS, which causes all associated services to stop functioning. In the first case, the worst, the domain has been deleted, and another can register it quickly. It is therefore crucial not to let the domain expire. One of the essential tools when managing an important area is therefore automatic supervision of the expiration. The information is disseminated by the registry, via several protocols. The traditional whois, standardized in RFC 3912, has several drawbacks, in particular the fact that the output format is not standardized. It is therefore difficult to write a program which analyzes the returned information (there are still solutions such as, in Perl, Net :: DRI). On the contrary, the more modern RDAP has a standardized output format (in RFC 9083). Let's use it, taking google.com as a domain example.

RDAP uses HTTPS and produces JSON (RFC 8259). On the other hand, to query the correct RDAP server (that of the registry), you need to know its name, which is in an IANA registry that associates the TLD with its RDAP server. We learn that information about .com can be requested from https://rdap.verisign.com/com/v1/domain/ Using RFC 9082 to form a correct RDAP request, we will use cURL to ask the question on google.com:

$ curl -s https://rdap.verisign.com/com/v1/domain/google.com

We get a big complicated JSON, which I won't show you here. We just want the domain's expiration date. We will use the excellent jq tool for this:

$ curl -s https://rdap.verisign.com/com/v1/domain/google.com | \
       jq '.events | map(select(.eventAction == "expiration"))[0] | .eventDate' 
"2028-09-14T04:00:00Z"

It took us more than a few seconds to write the jq program that extracts this information, and had to check RFC 9083. So let's explain.

Let's start from the top of the returned JSON object. It contains a member named events (RFC 9083, section 4.5) which indicates relevant past or future events, such as the creation of the domain or its future expiration. We must therefore start by asking the jq program to filter on this member, hence the .events at the beginning. Then, we want to keep, among the events, only the expiration. We therefore apply (map) a test (.eventAction == "expiration") to each element of the events array and we keep (select) only the one that interests us. Yes, “the one” and not “those” because there is normally only one expiration event, so we only keep the first element ([0]) of the produced array. This first element, like all those that make up the events array, is an event (RFC 9083, section 4.5), an object which here has two members, eventAction, its type (the one on which we filter, with .eventAction == "expiration" and eventDate, the information we are looking for. We end with a filter of the only eventDate, date which is in RFC 3339 format. There you go. We can then treat this date as we want. For example, the GNU program date can translate this date into seconds elapsed since the epoch:

$ date --date=$(curl -s https://rdap.verisign.com/com/v1/domain/google.com | \
                jq -r '.events | map(select(.eventAction== "expiration"))[0] | .eventDate') \
    +%s 
1852516800

Now let's give up cURL and jq for a program written in Python. We use the Requests library to do HTTPS and the standard libraries included in Python to do JSON, calculation on dates, etc. Getting the JSON is simple:

response = requests.get("%s/%s" % (SERVER, domain))
if response.status_code != 200:
    raise Exception("Invalid RDAP return code: %s" % response.status_code)

Turn it into a Python dictionary too:

rdap = json.loads(response.content)

We will then look for the expiration date, which we transform into a nice Python date-and-time on which it will be easy to make calculations:

for event in rdap["events"]:
    if event["eventAction"] == "expiration":
        expiration = datetime.datetime.strptime(event["eventDate"], RFC3339)
        now = datetime.datetime.utcnow()
        rest = expiration-now

And we can then put the treatments we want, here to prevent if the expiration is approaching:

if rest < CRITICAL:
    print("CRITICAL: domain %s expires in %s, HURRY UP!!!" % (domain, rest))
    sys.exit(2)
elif rest < WARNING:
    print("WARNING: domain %s expires in %s, renew now" % (domain, rest))
    sys.exit(1)
else:
    print("OK: domain %s expires in %s" % (domain, rest))
    sys.exit(0)

Here is the code for expiration-rdap.py (you also need the ianardap.py module). Note that it is not so robust, more error handling should be planned. Python, however, makes sure that most errors are caught automatically (for example if there is no events member in the result) so at least you won't get wrong results. And then, compared to the curl + jq program above, a big advantage of this Python program is that it uses the IANA server registry (described in RFC 7484) and therefore finds the RDAP server on its own:

$ python expiration-rdap.py google.com
OK: domain google.com expires in 2627 days, 9:29:45.817117

Note that such a tool is only one element among all that must be done to properly manage the possible expiration of your domain. It is also necessary to put the dates of renewal in your diary, it is recommended to activate the auto-renewal or, better, in the registers which allow it, to rent for several years.