X-Git-Url: http://git.ipfire.org/?p=oddments%2Fddns.git;a=blobdiff_plain;f=src%2Fddns%2Fproviders.py;h=f1fed2265c6c62510f3207039cf26f007fa3deca;hp=dcdc5daa21200837cf7f489568b78cf41b01f4ac;hb=HEAD;hpb=00be6eaf179fb34fb31e2f56915d2f44969c920c diff --git a/src/ddns/providers.py b/src/ddns/providers.py index dcdc5da..59f9665 100644 --- a/src/ddns/providers.py +++ b/src/ddns/providers.py @@ -21,6 +21,7 @@ import datetime import logging +import json import os import subprocess import urllib.request @@ -73,6 +74,10 @@ class DDNSProvider(object): # Required to remove AAAA records if IPv6 is absent again. can_remove_records = True + # True if the provider supports authentication via a random + # generated token instead of username and password. + supports_token_auth = True + @staticmethod def supported(): """ @@ -352,6 +357,10 @@ class DDNSProtocolDynDNS2(object): # The DynDNS protocol version 2 does not allow to remove records can_remove_records = False + # The DynDNS protocol version 2 only supports authentication via + # username and password. + supports_token_auth = False + def prepare_request_data(self, proto): data = { "hostname" : self.hostname, @@ -434,12 +443,13 @@ class DDNSProviderAllInkl(DDNSProvider): protocols = ("ipv4",) # There are only information provided by the vendor how to - # perform an update on a FRITZ Box. Grab requried informations + # perform an update on a FRITZ Box. Grab required information # from the net. # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/ - url = "http://dyndns.kasserver.com" + url = "https://dyndns.kasserver.com" can_remove_records = False + supports_token_auth = False def update(self): # There is no additional data required so we directly can @@ -464,6 +474,8 @@ class DDNSProviderBindNsupdate(DDNSProvider): DEFAULT_TTL = 60 + supports_token_auth = False + @staticmethod def supported(): # Search if the nsupdate utility is available @@ -536,7 +548,7 @@ class DDNSProviderBindNsupdate(DDNSProvider): logger.debug(" %s" % line) - return "\n".join(scriptlet) + return "\n".join(scriptlet).encode() class DDNSProviderChangeIP(DDNSProvider): @@ -550,6 +562,7 @@ class DDNSProviderChangeIP(DDNSProvider): url = "https://nic.changeip.com/nic/update" can_remove_records = False + supports_token_auth = False def update_protocol(self, proto): data = { @@ -614,8 +627,9 @@ class DDNSProviderDDNSS(DDNSProvider): # http://www.ddnss.de/info.php # http://www.megacomputing.de/2014/08/dyndns-service-response-time/#more-919 - url = "http://www.ddnss.de/upd.php" + url = "https://www.ddnss.de/upd.php" can_remove_records = False + supports_token_auth = False def update_protocol(self, proto): data = { @@ -642,10 +656,8 @@ class DDNSProviderDDNSS(DDNSProvider): response = self.send_request(self.url, data=data) # This provider sends the response code as part of the header. - header = response.info() - # Get status information from the header. - output = header.getheader('ddnss-response') + output = response.getheader('ddnss-response') # Handle success messages. if output == "good" or output == "nochg": @@ -678,8 +690,10 @@ class DDNSProviderDHS(DDNSProvider): # No information about the used update api provided on webpage, # grabed from source code of ez-ipudate. - url = "http://members.dhs.org/nic/hosts" + # Provider currently does not support TLS 1.2. + url = "https://members.dhs.org/nic/hosts" can_remove_records = False + supports_token_auth = False def update_protocol(self, proto): data = { @@ -712,6 +726,7 @@ class DDNSProviderDNSpark(DDNSProvider): url = "https://control.dnspark.com/api/dynamic/update.php" can_remove_records = False + supports_token_auth = False def update_protocol(self, proto): data = { @@ -760,6 +775,7 @@ class DDNSProviderDtDNS(DDNSProvider): url = "https://www.dtdns.com/api/autodns.cfm" can_remove_records = False + supports_token_auth = False def update_protocol(self, proto): data = { @@ -804,16 +820,63 @@ class DDNSProviderDtDNS(DDNSProvider): raise DDNSUpdateError -class DDNSProviderDuckDNS(DDNSProtocolDynDNS2, DDNSProvider): +class DDNSProviderDuckDNS(DDNSProvider): handle = "duckdns.org" name = "Duck DNS" website = "http://www.duckdns.org/" - protocols = ("ipv4",) + protocols = ("ipv6", "ipv4",) # Information about the format of the request is to be found - # https://www.duckdns.org/install.jsp + # https://www.duckdns.org/spec.jsp + + url = "https://www.duckdns.org/update" + can_remove_records = False + supports_token_auth = True + + def update(self): + # Raise an error if no auth details are given. + if not self.token: + raise DDNSConfigurationError + + data = { + "domains" : self.hostname, + "token" : self.token, + } + + # Check if we update an IPv4 address. + address4 = self.get_address("ipv4") + if address4: + data["ip"] = address4 + + # Check if we update an IPv6 address. + address6 = self.get_address("ipv6") + if address6: + data["ipv6"] = address6 + + # Raise an error if no address is given. + if "ip" not in data and "ipv6" not in data: + raise DDNSConfigurationError + + # Send update to the server. + response = self.send_request(self.url, data=data) + + # Get the full response message. + output = response.read().decode() + + # Remove all leading and trailing whitespace. + output = output.strip() + + # Handle success messages. + if output == "OK": + return - url = "https://www.duckdns.org/nic/update" + # The provider does not give detailed information + # if the update fails. Only a "KO" will be sent back. + if output == "KO": + raise DDNSUpdateError + + # If we got here, some other update error happened. + raise DDNSUpdateError class DDNSProviderDyFi(DDNSProtocolDynDNS2, DDNSProvider): @@ -826,7 +889,7 @@ class DDNSProviderDyFi(DDNSProtocolDynDNS2, DDNSProvider): # https://www.dy.fi/page/clients?lang=en # https://www.dy.fi/page/specification?lang=en - url = "http://www.dy.fi/nic/update" + url = "https://www.dy.fi/nic/update" # Please only send automatic updates when your IP address changes, # or once per 5 to 6 days to refresh the address mapping (they will @@ -870,6 +933,7 @@ class DDNSProviderDynUp(DDNSProvider): url = "https://dynup.de/dyn.php" can_remove_records = False + supports_token_auth = False def update_protocol(self, proto): data = { @@ -933,7 +997,9 @@ class DDNSProviderEasyDNS(DDNSProvider): # (API 1.3) are available on the providers webpage. # https://fusion.easydns.com/index.php?/Knowledgebase/Article/View/102/7/dynamic-dns - url = "http://api.cp.easydns.com/dyn/tomato.php" + url = "https://api.cp.easydns.com/dyn/tomato.php" + + supports_token_auth = False def update_protocol(self, proto): data = { @@ -979,7 +1045,8 @@ class DDNSProviderDomopoli(DDNSProtocolDynDNS2, DDNSProvider): # https://www.domopoli.de/?page=howto#DynDns_start - url = "http://dyndns.domopoli.de/nic/update" + # This provider does not support TLS 1.2. + url = "https://dyndns.domopoli.de/nic/update" class DDNSProviderDynsNet(DDNSProvider): @@ -988,12 +1055,13 @@ class DDNSProviderDynsNet(DDNSProvider): website = "http://www.dyns.net/" protocols = ("ipv4",) can_remove_records = False + supports_token_auth = False # There is very detailed informatio about how to send the update request and # the possible response codes. (Currently we are using the v1.1 proto) # http://www.dyns.net/documentation/technical/protocol/ - url = "http://www.dyns.net/postscript011.php" + url = "https://www.dyns.net/postscript011.php" def update_protocol(self, proto): data = { @@ -1039,6 +1107,7 @@ class DDNSProviderEnomCom(DDNSResponseParserXML, DDNSProvider): url = "https://dynamic.name-services.com/interface.asp" can_remove_records = False + supports_token_auth = False def update_protocol(self, proto): data = { @@ -1081,6 +1150,7 @@ class DDNSProviderEntryDNS(DDNSProvider): # here: https://entrydns.net/help url = "https://entrydns.net/records/modify" can_remove_records = False + supports_token_auth = True def update_protocol(self, proto): data = { @@ -1119,25 +1189,23 @@ class DDNSProviderFreeDNSAfraidOrg(DDNSProvider): # No information about the request or response could be found on the vendor # page. All used values have been collected by testing. - url = "https://freedns.afraid.org/dynamic/update.php" + url = "https://sync.afraid.org/u/" can_remove_records = False + supports_token_auth = True def update_protocol(self, proto): - data = { - "address" : self.get_address(proto), - } # Add auth token to the update url. - url = "%s?%s" % (self.url, self.token) + url = "%s%s/" % (self.url, self.token) # Send update to the server. - response = self.send_request(url, data=data) + response = self.send_request(url) # Get the full response message. output = response.read().decode() # Handle success messages. - if output.startswith("Updated") or "has not changed" in output: + if output.startswith("Updated") or output.startswith("No IP change detected"): return # Handle error codes. @@ -1150,6 +1218,71 @@ class DDNSProviderFreeDNSAfraidOrg(DDNSProvider): raise DDNSUpdateError +class DDNSProviderGodaddy(DDNSProvider): + handle = "godaddy.com" + name = "godaddy.com" + website = "https://godaddy.com/" + protocols = ("ipv4",) + + # Information about the format of the HTTP request is to be found + # here: https://developer.godaddy.com/doc/endpoint/domains#/v1/recordReplaceTypeName + url = "https://api.godaddy.com/v1/domains/" + can_remove_records = False + + def update_protocol(self, proto): + # retrieve ip + ip_address = self.get_address(proto) + + # set target url + url = f"{self.url}/{self.hostname}/records/A/@" + + # prepare data + data = json.dumps([{"data": ip_address, "ttl": 600, "name": self.hostname, "type": "A"}]).encode("utf-8") + + # Method requires authentication by special headers. + request = urllib.request.Request(url=url, + data=data, + headers={"Authorization": f"sso-key {self.username}:{self.password}", + "Content-Type": "application/json"}, + method="PUT") + result = urllib.request.urlopen(request) + + # handle success + if result.code == 200: + return + + # handle errors + if result.code == 400: + raise DDNSRequestError(_("Malformed request received.")) + if result.code in (401, 403): + raise DDNSAuthenticationError + if result.code == 404: + raise DDNSRequestError(_("Resource not found.")) + if result.code == 422: + raise DDNSRequestError(_("Record does not fulfill the schema.")) + if result.code == 429: + raise DDNSRequestError(_("API Rate limiting.")) + + # If we got here, some other update error happened. + raise DDNSUpdateError + + +class DDNSProviderHENet(DDNSProtocolDynDNS2, DDNSProvider): + handle = "he.net" + name = "he.net" + website = "https://he.net" + protocols = ("ipv6", "ipv4",) + + # Detailed information about the update api can be found here. + # http://dns.he.net/docs.html + + url = "https://dyn.dns.he.net/nic/update" + @property + def username(self): + return self.get("hostname") + + + class DDNSProviderItsdns(DDNSProtocolDynDNS2, DDNSProvider): handle = "inwx.com" name = "INWX" @@ -1202,6 +1335,7 @@ class DDNSProviderKEYSYSTEMS(DDNSProvider): url = "https://dynamicdns.key-systems.net/update.php" can_remove_records = False + supports_token_auth = False def update_protocol(self, proto): address = self.get_address(proto) @@ -1253,6 +1387,8 @@ class DDNSProviderLightningWireLabs(DDNSProvider): # Information about the format of the HTTPS request is to be found # https://dns.lightningwirelabs.com/knowledge-base/api/ddns + supports_token_auth = True + url = "https://dns.lightningwirelabs.com/update" def update(self): @@ -1321,6 +1457,7 @@ class DDNSProviderNamecheap(DDNSResponseParserXML, DDNSProvider): url = "https://dynamicdns.park-your-domain.com/update" can_remove_records = False + supports_token_auth = False def update_protocol(self, proto): # Namecheap requires the hostname splitted into a host and domain part. @@ -1372,7 +1509,7 @@ class DDNSProviderNOIP(DDNSProtocolDynDNS2, DDNSProvider): # here: http://www.noip.com/integrate/request and # here: http://www.noip.com/integrate/response - url = "http://dynupdate.noip.com/nic/update" + url = "https://dynupdate.noip.com/nic/update" def prepare_request_data(self, proto): assert proto == "ipv4" @@ -1414,6 +1551,8 @@ class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2, DDNSProvider): # has not been implemented here, yet. can_remove_records = False + supports_token_auth = True + # After a failed update, there will be no retries # https://bugzilla.ipfire.org/show_bug.cgi?id=10603 holdoff_failure_days = None @@ -1490,6 +1629,7 @@ class DDNSProviderRegfish(DDNSProvider): url = "https://dyndns.regfish.de/" can_remove_records = False + supports_token_auth = True def update(self): data = { @@ -1586,6 +1726,7 @@ class DDNSProviderServercow(DDNSProvider): url = "https://www.servercow.de/dnsupdate/update.php" can_remove_records = False + supports_token_auth = False def update_protocol(self, proto): data = { @@ -1627,6 +1768,8 @@ class DDNSProviderSPDNS(DDNSProtocolDynDNS2, DDNSProvider): url = "https://update.spdyn.de/nic/update" + supports_token_auth = True + @property def username(self): return self.get("username") or self.hostname @@ -1730,6 +1873,8 @@ class DDNSProviderZoneedit(DDNSProvider): website = "http://www.zoneedit.com" protocols = ("ipv4",) + supports_token_auth = False + # Detailed information about the request and the response codes can be # obtained here: # http://www.zoneedit.com/doc/api/other.html @@ -1777,6 +1922,7 @@ class DDNSProviderDNSmadeEasy(DDNSProvider): url = "https://cp.dnsmadeeasy.com/servlet/updateip?" can_remove_records = False + supports_token_auth = False def update_protocol(self, proto): data = { @@ -1827,6 +1973,7 @@ class DDNSProviderZZZZ(DDNSProvider): url = "https://zzzz.io/api/v1/update" can_remove_records = False + supports_token_auth = True def update_protocol(self, proto): data = { @@ -1858,3 +2005,15 @@ class DDNSProviderZZZZ(DDNSProvider): # If we got here, some other update error happened. raise DDNSUpdateError + +class DDNSProviderInfomaniak(DDNSProtocolDynDNS2, DDNSProvider): + handle = "infomaniak.ch" + name = "infomaniak" + website = "https://www.infomaniak.ch" + protocols = ("ipv4",) + + # Detailed information about how to send the update request and possible response + # codes can be obtained from here. + # https://www.infomaniak.com/de/support/faq/2376/dyndns-aktualisieren-eines-dynamischen-dns-uber-die-api + + url = "https://infomaniak.com/nic/update"