import configparser as configparser import sys import os import requests import json import ipaddress from datetime import datetime import time config_file = "config.txt" SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) DEFAULT_RETRIES = 3 DEFAULT_IPIFY_API = "https://api.ipify.org" class GandiDdnsError(Exception): pass def get_ip_inner(ipify_api): # Get external IP try: # Could be any service that just gives us a simple raw ASCII IP address (not HTML etc) r = requests.get(ipify_api, timeout=3) except requests.exceptions.RequestException: raise GandiDdnsError('Failed to retrieve external IP.') if r.status_code != 200: raise GandiDdnsError( 'Failed to retrieve external IP.' ' Server responded with status_code: %d' % r.status_code) ip = r.text.rstrip() # strip \n and any trailing whitespace if not(ipaddress.IPv4Address(ip)): # check if valid IPv4 address raise GandiDdnsError('Got invalid IP: ' + ip) return ip def get_ip(ipify_api, retries): # Get external IP with retries # Start at 5 seconds, double on every retry. retry_delay_time = 5 for attempt in range(retries): try: return get_ip_inner(ipify_api) except GandiDdnsError as e: print('Getting external IP failed: %s' % e) print('Waiting for %d seconds before trying again' % retry_delay_time) time.sleep(retry_delay_time) # Double retry time, cap at 60s. retry_delay_time = min(60, 2 * retry_delay_time) print('Exhausted retry attempts') sys.exit(2) def read_config(config_path): # Read configuration file cfg = configparser.ConfigParser() cfg.read(config_path) return cfg def get_record(url, headers): # Get existing record r = requests.get(url, headers=headers) return r def update_record(url, headers, payload): # Add record r = requests.put(url, headers=headers, json=payload) if r.status_code != 201: print(('Record update failed with status code: %d' % r.status_code)) print((r.text)) sys.exit(2) print('Zone record updated.') return r def main(): path = config_file if not path.startswith('/'): path = os.path.join(SCRIPT_DIR, path) config = read_config(path) if not config: sys.exit("Please fill in the 'config.txt' file.") for section in config.sections(): print('%s - section %s' % (str(datetime.now()), section)) # Retrieve API key apikey = config.get(section, 'apikey') # Set headers headers = {'Content-Type': 'application/json', 'X-Api-Key': '%s' % apikey} # Set URL url = '%sdomains/%s/records/%s/A' % (config.get(section, 'gandi_api'), config.get(section, 'domain'), config.get(section, 'a_name')) print(url) # Discover External IP ipify_api = config.get(section, 'ipify_api', fallback=DEFAULT_IPIFY_API) retries = int(config.get(section, 'retries', fallback=DEFAULT_RETRIES)) external_ip = get_ip(ipify_api, retries) print(('External IP is: %s' % external_ip)) # Prepare record payload = {'rrset_ttl': config.get(section, 'ttl'), 'rrset_values': [external_ip]} # Check current record record = get_record(url, headers) if record.status_code == 200: print(('Current record value is: %s' % json.loads(record.text)['rrset_values'][0])) if(json.loads(record.text)['rrset_values'][0] == external_ip): print('No change in IP address. Goodbye.') continue else: print('No existing record. Adding...') update_record(url, headers, payload) if __name__ == "__main__": main()