]>
git.ipfire.org Git - oddments/ddns.git/blob - src/ddns/providers.py
2 ###############################################################################
4 # ddns - A dynamic DNS client for IPFire #
5 # Copyright (C) 2012-2017 IPFire development team #
7 # This program is free software: you can redistribute it and/or modify #
8 # it under the terms of the GNU General Public License as published by #
9 # the Free Software Foundation, either version 3 of the License, or #
10 # (at your option) any later version. #
12 # This program is distributed in the hope that it will be useful, #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15 # GNU General Public License for more details. #
17 # You should have received a copy of the GNU General Public License #
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
20 ###############################################################################
29 import xml
.dom
.minidom
33 # Import all possible exception types.
36 logger
= logging
.getLogger("ddns.providers")
43 Returns a dict with all automatically registered providers.
45 return _providers
.copy()
47 class DDNSProvider(object):
48 # A short string that uniquely identifies
52 # The full name of the provider.
55 # A weburl to the homepage of the provider.
56 # (Where to register a new account?)
59 # A list of supported protocols.
60 protocols
= ("ipv6", "ipv4")
64 # holdoff time - Number of days no update is performed unless
65 # the IP address has changed.
68 # holdoff time for update failures - Number of days no update
69 # is tried after the last one has failed.
70 holdoff_failure_days
= 0.5
72 # True if the provider is able to remove records, too.
73 # Required to remove AAAA records if IPv6 is absent again.
74 can_remove_records
= True
76 # Automatically register all providers.
77 class __metaclass__(type):
78 def __init__(provider
, name
, bases
, dict):
79 type.__init
__(provider
, name
, bases
, dict)
81 # The main class from which is inherited is not registered
83 if name
== "DDNSProvider":
86 if not all((provider
.handle
, provider
.name
, provider
.website
)):
87 raise DDNSError(_("Provider is not properly configured"))
89 assert provider
.handle
not in _providers
, \
90 "Provider '%s' has already been registered" % provider
.handle
92 _providers
[provider
.handle
] = provider
97 Should be overwritten to check if the system the code is running
98 on has all the required tools to support this provider.
102 def __init__(self
, core
, **settings
):
105 # Copy a set of default settings and
106 # update them by those from the configuration file.
107 self
.settings
= self
.DEFAULT_SETTINGS
.copy()
108 self
.settings
.update(settings
)
111 return "<DDNS Provider %s (%s)>" % (self
.name
, self
.handle
)
113 def __cmp__(self
, other
):
114 return (lambda a
, b
: (a
> b
)-(a
< b
))(self
.hostname
, other
.hostname
)
120 def get(self
, key
, default
=None):
122 Get a setting from the settings dictionary.
124 return self
.settings
.get(key
, default
)
129 Fast access to the hostname.
131 return self
.get("hostname")
136 Fast access to the username.
138 return self
.get("username")
143 Fast access to the password.
145 return self
.get("password")
150 Fast access to the token.
152 return self
.get("token")
154 def __call__(self
, force
=False):
156 logger
.debug(_("Updating %s forced") % self
.hostname
)
158 # Do nothing if the last update has failed or no update is required
159 elif self
.has_failure
or not self
.requires_update
:
162 # Execute the update.
166 # 1) Catch network errors early, because we do not want to log
167 # them to the database. They are usually temporary and caused
168 # by the client side, so that we will retry quickly.
169 # 2) If there is an internet server error (HTTP code 500) on the
170 # provider's site, we will not log a failure and try again
172 except (DDNSNetworkError
, DDNSInternalServerError
):
175 # In case of any errors, log the failed request and
176 # raise the exception.
177 except DDNSError
as e
:
178 self
.core
.db
.log_failure(self
.hostname
, e
)
181 logger
.info(_("Dynamic DNS update for %(hostname)s (%(provider)s) successful") %
182 {"hostname": self
.hostname
, "provider": self
.name
})
183 self
.core
.db
.log_success(self
.hostname
)
186 for protocol
in self
.protocols
:
187 if self
.have_address(protocol
):
188 self
.update_protocol(protocol
)
189 elif self
.can_remove_records
:
190 self
.remove_protocol(protocol
)
192 def update_protocol(self
, proto
):
193 raise NotImplementedError
195 def remove_protocol(self
, proto
):
196 if not self
.can_remove_records
:
197 raise RuntimeError("can_remove_records is enabled, but remove_protocol() not implemented")
199 raise NotImplementedError
202 def requires_update(self
):
203 # If the IP addresses have changed, an update is required
204 if self
.ip_address_changed(self
.protocols
):
205 logger
.debug(_("An update for %(hostname)s (%(provider)s) is performed because of an IP address change") %
206 {"hostname": self
.hostname
, "provider": self
.name
})
210 # If the holdoff time has expired, an update is required, too
211 if self
.holdoff_time_expired():
212 logger
.debug(_("An update for %(hostname)s (%(provider)s) is performed because the holdoff time has expired") %
213 {"hostname": self
.hostname
, "provider": self
.name
})
217 # Otherwise, we don't need to perform an update
218 logger
.debug(_("No update required for %(hostname)s (%(provider)s)") %
219 {"hostname": self
.hostname
, "provider": self
.name
})
224 def has_failure(self
):
226 Returns True when the last update has failed and no retry
227 should be performed, yet.
229 last_status
= self
.db
.last_update_status(self
.hostname
)
231 # Return False if the last update has not failed.
232 if not last_status
== "failure":
235 # If there is no holdoff time, we won't update ever again.
236 if self
.holdoff_failure_days
is None:
237 logger
.warning(_("An update has not been performed because earlier updates failed for %s") % self
.hostname
)
238 logger
.warning(_("There will be no retries"))
242 # Determine when the holdoff time ends
243 last_update
= self
.db
.last_update(self
.hostname
, status
=last_status
)
244 holdoff_end
= last_update
+ datetime
.timedelta(days
=self
.holdoff_failure_days
)
246 now
= datetime
.datetime
.utcnow()
247 if now
< holdoff_end
:
248 failure_message
= self
.db
.last_update_failure_message(self
.hostname
)
250 logger
.warning(_("An update has not been performed because earlier updates failed for %s") % self
.hostname
)
253 logger
.warning(_("Last failure message:"))
255 for line
in failure_message
.splitlines():
256 logger
.warning(" %s" % line
)
258 logger
.warning(_("Further updates will be withheld until %s") % holdoff_end
)
264 def ip_address_changed(self
, protos
):
266 Returns True if this host is already up to date
267 and does not need to change the IP address on the
271 addresses
= self
.core
.system
.resolve(self
.hostname
, proto
)
272 current_address
= self
.get_address(proto
)
274 # Handle if the system has not got any IP address from a protocol
275 # (i.e. had full dual-stack connectivity which it has not any more)
276 if current_address
is None:
277 # If addresses still exists in the DNS system and if this provider
278 # is able to remove records, we will do that.
279 if addresses
and self
.can_remove_records
:
282 # Otherwise, we cannot go on...
285 if not current_address
in addresses
:
290 def holdoff_time_expired(self
):
292 Returns true if the holdoff time has expired
293 and the host requires an update
295 # If no holdoff days is defined, we cannot go on
296 if not self
.holdoff_days
:
299 # Get the timestamp of the last successfull update
300 last_update
= self
.db
.last_update(self
.hostname
, status
="success")
302 # If no timestamp has been recorded, no update has been
303 # performed. An update should be performed now.
307 # Determine when the holdoff time ends
308 holdoff_end
= last_update
+ datetime
.timedelta(days
=self
.holdoff_days
)
310 now
= datetime
.datetime
.utcnow()
312 if now
>= holdoff_end
:
313 logger
.debug("The holdoff time has expired for %s" % self
.hostname
)
316 logger
.debug("Updates for %s are held off until %s" %
317 (self
.hostname
, holdoff_end
))
320 def send_request(self
, *args
, **kwargs
):
322 Proxy connection to the send request
325 return self
.core
.system
.send_request(*args
, **kwargs
)
327 def get_address(self
, proto
, default
=None):
329 Proxy method to get the current IP address.
331 return self
.core
.system
.get_address(proto
) or default
333 def have_address(self
, proto
):
335 Returns True if an IP address for the given protocol
338 address
= self
.get_address(proto
)
346 class DDNSProtocolDynDNS2(object):
348 This is an abstract class that implements the DynDNS updater
349 protocol version 2. As this is a popular way to update dynamic
350 DNS records, this class is supposed make the provider classes
354 # Information about the format of the request is to be found
355 # http://dyn.com/support/developers/api/perform-update/
356 # http://dyn.com/support/developers/api/return-codes/
358 # The DynDNS protocol version 2 does not allow to remove records
359 can_remove_records
= False
361 def prepare_request_data(self
, proto
):
363 "hostname" : self
.hostname
,
364 "myip" : self
.get_address(proto
),
369 def update_protocol(self
, proto
):
370 data
= self
.prepare_request_data(proto
)
372 return self
.send_request(data
)
374 def send_request(self
, data
):
375 # Send update to the server.
376 response
= DDNSProvider
.send_request(self
, self
.url
, data
=data
, username
=self
.username
, password
=self
.password
)
378 # Get the full response message.
379 output
= response
.read()
381 # Handle success messages.
382 if output
.startswith("good") or output
.startswith("nochg"):
385 # Handle error codes.
386 if output
== "badauth":
387 raise DDNSAuthenticationError
388 elif output
== "abuse":
390 elif output
== "notfqdn":
391 raise DDNSRequestError(_("No valid FQDN was given"))
392 elif output
== "nohost":
393 raise DDNSRequestError(_("Specified host does not exist"))
394 elif output
== "911":
395 raise DDNSInternalServerError
396 elif output
== "dnserr":
397 raise DDNSInternalServerError(_("DNS error encountered"))
398 elif output
== "badagent":
399 raise DDNSBlockedError
400 elif output
== "badip":
401 raise DDNSBlockedError
403 # If we got here, some other update error happened.
404 raise DDNSUpdateError(_("Server response: %s") % output
)
407 class DDNSResponseParserXML(object):
409 This class provides a parser for XML responses which
410 will be sent by various providers. This class uses the python
411 shipped XML minidom module to walk through the XML tree and return
415 def get_xml_tag_value(self
, document
, content
):
416 # Send input to the parser.
417 xmldoc
= xml
.dom
.minidom
.parseString(document
)
419 # Get XML elements by the given content.
420 element
= xmldoc
.getElementsByTagName(content
)
422 # If no element has been found, we directly can return None.
426 # Only get the first child from an element, even there are more than one.
427 firstchild
= element
[0].firstChild
429 # Get the value of the child.
430 value
= firstchild
.nodeValue
436 class DDNSProviderAllInkl(DDNSProvider
):
437 handle
= "all-inkl.com"
438 name
= "All-inkl.com"
439 website
= "http://all-inkl.com/"
440 protocols
= ("ipv4",)
442 # There are only information provided by the vendor how to
443 # perform an update on a FRITZ Box. Grab requried informations
445 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
447 url
= "http://dyndns.kasserver.com"
448 can_remove_records
= False
451 # There is no additional data required so we directly can
453 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
)
455 # Get the full response message.
456 output
= response
.read()
458 # Handle success messages.
459 if output
.startswith("good") or output
.startswith("nochg"):
462 # If we got here, some other update error happened.
463 raise DDNSUpdateError
466 class DDNSProviderBindNsupdate(DDNSProvider
):
468 name
= "BIND nsupdate utility"
469 website
= "http://en.wikipedia.org/wiki/Nsupdate"
475 # Search if the nsupdate utility is available
476 paths
= os
.environ
.get("PATH")
478 for path
in paths
.split(":"):
479 executable
= os
.path
.join(path
, "nsupdate")
481 if os
.path
.exists(executable
):
487 scriptlet
= self
.__make
_scriptlet
()
489 # -v enables TCP hence we transfer keys and other data that may
490 # exceed the size of one packet.
491 # -t sets the timeout
492 command
= ["nsupdate", "-v", "-t", "60"]
494 p
= subprocess
.Popen(command
, shell
=True, stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
)
495 stdout
, stderr
= p
.communicate(scriptlet
)
497 if p
.returncode
== 0:
500 raise DDNSError("nsupdate terminated with error code: %s\n %s" % (p
.returncode
, stderr
))
502 def __make_scriptlet(self
):
505 # Set a different server the update is sent to.
506 server
= self
.get("server", None)
508 scriptlet
.append("server %s" % server
)
510 # Set the DNS zone the host should be added to.
511 zone
= self
.get("zone", None)
513 scriptlet
.append("zone %s" % zone
)
515 key
= self
.get("key", None)
517 secret
= self
.get("secret")
519 scriptlet
.append("key %s %s" % (key
, secret
))
521 ttl
= self
.get("ttl", self
.DEFAULT_TTL
)
523 # Perform an update for each supported protocol.
524 for rrtype
, proto
in (("AAAA", "ipv6"), ("A", "ipv4")):
525 address
= self
.get_address(proto
)
529 scriptlet
.append("update delete %s. %s" % (self
.hostname
, rrtype
))
530 scriptlet
.append("update add %s. %s %s %s" % \
531 (self
.hostname
, ttl
, rrtype
, address
))
533 # Send the actions to the server.
534 scriptlet
.append("send")
535 scriptlet
.append("quit")
537 logger
.debug(_("Scriptlet:"))
538 for line
in scriptlet
:
539 # Masquerade the line with the secret key.
540 if line
.startswith("key"):
541 line
= "key **** ****"
543 logger
.debug(" %s" % line
)
545 return "\n".join(scriptlet
)
548 class DDNSProviderChangeIP(DDNSProvider
):
549 handle
= "changeip.com"
550 name
= "ChangeIP.com"
551 website
= "https://changeip.com"
552 protocols
= ("ipv4",)
554 # Detailed information about the update api can be found here.
555 # http://www.changeip.com/accounts/knowledgebase.php?action=displayarticle&id=34
557 url
= "https://nic.changeip.com/nic/update"
558 can_remove_records
= False
560 def update_protocol(self
, proto
):
562 "hostname" : self
.hostname
,
563 "myip" : self
.get_address(proto
),
566 # Send update to the server.
568 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
, data
=data
)
570 # Handle error codes.
571 except urllib
.error
.HTTPError
as e
:
573 raise DDNSRequestError(_("Domain not found."))
577 # Handle success message.
578 if response
.code
== 200:
581 # If we got here, some other update error happened.
582 raise DDNSUpdateError(_("Server response: %s") % output
)
585 class DDNSProviderDesecIO(DDNSProtocolDynDNS2
, DDNSProvider
):
588 website
= "https://www.desec.io"
589 protocols
= ("ipv6", "ipv4",)
591 # ipv4 / ipv6 records are automatically removed when the update
592 # request originates from the respectively other protocol and no
593 # address is explicitly provided for the unused protocol.
595 url
= "https://update.dedyn.io"
597 # desec.io sends the IPv6 and IPv4 address in one request
600 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, "ipv4")
602 # This one supports IPv6
603 myipv6
= self
.get_address("ipv6")
605 # Add update information if we have an IPv6 address.
607 data
["myipv6"] = myipv6
609 self
.send_request(data
)
612 class DDNSProviderDDNSS(DDNSProvider
):
615 website
= "http://www.ddnss.de"
616 protocols
= ("ipv4",)
618 # Detailed information about how to send the update request and possible response
619 # codes can be obtained from here.
620 # http://www.ddnss.de/info.php
621 # http://www.megacomputing.de/2014/08/dyndns-service-response-time/#more-919
623 url
= "http://www.ddnss.de/upd.php"
624 can_remove_records
= False
626 def update_protocol(self
, proto
):
628 "ip" : self
.get_address(proto
),
629 "host" : self
.hostname
,
632 # Check if a token has been set.
634 data
["key"] = self
.token
636 # Check if username and hostname are given.
637 elif self
.username
and self
.password
:
639 "user" : self
.username
,
640 "pwd" : self
.password
,
643 # Raise an error if no auth details are given.
645 raise DDNSConfigurationError
647 # Send update to the server.
648 response
= self
.send_request(self
.url
, data
=data
)
650 # This provider sends the response code as part of the header.
651 header
= response
.info()
653 # Get status information from the header.
654 output
= header
.getheader('ddnss-response')
656 # Handle success messages.
657 if output
== "good" or output
== "nochg":
660 # Handle error codes.
661 if output
== "badauth":
662 raise DDNSAuthenticationError
663 elif output
== "notfqdn":
664 raise DDNSRequestError(_("No valid FQDN was given"))
665 elif output
== "nohost":
666 raise DDNSRequestError(_("Specified host does not exist"))
667 elif output
== "911":
668 raise DDNSInternalServerError
669 elif output
== "dnserr":
670 raise DDNSInternalServerError(_("DNS error encountered"))
671 elif output
== "disabled":
672 raise DDNSRequestError(_("Account disabled or locked"))
674 # If we got here, some other update error happened.
675 raise DDNSUpdateError
678 class DDNSProviderDHS(DDNSProvider
):
680 name
= "DHS International"
681 website
= "http://dhs.org/"
682 protocols
= ("ipv4",)
684 # No information about the used update api provided on webpage,
685 # grabed from source code of ez-ipudate.
687 url
= "http://members.dhs.org/nic/hosts"
688 can_remove_records
= False
690 def update_protocol(self
, proto
):
692 "domain" : self
.hostname
,
693 "ip" : self
.get_address(proto
),
695 "hostcmdstage" : "2",
699 # Send update to the server.
700 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
, data
=data
)
702 # Handle success messages.
703 if response
.code
== 200:
706 # If we got here, some other update error happened.
707 raise DDNSUpdateError
710 class DDNSProviderDNSpark(DDNSProvider
):
711 handle
= "dnspark.com"
713 website
= "http://dnspark.com/"
714 protocols
= ("ipv4",)
716 # Informations to the used api can be found here:
717 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
719 url
= "https://control.dnspark.com/api/dynamic/update.php"
720 can_remove_records
= False
722 def update_protocol(self
, proto
):
724 "domain" : self
.hostname
,
725 "ip" : self
.get_address(proto
),
728 # Send update to the server.
729 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
, data
=data
)
731 # Get the full response message.
732 output
= response
.read()
734 # Handle success messages.
735 if output
.startswith("ok") or output
.startswith("nochange"):
738 # Handle error codes.
739 if output
== "unauth":
740 raise DDNSAuthenticationError
741 elif output
== "abuse":
743 elif output
== "blocked":
744 raise DDNSBlockedError
745 elif output
== "nofqdn":
746 raise DDNSRequestError(_("No valid FQDN was given"))
747 elif output
== "nohost":
748 raise DDNSRequestError(_("Invalid hostname specified"))
749 elif output
== "notdyn":
750 raise DDNSRequestError(_("Hostname not marked as a dynamic host"))
751 elif output
== "invalid":
752 raise DDNSRequestError(_("Invalid IP address has been sent"))
754 # If we got here, some other update error happened.
755 raise DDNSUpdateError
758 class DDNSProviderDtDNS(DDNSProvider
):
761 website
= "http://dtdns.com/"
762 protocols
= ("ipv4",)
764 # Information about the format of the HTTPS request is to be found
765 # http://www.dtdns.com/dtsite/updatespec
767 url
= "https://www.dtdns.com/api/autodns.cfm"
768 can_remove_records
= False
770 def update_protocol(self
, proto
):
772 "ip" : self
.get_address(proto
),
773 "id" : self
.hostname
,
777 # Send update to the server.
778 response
= self
.send_request(self
.url
, data
=data
)
780 # Get the full response message.
781 output
= response
.read()
783 # Remove all leading and trailing whitespace.
784 output
= output
.strip()
786 # Handle success messages.
787 if "now points to" in output
:
790 # Handle error codes.
791 if output
== "No hostname to update was supplied.":
792 raise DDNSRequestError(_("No hostname specified"))
794 elif output
== "The hostname you supplied is not valid.":
795 raise DDNSRequestError(_("Invalid hostname specified"))
797 elif output
== "The password you supplied is not valid.":
798 raise DDNSAuthenticationError
800 elif output
== "Administration has disabled this account.":
801 raise DDNSRequestError(_("Account has been disabled"))
803 elif output
== "Illegal character in IP.":
804 raise DDNSRequestError(_("Invalid IP address has been sent"))
806 elif output
== "Too many failed requests.":
807 raise DDNSRequestError(_("Too many failed requests"))
809 # If we got here, some other update error happened.
810 raise DDNSUpdateError
813 class DDNSProviderDuckDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
814 handle
= "duckdns.org"
816 website
= "http://www.duckdns.org/"
817 protocols
= ("ipv4",)
819 # Information about the format of the request is to be found
820 # https://www.duckdns.org/install.jsp
822 url
= "https://www.duckdns.org/nic/update"
825 class DDNSProviderDyFi(DDNSProtocolDynDNS2
, DDNSProvider
):
828 website
= "https://www.dy.fi/"
829 protocols
= ("ipv4",)
831 # Information about the format of the request is to be found
832 # https://www.dy.fi/page/clients?lang=en
833 # https://www.dy.fi/page/specification?lang=en
835 url
= "http://www.dy.fi/nic/update"
837 # Please only send automatic updates when your IP address changes,
838 # or once per 5 to 6 days to refresh the address mapping (they will
839 # expire if not refreshed within 7 days).
843 class DDNSProviderDynDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
844 handle
= "dyndns.org"
846 website
= "http://dyn.com/dns/"
847 protocols
= ("ipv4",)
849 # Information about the format of the request is to be found
850 # http://http://dyn.com/support/developers/api/perform-update/
851 # http://dyn.com/support/developers/api/return-codes/
853 url
= "https://members.dyndns.org/nic/update"
856 class DDNSProviderDomainOffensive(DDNSProtocolDynDNS2
, DDNSProvider
):
858 name
= "Domain-Offensive"
859 website
= "https://www.do.de/"
860 protocols
= ("ipv6", "ipv4")
862 # Detailed information about the request and response codes
863 # are available on the providers webpage.
864 # https://www.do.de/wiki/FlexDNS_-_Entwickler
866 url
= "https://ddns.do.de/"
868 class DDNSProviderDynUp(DDNSProvider
):
871 website
= "http://dynup.de/"
872 protocols
= ("ipv4",)
874 # Information about the format of the HTTPS request is to be found
875 # https://dyndnsfree.de/user/hilfe.php
877 url
= "https://dynup.de/dyn.php"
878 can_remove_records
= False
880 def update_protocol(self
, proto
):
882 "username" : self
.username
,
883 "password" : self
.password
,
884 "hostname" : self
.hostname
,
888 # Send update to the server.
889 response
= self
.send_request(self
.url
, data
=data
)
891 # Get the full response message.
892 output
= response
.read()
894 # Remove all leading and trailing whitespace.
895 output
= output
.strip()
897 # Handle success messages.
898 if output
.startswith("I:OK"):
901 # If we got here, some other update error happened.
902 raise DDNSUpdateError
905 class DDNSProviderDynU(DDNSProtocolDynDNS2
, DDNSProvider
):
908 website
= "http://dynu.com/"
909 protocols
= ("ipv6", "ipv4",)
911 # Detailed information about the request and response codes
912 # are available on the providers webpage.
913 # http://dynu.com/Default.aspx?page=dnsapi
915 url
= "https://api.dynu.com/nic/update"
917 # DynU sends the IPv6 and IPv4 address in one request
920 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, "ipv4")
922 # This one supports IPv6
923 myipv6
= self
.get_address("ipv6")
925 # Add update information if we have an IPv6 address.
927 data
["myipv6"] = myipv6
929 self
.send_request(data
)
932 class DDNSProviderEasyDNS(DDNSProvider
):
933 handle
= "easydns.com"
935 website
= "http://www.easydns.com/"
936 protocols
= ("ipv4",)
938 # Detailed information about the request and response codes
939 # (API 1.3) are available on the providers webpage.
940 # https://fusion.easydns.com/index.php?/Knowledgebase/Article/View/102/7/dynamic-dns
942 url
= "http://api.cp.easydns.com/dyn/tomato.php"
944 def update_protocol(self
, proto
):
946 "myip" : self
.get_address(proto
, "-"),
947 "hostname" : self
.hostname
,
950 # Send update to the server.
951 response
= self
.send_request(self
.url
, data
=data
, username
=self
.username
, password
=self
.password
)
953 # Get the full response message.
954 output
= response
.read()
956 # Remove all leading and trailing whitespace.
957 output
= output
.strip()
959 # Handle success messages.
960 if output
.startswith("NOERROR"):
963 # Handle error codes.
964 if output
.startswith("NOACCESS"):
965 raise DDNSAuthenticationError
967 elif output
.startswith("NOSERVICE"):
968 raise DDNSRequestError(_("Dynamic DNS is not turned on for this domain"))
970 elif output
.startswith("ILLEGAL INPUT"):
971 raise DDNSRequestError(_("Invalid data has been sent"))
973 elif output
.startswith("TOOSOON"):
974 raise DDNSRequestError(_("Too frequent update requests have been sent"))
976 # If we got here, some other update error happened.
977 raise DDNSUpdateError
980 class DDNSProviderDomopoli(DDNSProtocolDynDNS2
, DDNSProvider
):
981 handle
= "domopoli.de"
983 website
= "http://domopoli.de/"
984 protocols
= ("ipv4",)
986 # https://www.domopoli.de/?page=howto#DynDns_start
988 url
= "http://dyndns.domopoli.de/nic/update"
991 class DDNSProviderDynsNet(DDNSProvider
):
994 website
= "http://www.dyns.net/"
995 protocols
= ("ipv4",)
996 can_remove_records
= False
998 # There is very detailed informatio about how to send the update request and
999 # the possible response codes. (Currently we are using the v1.1 proto)
1000 # http://www.dyns.net/documentation/technical/protocol/
1002 url
= "http://www.dyns.net/postscript011.php"
1004 def update_protocol(self
, proto
):
1006 "ip" : self
.get_address(proto
),
1007 "host" : self
.hostname
,
1008 "username" : self
.username
,
1009 "password" : self
.password
,
1012 # Send update to the server.
1013 response
= self
.send_request(self
.url
, data
=data
)
1015 # Get the full response message.
1016 output
= response
.read()
1018 # Handle success messages.
1019 if output
.startswith("200"):
1022 # Handle error codes.
1023 if output
.startswith("400"):
1024 raise DDNSRequestError(_("Malformed request has been sent"))
1025 elif output
.startswith("401"):
1026 raise DDNSAuthenticationError
1027 elif output
.startswith("402"):
1028 raise DDNSRequestError(_("Too frequent update requests have been sent"))
1029 elif output
.startswith("403"):
1030 raise DDNSInternalServerError
1032 # If we got here, some other update error happened.
1033 raise DDNSUpdateError(_("Server response: %s") % output
)
1036 class DDNSProviderEnomCom(DDNSResponseParserXML
, DDNSProvider
):
1039 website
= "http://www.enom.com/"
1040 protocols
= ("ipv4",)
1042 # There are very detailed information about how to send an update request and
1043 # the respone codes.
1044 # http://www.enom.com/APICommandCatalog/
1046 url
= "https://dynamic.name-services.com/interface.asp"
1047 can_remove_records
= False
1049 def update_protocol(self
, proto
):
1051 "command" : "setdnshost",
1052 "responsetype" : "xml",
1053 "address" : self
.get_address(proto
),
1054 "domainpassword" : self
.password
,
1055 "zone" : self
.hostname
1058 # Send update to the server.
1059 response
= self
.send_request(self
.url
, data
=data
)
1061 # Get the full response message.
1062 output
= response
.read()
1064 # Handle success messages.
1065 if self
.get_xml_tag_value(output
, "ErrCount") == "0":
1068 # Handle error codes.
1069 errorcode
= self
.get_xml_tag_value(output
, "ResponseNumber")
1071 if errorcode
== "304155":
1072 raise DDNSAuthenticationError
1073 elif errorcode
== "304153":
1074 raise DDNSRequestError(_("Domain not found"))
1076 # If we got here, some other update error happened.
1077 raise DDNSUpdateError
1080 class DDNSProviderEntryDNS(DDNSProvider
):
1081 handle
= "entrydns.net"
1083 website
= "http://entrydns.net/"
1084 protocols
= ("ipv4",)
1086 # Some very tiny details about their so called "Simple API" can be found
1087 # here: https://entrydns.net/help
1088 url
= "https://entrydns.net/records/modify"
1089 can_remove_records
= False
1091 def update_protocol(self
, proto
):
1093 "ip" : self
.get_address(proto
),
1096 # Add auth token to the update url.
1097 url
= "%s/%s" % (self
.url
, self
.token
)
1099 # Send update to the server.
1101 response
= self
.send_request(url
, data
=data
)
1103 # Handle error codes
1104 except urllib
.error
.HTTPError
as e
:
1106 raise DDNSAuthenticationError
1109 raise DDNSRequestError(_("An invalid IP address was submitted"))
1113 # Handle success messages.
1114 if response
.code
== 200:
1117 # If we got here, some other update error happened.
1118 raise DDNSUpdateError
1121 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider
):
1122 handle
= "freedns.afraid.org"
1123 name
= "freedns.afraid.org"
1124 website
= "http://freedns.afraid.org/"
1126 # No information about the request or response could be found on the vendor
1127 # page. All used values have been collected by testing.
1128 url
= "https://freedns.afraid.org/dynamic/update.php"
1129 can_remove_records
= False
1131 def update_protocol(self
, proto
):
1133 "address" : self
.get_address(proto
),
1136 # Add auth token to the update url.
1137 url
= "%s?%s" % (self
.url
, self
.token
)
1139 # Send update to the server.
1140 response
= self
.send_request(url
, data
=data
)
1142 # Get the full response message.
1143 output
= response
.read()
1145 # Handle success messages.
1146 if output
.startswith("Updated") or "has not changed" in output
:
1149 # Handle error codes.
1150 if output
== "ERROR: Unable to locate this record":
1151 raise DDNSAuthenticationError
1152 elif "is an invalid IP address" in output
:
1153 raise DDNSRequestError(_("Invalid IP address has been sent"))
1155 # If we got here, some other update error happened.
1156 raise DDNSUpdateError
1159 class DDNSProviderItsdns(DDNSProtocolDynDNS2
, DDNSProvider
):
1162 website
= "https://www.inwx.com"
1163 protocols
= ("ipv6", "ipv4")
1165 # Information about the format of the HTTP request is to be found
1166 # here: https://www.inwx.com/en/nameserver2/dyndns (requires login)
1167 # Notice: The URL is the same for: inwx.com|de|at|ch|es
1169 url
= "https://dyndns.inwx.com/nic/update"
1172 class DDNSProviderItsdns(DDNSProtocolDynDNS2
, DDNSProvider
):
1173 handle
= "itsdns.de"
1175 website
= "http://www.itsdns.de/"
1176 protocols
= ("ipv6", "ipv4")
1178 # Information about the format of the HTTP request is to be found
1179 # here: https://www.itsdns.de/dynupdatehelp.htm
1181 url
= "https://www.itsdns.de/update.php"
1184 class DDNSProviderJoker(DDNSProtocolDynDNS2
, DDNSProvider
):
1185 handle
= "joker.com"
1186 name
= "Joker.com Dynamic DNS"
1187 website
= "https://joker.com/"
1188 protocols
= ("ipv4",)
1190 # Information about the request can be found here:
1191 # https://joker.com/faq/content/11/427/en/what-is-dynamic-dns-dyndns.html
1192 # Using DynDNS V2 protocol over HTTPS here
1194 url
= "https://svc.joker.com/nic/update"
1197 class DDNSProviderGoogle(DDNSProtocolDynDNS2
, DDNSProvider
):
1198 handle
= "domains.google.com"
1199 name
= "Google Domains"
1200 website
= "https://domains.google.com/"
1201 protocols
= ("ipv4",)
1203 # Information about the format of the HTTP request is to be found
1204 # here: https://support.google.com/domains/answer/6147083?hl=en
1206 url
= "https://domains.google.com/nic/update"
1209 class DDNSProviderLightningWireLabs(DDNSProvider
):
1210 handle
= "dns.lightningwirelabs.com"
1211 name
= "Lightning Wire Labs DNS Service"
1212 website
= "http://dns.lightningwirelabs.com/"
1214 # Information about the format of the HTTPS request is to be found
1215 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
1217 url
= "https://dns.lightningwirelabs.com/update"
1221 "hostname" : self
.hostname
,
1222 "address6" : self
.get_address("ipv6", "-"),
1223 "address4" : self
.get_address("ipv4", "-"),
1226 # Check if a token has been set.
1228 data
["token"] = self
.token
1230 # Check for username and password.
1231 elif self
.username
and self
.password
:
1233 "username" : self
.username
,
1234 "password" : self
.password
,
1237 # Raise an error if no auth details are given.
1239 raise DDNSConfigurationError
1241 # Send update to the server.
1242 response
= self
.send_request(self
.url
, data
=data
)
1244 # Handle success messages.
1245 if response
.code
== 200:
1248 # If we got here, some other update error happened.
1249 raise DDNSUpdateError
1252 class DDNSProviderLoopia(DDNSProtocolDynDNS2
, DDNSProvider
):
1253 handle
= "loopia.se"
1255 website
= "https://www.loopia.com"
1256 protocols
= ("ipv4",)
1258 # Information about the format of the HTTP request is to be found
1259 # here: https://support.loopia.com/wiki/About_the_DynDNS_support
1261 url
= "https://dns.loopia.se/XDynDNSServer/XDynDNS.php"
1264 class DDNSProviderMyOnlinePortal(DDNSProtocolDynDNS2
, DDNSProvider
):
1265 handle
= "myonlineportal.net"
1266 name
= "myonlineportal.net"
1267 website
= "https:/myonlineportal.net/"
1269 # Information about the request and response can be obtained here:
1270 # https://myonlineportal.net/howto_dyndns
1272 url
= "https://myonlineportal.net/updateddns"
1274 def prepare_request_data(self
, proto
):
1276 "hostname" : self
.hostname
,
1277 "ip" : self
.get_address(proto
),
1283 class DDNSProviderNamecheap(DDNSResponseParserXML
, DDNSProvider
):
1284 handle
= "namecheap.com"
1286 website
= "http://namecheap.com"
1287 protocols
= ("ipv4",)
1289 # Information about the format of the HTTP request is to be found
1290 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
1291 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
1293 url
= "https://dynamicdns.park-your-domain.com/update"
1294 can_remove_records
= False
1296 def update_protocol(self
, proto
):
1297 # Namecheap requires the hostname splitted into a host and domain part.
1298 host
, domain
= self
.hostname
.split(".", 1)
1300 # Get and store curent IP address.
1301 address
= self
.get_address(proto
)
1305 "password" : self
.password
,
1310 # Send update to the server.
1311 response
= self
.send_request(self
.url
, data
=data
)
1313 # Get the full response message.
1314 output
= response
.read()
1316 # Handle success messages.
1317 if self
.get_xml_tag_value(output
, "IP") == address
:
1320 # Handle error codes.
1321 errorcode
= self
.get_xml_tag_value(output
, "ResponseNumber")
1323 if errorcode
== "304156":
1324 raise DDNSAuthenticationError
1325 elif errorcode
== "316153":
1326 raise DDNSRequestError(_("Domain not found"))
1327 elif errorcode
== "316154":
1328 raise DDNSRequestError(_("Domain not active"))
1329 elif errorcode
in ("380098", "380099"):
1330 raise DDNSInternalServerError
1332 # If we got here, some other update error happened.
1333 raise DDNSUpdateError
1336 class DDNSProviderNOIP(DDNSProtocolDynDNS2
, DDNSProvider
):
1337 handle
= "no-ip.com"
1339 website
= "http://www.noip.com/"
1340 protocols
= ("ipv4",)
1342 # Information about the format of the HTTP request is to be found
1343 # here: http://www.noip.com/integrate/request and
1344 # here: http://www.noip.com/integrate/response
1346 url
= "http://dynupdate.noip.com/nic/update"
1348 def prepare_request_data(self
, proto
):
1349 assert proto
== "ipv4"
1352 "hostname" : self
.hostname
,
1353 "address" : self
.get_address(proto
),
1359 class DDNSProviderNowDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1360 handle
= "now-dns.com"
1362 website
= "http://now-dns.com/"
1363 protocols
= ("ipv6", "ipv4")
1365 # Information about the format of the request is to be found
1366 # but only can be accessed by register an account and login
1367 # https://now-dns.com/?m=api
1369 url
= "https://now-dns.com/update"
1372 class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2
, DDNSProvider
):
1373 handle
= "nsupdate.info"
1374 name
= "nsupdate.info"
1375 website
= "http://nsupdate.info/"
1376 protocols
= ("ipv6", "ipv4",)
1378 # Information about the format of the HTTP request can be found
1379 # after login on the provider user interface and here:
1380 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
1382 url
= "https://nsupdate.info/nic/update"
1384 # TODO nsupdate.info can actually do this, but the functionality
1385 # has not been implemented here, yet.
1386 can_remove_records
= False
1388 # After a failed update, there will be no retries
1389 # https://bugzilla.ipfire.org/show_bug.cgi?id=10603
1390 holdoff_failure_days
= None
1392 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
1393 # and for the password a so called secret.
1396 return self
.get("hostname")
1400 return self
.token
or self
.get("secret")
1402 def prepare_request_data(self
, proto
):
1404 "myip" : self
.get_address(proto
),
1410 class DDNSProviderOpenDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1411 handle
= "opendns.com"
1413 website
= "http://www.opendns.com"
1415 # Detailed information about the update request and possible
1416 # response codes can be obtained from here:
1417 # https://support.opendns.com/entries/23891440
1419 url
= "https://updates.opendns.com/nic/update"
1421 def prepare_request_data(self
, proto
):
1423 "hostname" : self
.hostname
,
1424 "myip" : self
.get_address(proto
),
1430 class DDNSProviderOVH(DDNSProtocolDynDNS2
, DDNSProvider
):
1433 website
= "http://www.ovh.com/"
1434 protocols
= ("ipv4",)
1436 # OVH only provides very limited information about how to
1437 # update a DynDNS host. They only provide the update url
1438 # on the their german subpage.
1440 # http://hilfe.ovh.de/DomainDynHost
1442 url
= "https://www.ovh.com/nic/update"
1444 def prepare_request_data(self
, proto
):
1445 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1447 "system" : "dyndns",
1453 class DDNSProviderRegfish(DDNSProvider
):
1454 handle
= "regfish.com"
1455 name
= "Regfish GmbH"
1456 website
= "http://www.regfish.com/"
1458 # A full documentation to the providers api can be found here
1459 # but is only available in german.
1460 # https://www.regfish.de/domains/dyndns/dokumentation
1462 url
= "https://dyndns.regfish.de/"
1463 can_remove_records
= False
1467 "fqdn" : self
.hostname
,
1470 # Check if we update an IPv6 address.
1471 address6
= self
.get_address("ipv6")
1473 data
["ipv6"] = address6
1475 # Check if we update an IPv4 address.
1476 address4
= self
.get_address("ipv4")
1478 data
["ipv4"] = address4
1480 # Raise an error if none address is given.
1481 if "ipv6" not in data
and "ipv4" not in data
:
1482 raise DDNSConfigurationError
1484 # Check if a token has been set.
1486 data
["token"] = self
.token
1488 # Raise an error if no token and no useranem and password
1490 elif not self
.username
and not self
.password
:
1491 raise DDNSConfigurationError(_("No Auth details specified"))
1493 # HTTP Basic Auth is only allowed if no token is used.
1495 # Send update to the server.
1496 response
= self
.send_request(self
.url
, data
=data
)
1498 # Send update to the server.
1499 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
, data
=data
)
1501 # Get the full response message.
1502 output
= response
.read()
1504 # Handle success messages.
1505 if "100" in output
or "101" in output
:
1508 # Handle error codes.
1509 if "401" or "402" in output
:
1510 raise DDNSAuthenticationError
1511 elif "408" in output
:
1512 raise DDNSRequestError(_("Invalid IPv4 address has been sent"))
1513 elif "409" in output
:
1514 raise DDNSRequestError(_("Invalid IPv6 address has been sent"))
1515 elif "412" in output
:
1516 raise DDNSRequestError(_("No valid FQDN was given"))
1517 elif "414" in output
:
1518 raise DDNSInternalServerError
1520 # If we got here, some other update error happened.
1521 raise DDNSUpdateError
1524 class DDNSProviderSchokokeksDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1525 handle
= "schokokeks.org"
1527 website
= "http://www.schokokeks.org/"
1528 protocols
= ("ipv4",)
1530 # Information about the format of the request is to be found
1531 # https://wiki.schokokeks.org/DynDNS
1532 url
= "https://dyndns.schokokeks.org/nic/update"
1535 class DDNSProviderSelfhost(DDNSProtocolDynDNS2
, DDNSProvider
):
1536 handle
= "selfhost.de"
1537 name
= "Selfhost.de"
1538 website
= "http://www.selfhost.de/"
1539 protocols
= ("ipv4",)
1541 url
= "https://carol.selfhost.de/nic/update"
1543 def prepare_request_data(self
, proto
):
1544 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1552 class DDNSProviderServercow(DDNSProvider
):
1553 handle
= "servercow.de"
1554 name
= "servercow.de"
1555 website
= "https://servercow.de/"
1556 protocols
= ("ipv4", "ipv6")
1558 url
= "https://www.servercow.de/dnsupdate/update.php"
1559 can_remove_records
= False
1561 def update_protocol(self
, proto
):
1563 "ipaddr" : self
.get_address(proto
),
1564 "hostname" : self
.hostname
,
1565 "username" : self
.username
,
1566 "pass" : self
.password
,
1569 # Send request to provider
1570 response
= self
.send_request(self
.url
, data
=data
)
1573 output
= response
.read()
1575 # Server responds with OK if update was successful
1576 if output
.startswith("OK"):
1580 elif output
.startswith("FAILED - Authentication failed"):
1581 raise DDNSAuthenticationError
1583 # If we got here, some other update error happened
1584 raise DDNSUpdateError(output
)
1587 class DDNSProviderSPDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1588 handle
= "spdns.org"
1590 website
= "https://www.spdyn.de/"
1592 # Detailed information about request and response codes are provided
1593 # by the vendor. They are using almost the same mechanism and status
1594 # codes as dyndns.org so we can inherit all those stuff.
1596 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
1597 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
1599 url
= "https://update.spdyn.de/nic/update"
1603 return self
.get("username") or self
.hostname
1607 return self
.get("password") or self
.token
1610 class DDNSProviderStrato(DDNSProtocolDynDNS2
, DDNSProvider
):
1611 handle
= "strato.com"
1613 website
= "http:/www.strato.com/"
1614 protocols
= ("ipv4",)
1616 # Information about the request and response can be obtained here:
1617 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
1619 url
= "https://dyndns.strato.com/nic/update"
1621 def prepare_request_data(self
, proto
):
1622 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1625 "backupmx" : "NOCHG"
1631 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1632 handle
= "twodns.de"
1634 website
= "http://www.twodns.de"
1635 protocols
= ("ipv4",)
1637 # Detailed information about the request can be found here
1638 # http://twodns.de/en/faqs
1639 # http://twodns.de/en/api
1641 url
= "https://update.twodns.de/update"
1643 def prepare_request_data(self
, proto
):
1644 assert proto
== "ipv4"
1647 "ip" : self
.get_address(proto
),
1648 "hostname" : self
.hostname
1654 class DDNSProviderUdmedia(DDNSProtocolDynDNS2
, DDNSProvider
):
1655 handle
= "udmedia.de"
1656 name
= "Udmedia GmbH"
1657 website
= "http://www.udmedia.de"
1658 protocols
= ("ipv4",)
1660 # Information about the request can be found here
1661 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1663 url
= "https://www.udmedia.de/nic/update"
1666 class DDNSProviderVariomedia(DDNSProtocolDynDNS2
, DDNSProvider
):
1667 handle
= "variomedia.de"
1669 website
= "http://www.variomedia.de/"
1670 protocols
= ("ipv6", "ipv4",)
1672 # Detailed information about the request can be found here
1673 # https://dyndns.variomedia.de/
1675 url
= "https://dyndns.variomedia.de/nic/update"
1677 def prepare_request_data(self
, proto
):
1679 "hostname" : self
.hostname
,
1680 "myip" : self
.get_address(proto
),
1686 class DDNSProviderXLhost(DDNSProtocolDynDNS2
, DDNSProvider
):
1687 handle
= "xlhost.de"
1689 website
= "http://xlhost.de/"
1690 protocols
= ("ipv4",)
1692 # Information about the format of the HTTP request is to be found
1693 # here: https://xlhost.de/faq/index_html?topicId=CQA2ELIPO4SQ
1695 url
= "https://nsupdate.xlhost.de/"
1698 class DDNSProviderZoneedit(DDNSProvider
):
1699 handle
= "zoneedit.com"
1701 website
= "http://www.zoneedit.com"
1702 protocols
= ("ipv4",)
1704 # Detailed information about the request and the response codes can be
1706 # http://www.zoneedit.com/doc/api/other.html
1707 # http://www.zoneedit.com/faq.html
1709 url
= "https://dynamic.zoneedit.com/auth/dynamic.html"
1711 def update_protocol(self
, proto
):
1713 "dnsto" : self
.get_address(proto
),
1714 "host" : self
.hostname
1717 # Send update to the server.
1718 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
, data
=data
)
1720 # Get the full response message.
1721 output
= response
.read()
1723 # Handle success messages.
1724 if output
.startswith("<SUCCESS"):
1727 # Handle error codes.
1728 if output
.startswith("invalid login"):
1729 raise DDNSAuthenticationError
1730 elif output
.startswith("<ERROR CODE=\"704\""):
1731 raise DDNSRequestError(_("No valid FQDN was given"))
1732 elif output
.startswith("<ERROR CODE=\"702\""):
1733 raise DDNSRequestError(_("Too frequent update requests have been sent"))
1735 # If we got here, some other update error happened.
1736 raise DDNSUpdateError
1739 class DDNSProviderDNSmadeEasy(DDNSProvider
):
1740 handle
= "dnsmadeeasy.com"
1741 name
= "DNSmadeEasy.com"
1742 website
= "http://www.dnsmadeeasy.com/"
1743 protocols
= ("ipv4",)
1745 # DNS Made Easy Nameserver Provider also offering Dynamic DNS
1746 # Documentation can be found here:
1747 # http://www.dnsmadeeasy.com/dynamic-dns/
1749 url
= "https://cp.dnsmadeeasy.com/servlet/updateip?"
1750 can_remove_records
= False
1752 def update_protocol(self
, proto
):
1754 "ip" : self
.get_address(proto
),
1755 "id" : self
.hostname
,
1756 "username" : self
.username
,
1757 "password" : self
.password
,
1760 # Send update to the server.
1761 response
= self
.send_request(self
.url
, data
=data
)
1763 # Get the full response message.
1764 output
= response
.read()
1766 # Handle success messages.
1767 if output
.startswith("success") or output
.startswith("error-record-ip-same"):
1770 # Handle error codes.
1771 if output
.startswith("error-auth-suspend"):
1772 raise DDNSRequestError(_("Account has been suspended"))
1774 elif output
.startswith("error-auth-voided"):
1775 raise DDNSRequestError(_("Account has been revoked"))
1777 elif output
.startswith("error-record-invalid"):
1778 raise DDNSRequestError(_("Specified host does not exist"))
1780 elif output
.startswith("error-auth"):
1781 raise DDNSAuthenticationError
1783 # If we got here, some other update error happened.
1784 raise DDNSUpdateError(_("Server response: %s") % output
)
1787 class DDNSProviderZZZZ(DDNSProvider
):
1790 website
= "https://zzzz.io"
1791 protocols
= ("ipv6", "ipv4",)
1793 # Detailed information about the update request can be found here:
1794 # https://zzzz.io/faq/
1796 # Details about the possible response codes have been provided in the bugtracker:
1797 # https://bugzilla.ipfire.org/show_bug.cgi?id=10584#c2
1799 url
= "https://zzzz.io/api/v1/update"
1800 can_remove_records
= False
1802 def update_protocol(self
, proto
):
1804 "ip" : self
.get_address(proto
),
1805 "token" : self
.token
,
1809 data
["type"] = "aaaa"
1811 # zzzz uses the host from the full hostname as part
1812 # of the update url.
1813 host
, domain
= self
.hostname
.split(".", 1)
1815 # Add host value to the update url.
1816 url
= "%s/%s" % (self
.url
, host
)
1818 # Send update to the server.
1820 response
= self
.send_request(url
, data
=data
)
1822 # Handle error codes.
1823 except DDNSNotFound
:
1824 raise DDNSRequestError(_("Invalid hostname specified"))
1826 # Handle success messages.
1827 if response
.code
== 200:
1830 # If we got here, some other update error happened.
1831 raise DDNSUpdateError