dynamicdns: Replace ez-ipupdate

Add Python implementation of GnuDIP client.

Tests:

- In testing container, configure Dynamic DNS with a (previously
  offlined) freedombox.rocks account. FreedomBox interface shows that
  the address has been updated. GnuDIP server also shows the correct
  IP address.

- Running "gnudip update" and "dynamicdns update" actions produce the
  expected results.
This commit is contained in:
James Valleroy 2022-01-28 07:33:02 -05:00 committed by Sunil Mohan Adapa
parent bfbb5ac62b
commit 84a7323b42
No known key found for this signature in database
GPG Key ID: 43EA1CFF0AA7C5F2
4 changed files with 130 additions and 31 deletions

View File

@ -31,7 +31,7 @@ UPDATEMINUTES=5
# a "blind" update
UPDATEMINUTESUNKNOWN=3600
TOOLNAME=ez-ipupdate
UPDATE_TOOL=$(which ${TOOLNAME})
GNUDIP_ACTION=/usr/share/plinth/actions/gnudip
DISABLED_STRING='disabled'
ENABLED_STRING='enabled'
@ -157,13 +157,6 @@ doWriteCFG()
else
# we are directly connected
echo "NAT no" >> ${HELPERCFG}
# if this file is added ez-ipupdate will take ip form this interface
{
echo "interface=${default_interface}"
# if this line is added to config file, ez-ipupdate will be launched on startup via init.d
echo "daemon"
echo "execute=${0} success"
} >> ${out_file}
fi
fi
}
@ -296,7 +289,7 @@ doGetWANIP()
fi
}
# actualy do the update (using wget or ez-ipupdate or even both)
# actualy do the update (using wget or gnudip action or even both)
# this function is called via cronjob
doUpdate()
{
@ -309,7 +302,11 @@ doUpdate()
return
fi
if [ ! -z "${server}" ];then
start-stop-daemon -S -x "${UPDATE_TOOL}" -m -p "${PIDFILE}" -- -c "${cfgfile}"
if "${GNUDIP_ACTION}" update; then
${0} success ${wanip}
else
${0} failed
fi
fi
if [ ! -z "${updateurl}" ];then
doReplaceVars
@ -351,24 +348,14 @@ case ${cmd} in
;;
start)
doGetWANIP
if [ "$(grep ^NAT ${HELPERCFG} | awk '{print $2}')" = "no" ];then
#if we are not behind a NAT device and we use gnudip, start the daemon tool
gnudipServer=$(grep ^server= ${cfgfile} 2> /dev/null | cut -d = -f 2- |grep -v ^\'\')
if [ ! -f ${CFG} -a ! -z "${gnudipServer}" ];then
mv ${CFG_disabled} ${CFG}
/etc/init.d/${TOOLNAME} start
fi
# if we are not behind a NAT device and we use update-URL, add a cronjob
# (daemon tool does not support update-URL feature)
if [ ! -z "$(grep ^POSTURL $HELPERCFG | awk '{print $2}')" ];then
echo "*/${UPDATEMINUTES} * * * * root ${0} update" > ${CRONJOB}
$0 update
fi
else
# if we are behind a NAT device, add a cronjob (daemon tool cannot monitor WAN IP changes)
echo "*/${UPDATEMINUTES} * * * * root ${0} update" > $CRONJOB
$0 update
# if we use gnudip, enable gnudip.
gnudipServer=$(grep ^server= ${cfgfile} 2> /dev/null | cut -d = -f 2- |grep -v ^\'\')
if [ ! -f ${CFG} -a ! -z "${gnudipServer}" ];then
mv ${CFG_disabled} ${CFG}
fi
# add a cronjob
echo "*/${UPDATEMINUTES} * * * * root ${0} update" > $CRONJOB
$0 update
;;
get-nat)
NAT=$(grep ^NAT $HELPERCFG 2> /dev/null | awk '{print $2}')

77
actions/gnudip Executable file
View File

@ -0,0 +1,77 @@
#!/usr/bin/python3
# -*- mode: python -*-
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
GnuDIP client for updating Dynamic DNS records.
"""
import argparse
import logging
import pathlib
import sys
from plinth.modules.dynamicdns import gnudip
logger = logging.getLogger(__name__)
CONFIG_FILE = pathlib.Path('/etc/ez-ipupdate/ez-ipupdate.conf')
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('update', help='Update Dynamic DNS record')
subparsers.required = True
return parser.parse_args()
def subcommand_update(_):
"""Update Dynamic DNS record.
Uses settings from ez-ipupdate config file.
"""
if not CONFIG_FILE.is_file():
logger.info('GnuDIP configuration not found.')
return
config = {}
with CONFIG_FILE.open() as cf:
lines = cf.readlines()
for line in lines:
if '=' in line:
items = line.split('=')
key = items[0].strip()
value = items[1].strip()
else:
key = line.strip()
value = True
config[key] = value
if not all(['host' in config, 'server' in config, 'user' in config]):
logger.warn('GnuDIP configuration is not complete.')
return
username, password = config['user'].split(':')
result, new_ip = gnudip.update(config['server'], config['host'], username,
password)
if result == 0 and new_ip:
print(new_ip)
return result
def main():
"""Parse arguments and perform all duties."""
arguments = parse_arguments()
subcommand = arguments.subcommand.replace('-', '_')
subcommand_method = globals()['subcommand_' + subcommand]
sys.exit(subcommand_method(arguments))
if __name__ == '__main__':
main()

View File

@ -11,7 +11,6 @@ from plinth import cfg, menu
from plinth.modules.backups.components import BackupRestore
from plinth.modules.names.components import DomainType
from plinth.modules.users.components import UsersAndGroups
from plinth.package import Packages
from plinth.signals import domain_added
from plinth.utils import format_lazy
@ -58,9 +57,6 @@ class DynamicDNSApp(app_module.App):
'dynamicdns:index', parent_url_name='system')
self.add(menu_item)
packages = Packages('packages-dynamicdns', ['ez-ipupdate'])
self.add(packages)
domain_type = DomainType('domain-type-dynamic',
_('Dynamic Domain Name'), 'dynamicdns:index',
can_have_certificate=True)

View File

@ -0,0 +1,39 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
GnuDIP client for updating Dynamic DNS records.
"""
import hashlib
import logging
import socket
BUF_SIZE = 50
GNUDIP_PORT = 3495
logger = logging.getLogger(__name__)
def update(server, domain, username, password):
"""Update Dynamic DNS record."""
domain = domain.removeprefix(username + '.')
password_digest = hashlib.md5(password.encode()).hexdigest()
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
logger.debug('Connecting to %s:%d', server, GNUDIP_PORT)
s.connect((server, GNUDIP_PORT))
salt = s.recv(BUF_SIZE).decode().strip()
salted_digest = password_digest + '.' + salt
final_digest = hashlib.md5(salted_digest.encode()).hexdigest()
update_request = username + ':' + final_digest + ':' + domain + ':2\n'
s.sendall(update_request.encode())
response = s.recv(BUF_SIZE).decode().strip()
result, new_ip = response.split(':')
result = int(result)
if result == 0:
logger.info('GnuDIP update success: %s', new_ip)
else:
logger.warn('GnuDIP update error: %s', response)
return result, new_ip