Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dns/ddclient: Add Multihostname support for netcup #4268

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 37 additions & 30 deletions dns/ddclient/src/opnsense/scripts/ddclient/lib/account/netcup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Copyright (c) 2023 Ingo Lafrenz <[email protected]>
Copyright (c) 2023 Ad Schellevis <[email protected]>
Copyright (c) 2024 Tobias Sdun <[email protected]>
All rights reserved.

Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -53,19 +54,20 @@ def match(account):

def execute(self):
if super().execute():
if self.settings['hostnames'].find(',') > -1:
self.settings['hostnames'] = self.settings['hostnames'].split(',')[0]
syslog.syslog(
syslog.LOG_WARNING,
"Multiple hostnames detected, ignoring all except first. "+
"Consider using CNAMEs or create separate DynDNS instances for each hostname."
)
if self.settings['hostnames'].find('.') == -1:
syslog.syslog(syslog.LOG_ERR, "Incomplete FQDN offerred %s" % self.settings['hostnames'])
return False

self.hostname, self.domain = self.settings['hostnames'].split('.', 1)

syslog.syslog(syslog.LOG_DEBUG, f"Hostnames in settings: {str(self.settings['hostnames'])}")
all_hostnames = self.settings['hostnames'].split(',')
hostnames = {}
for hostname in all_hostnames:
domain = (hostname.split('.', hostname.count('.')-1)[-1]).strip(' \t\n\r')
hostname = (hostname.rsplit('.', 2)[0]).strip(' \t\n\r')
if domain not in hostnames:
hostnames[domain] = []
hostnames[domain].append(hostname)

if self.settings['password'].count('|') == 1:
self.settings['APIPassword'], self.settings['APIKey'] = self.settings['password'].split('|')

Expand All @@ -76,17 +78,22 @@ def execute(self):
self.netcupAPISessionID = self._login()
if not self.netcupAPISessionID:
return False
dnsZoneInfo = self._sendRequest(self._createRequestPayload('infoDnsZone'))
if not dnsZoneInfo:
return False
if str(self.settings['ttl']) != dnsZoneInfo['ttl']:
dnsZoneInfo['ttl'] = str(self.settings['ttl'])
self._sendRequest(self._createRequestPayload('updateDnsZone', {'dnszone': dnsZoneInfo}))
dnsRecordsInfo = self._sendRequest(self._createRequestPayload('infoDnsRecords'))
if not dnsRecordsInfo:
return False
recordType = 'AAAA' if ':' in self.current_address else 'A'
self._updateIpAddress(recordType, dnsRecordsInfo)

for domain, subdomains in hostnames.items():
dnsZoneInfo = self._sendRequest(self._createRequestPayload(domain, 'infoDnsZone'))
if not dnsZoneInfo:
return False
if str(self.settings['ttl']) != dnsZoneInfo['ttl']:
dnsZoneInfo['ttl'] = str(self.settings['ttl'])
self._sendRequest(self._createRequestPayload(domain, 'updateDnsZone', {'dnszone': dnsZoneInfo}))
dnsRecordsInfo = self._sendRequest(self._createRequestPayload(domain, 'infoDnsRecords'))
syslog.syslog(syslog.LOG_DEBUG, f'DNS Records Info: {dnsRecordsInfo}')
if not dnsRecordsInfo:
return False
recordType = 'AAAA' if ':' in self.current_address else 'A'
for subdomain in subdomains:
self._updateIpAddress(subdomain, domain, recordType, dnsRecordsInfo)

self._logout()
self.update_state(address=self.current_address)
return True
Expand All @@ -102,9 +109,9 @@ def _login(self):
}
return self._sendRequest(requestPayload).get('apisessionid', None)

def _updateDnsRecords(self, hostRecord):
def _updateDnsRecords(self, domain, hostRecord):
return self._sendRequest(
self._createRequestPayload(
self._createRequestPayload(domain,
'updateDnsRecords',
{'dnsrecordset': {'dnsrecords': [hostRecord]}}
)
Expand All @@ -121,17 +128,17 @@ def _logout(self):
}
return self._sendRequest(requestPayload)

def _updateIpAddress(self, recordType, dnsRecordsInfo):
def _updateIpAddress(self, subdomain, domain, recordType, dnsRecordsInfo):
matchingRecords = [
r for r in dnsRecordsInfo['dnsrecords'] if r['type'] == recordType and r['hostname'] == self.hostname
r for r in dnsRecordsInfo['dnsrecords'] if r['type'] == recordType and r['hostname'] == subdomain
]
if len(matchingRecords) > 1:
raise Exception(f'Too many {recordType} records for hostname {self.hostname} in DNS zone {self.domain}.')
raise Exception(f'Too many {recordType} records for hostname {subdomain} in DNS zone {domain}.')
if matchingRecords:
hostRecord = matchingRecords[0]
else:
hostRecord = {
'hostname': self.hostname,
'hostname': subdomain,
'type': recordType,
'destination': None
}
Expand All @@ -142,19 +149,19 @@ def _updateIpAddress(self, recordType, dnsRecordsInfo):
f'IP address change detected. Old IP: {currentNetcupIPAddress}, new IP: {self.current_address}'
)
hostRecord['destination'] = self.current_address
if self._updateDnsRecords(hostRecord):
if self._updateDnsRecords(domain, hostRecord):
syslog.syslog(
syslog.LOG_NOTICE,
f'Successfully updated {recordType} record for {self.hostname}.{self.domain} to {self.current_address}'
f'Successfully updated {recordType} record for {subdomain}.{domain} to {self.current_address}'
)
else:
syslog.syslog(syslog.LOG_NOTICE, 'IP address has not changed. Nothing to do.')

def _createRequestPayload(self, action, extraParameters={}):
def _createRequestPayload(self, domain, action, extraParameters={}):
requestPayload = {
'action': action,
'param': {
'domainname': self.domain,
'domainname': domain,
'customernumber': self._account['username'],
'apikey': self.settings['APIKey'],
'apisessionid': self.netcupAPISessionID,
Expand Down