#!/usr/bin/env python # Program: DNS Domain Expiration Checker # Author: Matty < matty91 at gmail dot com > # Current Version: 9.1 # Date: 01-27-2020 # License: # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. import sys import time import argparse import smtplib import dateutil.parser import subprocess from datetime import datetime from email.MIMEMultipart import MIMEMultipart from email.MIMEText import MIMEText EXPIRE_STRINGS = [ "Registry Expiry Date:", "Expiration:", "Domain Expiration Date", "Registrar Registration Expiration Date:", "expire:", "expires:", "Expiry date" ] REGISTRAR_STRINGS = [ "Registrar:" ] DEBUG = 0 def debug(string_to_print): """ Helper function to assist with printing debug messages. """ if DEBUG: print(string_to_print) def print_heading(): """ Print a formatted heading when called interactively """ print("%-25s %-20s %-30s %-4s" % ("Domain Name", "Registrar", "Expiration Date", "Days Left")) def print_domain(domain, registrar, expiration_date, days_remaining): """ Pretty print the domain information on stdout """ print("%-25s %-20s %-30s %-d" % (domain, registrar, expiration_date, days_remaining)) def make_whois_query(domain): """ Execute whois and parse the data to extract specific data """ debug("Sending a WHOIS query for the domain %s" % domain) try: p = subprocess.Popen(['whois', domain], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) except Exception as e: print("Unable to Popen() the whois binary. Exception %s" % e) sys.exit(1) try: whois_data = p.communicate()[0] except Exception as e: print("Unable to read from the Popen pipe. Exception %s" % e) sys.exit(1) # TODO: Work around whois issue #55 which returns a non-zero # exit code for valid domains. # if p.returncode != 0: # print("The WHOIS utility exit()'ed with a non-zero return code") # sys.exit(1) return(parse_whois_data(whois_data)) def parse_whois_data(whois_data): """ Grab the registrar and expiration date from the WHOIS data """ debug("Parsing the whois data blob %s" % whois_data) expiration_date = "00/00/00 00:00:00" registrar = "Unknown" for line in whois_data.splitlines(): if any(expire_string in line for expire_string in EXPIRE_STRINGS): expiration_date = dateutil.parser.parse(line.partition(": ")[2], ignoretz=True) if any(registrar_string in line for registrar_string in REGISTRAR_STRINGS): registrar = line.split("Registrar:")[1].strip() return expiration_date, registrar def calculate_expiration_days(expire_days, expiration_date): """ Check to see when a domain will expire """ debug("Expiration date %s Time now %s" % (expiration_date, datetime.now())) try: domain_expire = expiration_date - datetime.now() except: print("Unable to calculate the expiration days") sys.exit(1) if domain_expire.days < expire_days: return domain_expire.days else: return 0 def check_expired(expiration_days, days_remaining): """ Check to see if a domain has passed the expiration day threshold. If so send out notifications """ if int(days_remaining) < int(expiration_days): return days_remaining else: return 0 def domain_expire_notify(domain, config_options, days): """ Functions to call when a domain is about to expire. Adding support for Nagios, SNMP, etc. can be done by defining a new function and calling it here. """ debug("Triggering notifications for the DNS domain %s" % domain) # Send outbound e-mail if a rcpt is passed in if config_options["email"]: send_expire_email(domain, days, config_options) def send_expire_email(domain, days, config_options): """ Generate an e-mail to let someone know a domain is about to expire """ debug("Generating an e-mail to %s for domain %s" % (config_options["smtpto"], domain)) msg = MIMEMultipart() msg['From'] = config_options["smtpfrom"] msg['To'] = config_options["smtpto"] msg['Subject'] = "The DNS Domain %s is set to expire in %d days" % (domain, days) body = "Time to renew %s" % domain msg.attach(MIMEText(body, 'plain')) smtp_connection = smtplib.SMTP(config_options["smtpserver"],config_options["smtpport"]) message = msg.as_string() smtp_connection.sendmail(config_options["smtpfrom"], config_options["smtpto"], message) smtp_connection.quit() def processcli(): """ parses the CLI arguments and returns a domain or a file with a list of domains. """ parser = argparse.ArgumentParser(description='DNS Statistics Processor') parser.add_argument('--domainfile', help="Path to file with list of domains and expiration intervals.") parser.add_argument('--domainname', help="Domain to check expiration on.") parser.add_argument('--email', action="store_true", help="Enable debugging output.") parser.add_argument('--interactive',action="store_true", help="Enable debugging output.") parser.add_argument('--expiredays', default=10000, type=int, help="Expiration threshold to check against.") parser.add_argument('--sleeptime', default=60, type=int, help="Time to sleep between whois queries.") parser.add_argument('--smtpserver', default="localhost", help="SMTP server to use.") parser.add_argument('--smtpport', default=25, help="SMTP port to connect to.") parser.add_argument('--smtpto', default="root", help="SMTP To: address.") parser.add_argument('--smtpfrom', default="root", help="SMTP From: address.") # Return a dict() with all of the arguments passed in return(vars(parser.parse_args())) def main(): """ Main loop """ days_remaining = 0 conf_options = processcli() if conf_options["interactive"]: print_heading() if conf_options["domainfile"]: with open(conf_options["domainfile"], "r") as domains_to_process: for line in domains_to_process: try: domainname, expiration_days = line.split() except Exception as e: print("Unable to parse configuration file. Problem line \"%s\"" % line.strip()) sys.exit(1) expiration_date, registrar = make_whois_query(domainname) days_remaining = calculate_expiration_days(expiration_days, expiration_date) if check_expired(expiration_days, days_remaining): domain_expire_notify(domainname, conf_options, days_remaining) if conf_options["interactive"]: print_domain(domainname, registrar, expiration_date, days_remaining) # Need to wait between queries to avoid triggering DOS measures like so: # Your IP has been restricted due to excessive access, please wait a bit time.sleep(conf_options["sleeptime"]) elif conf_options["domainname"]: expiration_date, registrar = make_whois_query(conf_options["domainname"]) days_remaining = calculate_expiration_days(conf_options["expiredays"], expiration_date) if check_expired(conf_options["expiredays"], days_remaining): domain_expire_notify(conf_options["domainname"], conf_options, days_remaining) if conf_options["interactive"]: print_domain(conf_options["domainname"], registrar, expiration_date, days_remaining) # Need to wait between queries to avoid triggering DOS measures like so: # Your IP has been restricted due to excessive access, please wait a bit time.sleep(conf_options["sleeptime"]) if __name__ == "__main__": main()