Skip to content

dns/ddclient: Add Multihostname support for netcup #4268

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

Closed
wants to merge 9 commits into from
Closed
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
76 changes: 41 additions & 35 deletions dns/ddclient/src/opnsense/scripts/ddclient/lib/account/netcup.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,49 +45,55 @@ def __init__(self, account: dict):

@staticmethod
def known_services():
return Netcup._services
return {"netcup": "Netcup"}

@staticmethod
def match(account):
return account.get('service') in Netcup._services

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.domain = self.settings['hostnames'].split('.', self.settings['hostnames'].count('.')-1)[-1]
self.hostname = self.settings['hostnames'].rsplit('.', 2)[0] if self.domain != self.settings['hostnames'] else '@'

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

if self.settings['APIPassword'] is None or self.settings['APIKey'] is None:
else:
syslog.syslog(syslog.LOG_ERR, "Unable to parse APIPassword|APIKey.")
return False

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)

hostnames = {}
for hostname in self.settings['hostnames'].split(','):

domain = (hostname.split('.', hostname.count('.')-1)[-1]).strip(' \t\n\r')
hostname = (hostname.rsplit('.', 2)[0]).strip(' \t\n\r') if domain != self.settings['hostnames'] else '@'

if domain not in hostnames:
hostnames[domain] = []
hostnames[domain].append(hostname)

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'))

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 @@ -103,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 @@ -122,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 @@ -143,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