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 # a "blind" update
UPDATEMINUTESUNKNOWN=3600 UPDATEMINUTESUNKNOWN=3600
TOOLNAME=ez-ipupdate TOOLNAME=ez-ipupdate
UPDATE_TOOL=$(which ${TOOLNAME}) GNUDIP_ACTION=/usr/share/plinth/actions/gnudip
DISABLED_STRING='disabled' DISABLED_STRING='disabled'
ENABLED_STRING='enabled' ENABLED_STRING='enabled'
@ -157,13 +157,6 @@ doWriteCFG()
else else
# we are directly connected # we are directly connected
echo "NAT no" >> ${HELPERCFG} 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
fi fi
} }
@ -296,7 +289,7 @@ doGetWANIP()
fi 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 # this function is called via cronjob
doUpdate() doUpdate()
{ {
@ -309,7 +302,11 @@ doUpdate()
return return
fi fi
if [ ! -z "${server}" ];then 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 fi
if [ ! -z "${updateurl}" ];then if [ ! -z "${updateurl}" ];then
doReplaceVars doReplaceVars
@ -351,24 +348,14 @@ case ${cmd} in
;; ;;
start) start)
doGetWANIP doGetWANIP
if [ "$(grep ^NAT ${HELPERCFG} | awk '{print $2}')" = "no" ];then # if we use gnudip, enable gnudip.
#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 ^\'\')
gnudipServer=$(grep ^server= ${cfgfile} 2> /dev/null | cut -d = -f 2- |grep -v ^\'\') if [ ! -f ${CFG} -a ! -z "${gnudipServer}" ];then
if [ ! -f ${CFG} -a ! -z "${gnudipServer}" ];then mv ${CFG_disabled} ${CFG}
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
fi fi
# add a cronjob
echo "*/${UPDATEMINUTES} * * * * root ${0} update" > $CRONJOB
$0 update
;; ;;
get-nat) get-nat)
NAT=$(grep ^NAT $HELPERCFG 2> /dev/null | awk '{print $2}') 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.backups.components import BackupRestore
from plinth.modules.names.components import DomainType from plinth.modules.names.components import DomainType
from plinth.modules.users.components import UsersAndGroups from plinth.modules.users.components import UsersAndGroups
from plinth.package import Packages
from plinth.signals import domain_added from plinth.signals import domain_added
from plinth.utils import format_lazy from plinth.utils import format_lazy
@ -58,9 +57,6 @@ class DynamicDNSApp(app_module.App):
'dynamicdns:index', parent_url_name='system') 'dynamicdns:index', parent_url_name='system')
self.add(menu_item) self.add(menu_item)
packages = Packages('packages-dynamicdns', ['ez-ipupdate'])
self.add(packages)
domain_type = DomainType('domain-type-dynamic', domain_type = DomainType('domain-type-dynamic',
_('Dynamic Domain Name'), 'dynamicdns:index', _('Dynamic Domain Name'), 'dynamicdns:index',
can_have_certificate=True) 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