diff --git a/src/main.py b/src/main.py index 958a77c..06b5c0f 100755 --- a/src/main.py +++ b/src/main.py @@ -10,12 +10,13 @@ # # 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 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# along with this program. If not, see . +import os import sys import requests import json @@ -27,189 +28,189 @@ from collections import OrderedDict def get_cf_dns_domain(cf_zone_id): - return "https://api.cloudflare.com/client/v4/zones/{}/dns_records".format( - cf_zone_id - ) + return "https://api.cloudflare.com/client/v4/zones/{}/dns_records".format( + cf_zone_id + ) def get_cf_dns_domain_record_id(cf_zone_id, cf_record_id): - return "/".join([ - get_cf_dns_domain(cf_zone_id), - cf_record_id - ]) + return "/".join([ + get_cf_dns_domain(cf_zone_id), + cf_record_id + ]) # !! Do not use this !! The global Cloudflair API is dangerous. Keeping this here for posterity and info # def get_headers_global_api(cf_api_key, cf_email): -# return { -# "X-Auth-Key": cf_api_key, -# "X-Auth-Email": cf_email -# } +# return { +# "X-Auth-Key": cf_api_key, +# "X-Auth-Email": cf_email +# } def get_headers(cf_api_key): - return { - "Authorization": "Bearer {}".format(cf_api_key) - } + return { + "Authorization": "Bearer {}".format(cf_api_key) + } def get_record_id(cf_zone_id, cf_api_key): - resp = requests.get( - url=get_cf_dns_domain(cf_zone_id), - headers=get_headers(cf_api_key) - ) - failed = resp.status_code != 200 - if not failed: - print(json.dumps(resp.json(), indent=4, sort_keys=True)) - return failed + resp = requests.get( + url=get_cf_dns_domain(cf_zone_id), + headers=get_headers(cf_api_key) + ) + failed = resp.status_code != 200 + if not failed: + print(json.dumps(resp.json(), indent=4, sort_keys=True)) + return failed # A hack to prevent pycharm from bitching def dictionary_split(s, delim='='): - k, v = s.split(delim, 1) - return k, v + k, v = s.split(delim, 1) + return k, v # Get the current ip address of the user def get_new_ip(): - with contextlib.suppress(Exception): - a = requests.get("https://1.1.1.1/cdn-cgi/trace").text.split("\n") - ip = dict(dictionary_split(item) for item in a if item != "")["ip"] - return ip - return None + with contextlib.suppress(Exception): + a = requests.get("https://1.1.1.1/cdn-cgi/trace").text.split("\n") + ip = dict(dictionary_split(item) for item in a if item != "")["ip"] + return ip + return None def get_old_ip(cf_zone_id, cf_record_id, cf_api_key): - resp = requests.get( - url=get_cf_dns_domain_record_id(cf_zone_id, cf_record_id), - headers=get_headers(cf_api_key) - ) - if resp.status_code == 200: - return resp.json()["result"]["content"] - return None + resp = requests.get( + url=get_cf_dns_domain_record_id(cf_zone_id, cf_record_id), + headers=get_headers(cf_api_key) + ) + if resp.status_code == 200: + return resp.json()["result"]["content"] + return None def put_new_ip(domain_name, ip, cf_zone_id, cf_record_id, cf_api_key): - json_payload = { - "type": 'A', - "name": domain_name, - "content": ip, - "proxied": True - } + json_payload = { + "type": 'A', + "name": domain_name, + "content": ip, + "proxied": True + } - resp = requests.put( - url=get_cf_dns_domain_record_id(cf_zone_id, cf_record_id), - json=json_payload, - headers=get_headers(cf_api_key) - ) - success = resp.status_code == 200 - return success + resp = requests.put( + url=get_cf_dns_domain_record_id(cf_zone_id, cf_record_id), + json=json_payload, + headers=get_headers(cf_api_key) + ) + success = resp.status_code == 200 + return success def config_parser_has_option(config, profile, option): - # 'DEFAULT' is a special value in ConfigParser - config_name = "" if profile == "DEFAULT" else profile - if config.has_option(config_name, option): - return config[profile][option] - return None + # 'DEFAULT' is a special value in ConfigParser + config_name = "" if profile == "DEFAULT" else profile + if config.has_option(config_name, option): + return config[profile][option] + return None def get_default_path(filename): paths = (".", os.environ["HOME"], "/root", "/") - for path in path: + for path in paths: full_path = os.path.join(path, filename) - if os.file.exists(full_path): + if os.path.isfile(full_path): return full_path return filename def main(): - # Logger - set to logging.INFO for production - logging.basicConfig(level=logging.INFO) + # Logger - set to logging.INFO for production + logging.basicConfig(level=logging.INFO) - # String constants - default_secret_file = ".credentials.secret" - default_profile = "CLOUDFLARE" - default_fullpath = get_defualt_path(default_secret_file) + # String constants + default_secret_file = ".credentials.secret" + default_profile = "CLOUDFLARE" + default_fullpath = get_default_path(default_secret_file) # Find the default path to crdentials file - # Argument Parser - parser = argparse.ArgumentParser( - description="A utility for updating DNS records for dynamic IP addresses", - epilog="Configuration files should be owned by root with an octal permission of 600 (read/write by root only). Run this program as sudo.", - add_help=True - ) - parser.add_argument("-i", "--get-record-ids", help="Print out (in JSON format) all records for your Cloudflair account", action="store_true") - parser.add_argument("-c", "--config", help="Specify configuration file (DEFAULT: '{}')".format(default_fullpath), default=default_fullpath) - parser.add_argument("-p", "--profile", help="Specify profile in configuration file (DEFAULT: '{}')".format(default_profile), default=default_profile) - parser.add_argument("-s", "--set", help="Set ip address to ip specified") - parser.add_argument("-g", "--get", help="Get old (from Cloudflare DNS records) and current ip address", action="store_true") - arguments = parser.parse_args() + # Argument Parser + parser = argparse.ArgumentParser( + description="A utility for updating DNS records for dynamic IP addresses", + epilog="Configuration files should be owned by root with an octal permission of 600 (read/write by root only). Run this program as sudo.", + add_help=True + ) + parser.add_argument("-i", "--get-record-ids", help="Print out (in JSON format) all records for your Cloudflair account", action="store_true") + parser.add_argument("-c", "--config", help="Specify configuration file (DEFAULT: '{}')".format(default_fullpath), default=default_fullpath) + parser.add_argument("-p", "--profile", help="Specify profile in configuration file (DEFAULT: '{}')".format(default_profile), default=default_profile) + parser.add_argument("-s", "--set", help="Set ip address to ip specified") + parser.add_argument("-g", "--get", help="Get old (from Cloudflare DNS records) and current ip address", action="store_true") + arguments = parser.parse_args() - # Configuration file - config = configparser.ConfigParser() - config.read(arguments.config) + # Configuration file + config = configparser.ConfigParser() + config.read(arguments.config) - options = OrderedDict([ - ("domain_name", config_parser_has_option(config, arguments.profile, "domain_name")), - ("cf_api_key", config_parser_has_option(config, arguments.profile, "cf_api_key")), - ("cf_zone_id", config_parser_has_option(config, arguments.profile, "cf_zone_id")), - ("cf_record_id", config_parser_has_option(config, arguments.profile, "cf_record_id")) - ]) + options = OrderedDict([ + ("domain_name", config_parser_has_option(config, arguments.profile, "domain_name")), + ("cf_api_key", config_parser_has_option(config, arguments.profile, "cf_api_key")), + ("cf_zone_id", config_parser_has_option(config, arguments.profile, "cf_zone_id")), + ("cf_record_id", config_parser_has_option(config, arguments.profile, "cf_record_id")) + ]) - # Validate configuration file - missing_parameter = next((i for i in options if options[i] is None and i != "cf_record_id"), None) - if missing_parameter is not None: - logging.error("Configuration: {} | Profile: {} | Option: {}".format( - arguments.config, arguments.profile, missing_parameter - )) - return 1 + # Validate configuration file + missing_parameter = next((i for i in options if options[i] is None and i != "cf_record_id"), None) + if missing_parameter is not None: + logging.error("Configuration: {} | Profile: {} | Option: {}".format( + arguments.config, arguments.profile, missing_parameter + )) + return 1 - # If cf_zone_id is None, print out all record ids. The user will have to update this setting in their config file - # This will have to be done at least once - if arguments.get_record_ids or options["cf_record_id"] is None: - return int(get_record_id(options["cf_zone_id"], options["cf_api_key"])) + # If cf_zone_id is None, print out all record ids. The user will have to update this setting in their config file + # This will have to be done at least once + if arguments.get_record_ids or options["cf_record_id"] is None: + return int(get_record_id(options["cf_zone_id"], options["cf_api_key"])) - # Get the current dns record from cloudflair (old) - old_ip = get_old_ip(options["cf_zone_id"], options["cf_record_id"], options["cf_api_key"]) + # Get the current dns record from cloudflair (old) + old_ip = get_old_ip(options["cf_zone_id"], options["cf_record_id"], options["cf_api_key"]) - # Get the current ip address of the server (new) - new_ip = None + # Get the current ip address of the server (new) + new_ip = None - if arguments.set: - new_ip = arguments.set - else: - new_ip = get_new_ip() - - if new_ip is None: - logging.error("Could not determine new IP") - return 1 + if arguments.set: + new_ip = arguments.set + else: + new_ip = get_new_ip() + + if new_ip is None: + logging.error("Could not determine new IP") + return 1 - # Print beginning of status line - if old_ip is None: - logging.warn("Could not determine old IP") - - # Update cloudflair's dns record if necessary and print out the end of the status line - if arguments.get: - logging.info("Current IP address: {}".format(new_ip)) - elif old_ip == new_ip: - logging.info("New IP matches old IP: {}".format(new_ip)) - else: - if put_new_ip(options["domain_name"], new_ip, options["cf_zone_id"], options["cf_record_id"], options["cf_api_key"]): - logging.info("IP updated to: {} from: {}".format(old_ip, new_ip)) - else: - logging.error("Failed to update to: {} from: {}".format(old_ip, new_ip)) - return 1 - return 0 + # Print beginning of status line + if old_ip is None: + logging.warn("Could not determine old IP") + + # Update cloudflair's dns record if necessary and print out the end of the status line + if arguments.get: + logging.info("Current IP address: {}".format(new_ip)) + elif old_ip == new_ip: + logging.info("New IP matches old IP: {}".format(new_ip)) + else: + if put_new_ip(options["domain_name"], new_ip, options["cf_zone_id"], options["cf_record_id"], options["cf_api_key"]): + logging.info("IP updated to: {} from: {}".format(old_ip, new_ip)) + else: + logging.error("Failed to update to: {} from: {}".format(old_ip, new_ip)) + return 1 + return 0 # Exit codes: # 0 - Success # 1 - An error occurred if __name__ == "__main__": - try: - sys.exit(main()) - except Exception as e: - print("Exception!!", e) - sys.exit(1) + try: + sys.exit(main()) + except Exception as e: + print("Exception!!", e) + sys.exit(1)