#!/usr/bin/python3 # SPDX-License-Identifier: AGPL-3.0-or-later """ Configuration helper for Dynamic DNS. """ import argparse import json import pathlib import urllib _conf_dir = pathlib.Path('/etc/ez-ipupdate/') _active_config = _conf_dir / 'ez-ipupdate.conf' _inactive_config = _conf_dir / 'ez-ipupdate.inactive' _helper_config = _conf_dir / 'ez-ipupdate-plinth.cfg' _cron_job = pathlib.Path('/etc/cron.d/ez-ipupdate') def parse_arguments(): """ Return parsed command line arguments as dictionary. """ parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(dest='subcommand', help='Sub command') subparsers.add_parser('export-config', help='Print configuration in JSON format') subparsers.add_parser('clean', help='Remove all old configuration files') subparsers.add_parser('update', help='For backwards compatibility') subparser = subparsers.add_parser('success', help='For backwards compatibility') subparser.add_argument('wan_ip_address') subparsers.required = True return parser.parse_args() def _read_configuration(path, separator='='): """Read ez-ipupdate configuration.""" config = {} for line in path.read_text().splitlines(): if line.startswith('#'): continue parts = line.partition(separator) if parts[1]: config[parts[0].strip()] = parts[2].strip() else: config[parts[0].strip()] = True return config def subcommand_export_config(_): """Print the old ez-ipupdate configuration in JSON format.""" input_config = {} if _active_config.exists(): input_config = _read_configuration(_active_config) elif _inactive_config.exists(): input_config = _read_configuration(_inactive_config) helper = {} if _helper_config.exists(): helper.update(_read_configuration(_helper_config, separator=' ')) def _clean(value): value_map = {'enabled': True, 'disabled': False, '': None} return value_map.get(value, value) domain = { 'service_type': 'gnudip', 'domain': input_config.get('host'), 'server': input_config.get('server'), 'username': input_config.get('user', '').split(':')[0] or None, 'password': input_config.get('user', '').split(':')[-1] or None, 'ip_lookup_url': helper.get('IPURL'), 'update_url': _clean(helper.get('POSTURL')) or None, 'use_http_basic_auth': _clean(helper.get('POSTAUTH')), 'disable_ssl_cert_check': _clean(helper.get('POSTSSLIGNORE')), 'use_ipv6': _clean(helper.get('POSTUSEIPV6')), } if isinstance(domain['update_url'], bool): # 'POSTURL ' is a line found in the configuration file domain['update_url'] = None if not domain['server']: domain['service_type'] = 'other' update_url = domain['update_url'] try: server = urllib.parse.urlparse(update_url).netloc service_types = { 'dynupdate.noip.com': 'noip.com', 'dynupdate.no-ip.com': 'noip.com', 'freedns.afraid.org': 'freedns.afraid.org' } domain['service_type'] = service_types.get(server, 'other') except ValueError: pass # Old logic for 'enabling' the app is as follows: If behind NAT, add # cronjob. If not behind NAT and type is update URL, add cronjob. If not # behind NAT and type is GnuDIP, move inactive configuration to active # configuration and start the ez-ipupdate daemon. enabled = False if _cron_job.exists() or (domain['service_type'] == 'gnudip' and _active_config.exists()): enabled = True output_config = {'enabled': enabled, 'domains': {domain['domain']: domain}} print(json.dumps(output_config)) def subcommand_clean(_): """Remove all old configuration files.""" last_update = _conf_dir / 'last-update' status = _conf_dir / 'ez-ipupdate.status' current_ip = _conf_dir / 'ez-ipupdate.currentIP' cleanup_files = [ _active_config, _inactive_config, last_update, _helper_config, status, current_ip ] for cleanup_file in cleanup_files: try: cleanup_file.rename(cleanup_file.with_suffix('.bak')) except FileNotFoundError: pass _cron_job.unlink(missing_ok=True) def subcommand_update(_): """Empty subcommand kept only for backwards compatibility. Drop after stable release. """ def subcommand_success(_): """Empty subcommand kept only for backwards compatibility. Drop after stable release. """ def main(): """Parse arguments and perform all duties.""" arguments = parse_arguments() subcommand = arguments.subcommand.replace('-', '_') subcommand_method = globals()['subcommand_' + subcommand] subcommand_method(arguments) if __name__ == '__main__': main()