]>
git.ipfire.org Git - ddns.git/blob - src/ddns/providers.py
ea723e591667e926907e3536d10a751fb7521b2a
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 ###############################################################################
27 import xml
.dom
.minidom
31 # Import all possible exception types.
34 logger
= logging
.getLogger("ddns.providers")
41 Returns a dict with all automatically registered providers.
43 return _providers
.copy()
45 class DDNSProvider(object):
46 # A short string that uniquely identifies
50 # The full name of the provider.
53 # A weburl to the homepage of the provider.
54 # (Where to register a new account?)
57 # A list of supported protocols.
58 protocols
= ("ipv6", "ipv4")
62 # holdoff time - Number of days no update is performed unless
63 # the IP address has changed.
66 # holdoff time for update failures - Number of days no update
67 # is tried after the last one has failed.
68 holdoff_failure_days
= 0.5
70 # True if the provider is able to remove records, too.
71 # Required to remove AAAA records if IPv6 is absent again.
72 can_remove_records
= True
74 # Automatically register all providers.
75 class __metaclass__(type):
76 def __init__(provider
, name
, bases
, dict):
77 type.__init
__(provider
, name
, bases
, dict)
79 # The main class from which is inherited is not registered
81 if name
== "DDNSProvider":
84 if not all((provider
.handle
, provider
.name
, provider
.website
)):
85 raise DDNSError(_("Provider is not properly configured"))
87 assert not _providers
.has_key(provider
.handle
), \
88 "Provider '%s' has already been registered" % provider
.handle
90 _providers
[provider
.handle
] = provider
95 Should be overwritten to check if the system the code is running
96 on has all the required tools to support this provider.
100 def __init__(self
, core
, **settings
):
103 # Copy a set of default settings and
104 # update them by those from the configuration file.
105 self
.settings
= self
.DEFAULT_SETTINGS
.copy()
106 self
.settings
.update(settings
)
109 return "<DDNS Provider %s (%s)>" % (self
.name
, self
.handle
)
111 def __cmp__(self
, other
):
112 return cmp(self
.hostname
, other
.hostname
)
118 def get(self
, key
, default
=None):
120 Get a setting from the settings dictionary.
122 return self
.settings
.get(key
, default
)
127 Fast access to the hostname.
129 return self
.get("hostname")
134 Fast access to the username.
136 return self
.get("username")
141 Fast access to the password.
143 return self
.get("password")
148 Fast access to the token.
150 return self
.get("token")
152 def __call__(self
, force
=False):
154 logger
.debug(_("Updating %s forced") % self
.hostname
)
156 # Do nothing if the last update has failed or no update is required
157 elif self
.has_failure
or not self
.requires_update
:
160 # Execute the update.
164 # 1) Catch network errors early, because we do not want to log
165 # them to the database. They are usually temporary and caused
166 # by the client side, so that we will retry quickly.
167 # 2) If there is an internet server error (HTTP code 500) on the
168 # provider's site, we will not log a failure and try again
170 except (DDNSNetworkError
, DDNSInternalServerError
):
173 # In case of any errors, log the failed request and
174 # raise the exception.
175 except DDNSError
as e
:
176 self
.core
.db
.log_failure(self
.hostname
, e
)
179 logger
.info(_("Dynamic DNS update for %(hostname)s (%(provider)s) successful") % \
180 { "hostname" : self
.hostname
, "provider" : self
.name
})
181 self
.core
.db
.log_success(self
.hostname
)
184 for protocol
in self
.protocols
:
185 if self
.have_address(protocol
):
186 self
.update_protocol(protocol
)
187 elif self
.can_remove_records
:
188 self
.remove_protocol(protocol
)
190 def update_protocol(self
, proto
):
191 raise NotImplementedError
193 def remove_protocol(self
, proto
):
194 if not self
.can_remove_records
:
195 raise RuntimeError, "can_remove_records is enabled, but remove_protocol() not implemented"
197 raise NotImplementedError
200 def requires_update(self
):
201 # If the IP addresses have changed, an update is required
202 if self
.ip_address_changed(self
.protocols
):
203 logger
.debug(_("An update for %(hostname)s (%(provider)s)"
204 " is performed because of an IP address change") % \
205 { "hostname" : self
.hostname
, "provider" : self
.name
})
209 # If the holdoff time has expired, an update is required, too
210 if self
.holdoff_time_expired():
211 logger
.debug(_("An update for %(hostname)s (%(provider)s)"
212 " 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") \
239 logger
.warning(_("There will be no retries"))
243 # Determine when the holdoff time ends
244 last_update
= self
.db
.last_update(self
.hostname
, status
=last_status
)
245 holdoff_end
= last_update
+ datetime
.timedelta(days
=self
.holdoff_failure_days
)
247 now
= datetime
.datetime
.utcnow()
248 if now
< holdoff_end
:
249 failure_message
= self
.db
.last_update_failure_message(self
.hostname
)
251 logger
.warning(_("An update has not been performed because earlier updates failed for %s") \
255 logger
.warning(_("Last failure message:"))
257 for line
in failure_message
.splitlines():
258 logger
.warning(" %s" % line
)
260 logger
.warning(_("Further updates will be withheld until %s") % holdoff_end
)
266 def ip_address_changed(self
, protos
):
268 Returns True if this host is already up to date
269 and does not need to change the IP address on the
273 addresses
= self
.core
.system
.resolve(self
.hostname
, proto
)
274 current_address
= self
.get_address(proto
)
276 # Handle if the system has not got any IP address from a protocol
277 # (i.e. had full dual-stack connectivity which it has not any more)
278 if current_address
is None:
279 # If addresses still exists in the DNS system and if this provider
280 # is able to remove records, we will do that.
281 if addresses
and self
.can_remove_records
:
284 # Otherwise, we cannot go on...
287 if not current_address
in addresses
:
292 def holdoff_time_expired(self
):
294 Returns true if the holdoff time has expired
295 and the host requires an update
297 # If no holdoff days is defined, we cannot go on
298 if not self
.holdoff_days
:
301 # Get the timestamp of the last successfull update
302 last_update
= self
.db
.last_update(self
.hostname
, status
="success")
304 # If no timestamp has been recorded, no update has been
305 # performed. An update should be performed now.
309 # Determine when the holdoff time ends
310 holdoff_end
= last_update
+ datetime
.timedelta(days
=self
.holdoff_days
)
312 now
= datetime
.datetime
.utcnow()
314 if now
>= holdoff_end
:
315 logger
.debug("The holdoff time has expired for %s" % self
.hostname
)
318 logger
.debug("Updates for %s are held off until %s" % \
319 (self
.hostname
, holdoff_end
))
322 def send_request(self
, *args
, **kwargs
):
324 Proxy connection to the send request
327 return self
.core
.system
.send_request(*args
, **kwargs
)
329 def get_address(self
, proto
, default
=None):
331 Proxy method to get the current IP address.
333 return self
.core
.system
.get_address(proto
) or default
335 def have_address(self
, proto
):
337 Returns True if an IP address for the given protocol
340 address
= self
.get_address(proto
)
348 class DDNSProtocolDynDNS2(object):
350 This is an abstract class that implements the DynDNS updater
351 protocol version 2. As this is a popular way to update dynamic
352 DNS records, this class is supposed make the provider classes
356 # Information about the format of the request is to be found
357 # http://dyn.com/support/developers/api/perform-update/
358 # http://dyn.com/support/developers/api/return-codes/
360 # The DynDNS protocol version 2 does not allow to remove records
361 can_remove_records
= False
363 def prepare_request_data(self
, proto
):
365 "hostname" : self
.hostname
,
366 "myip" : self
.get_address(proto
),
371 def update_protocol(self
, proto
):
372 data
= self
.prepare_request_data(proto
)
374 return self
.send_request(data
)
376 def send_request(self
, data
):
377 # Send update to the server.
378 response
= DDNSProvider
.send_request(self
, self
.url
, data
=data
,
379 username
=self
.username
, password
=self
.password
)
381 # Get the full response message.
382 output
= response
.read()
384 # Handle success messages.
385 if output
.startswith("good") or output
.startswith("nochg"):
388 # Handle error codes.
389 if output
== "badauth":
390 raise DDNSAuthenticationError
391 elif output
== "abuse":
393 elif output
== "notfqdn":
394 raise DDNSRequestError(_("No valid FQDN was given"))
395 elif output
== "nohost":
396 raise DDNSRequestError(_("Specified host does not exist"))
397 elif output
== "911":
398 raise DDNSInternalServerError
399 elif output
== "dnserr":
400 raise DDNSInternalServerError(_("DNS error encountered"))
401 elif output
== "badagent":
402 raise DDNSBlockedError
404 # If we got here, some other update error happened.
405 raise DDNSUpdateError(_("Server response: %s") % output
)
408 class DDNSResponseParserXML(object):
410 This class provides a parser for XML responses which
411 will be sent by various providers. This class uses the python
412 shipped XML minidom module to walk through the XML tree and return
416 def get_xml_tag_value(self
, document
, content
):
417 # Send input to the parser.
418 xmldoc
= xml
.dom
.minidom
.parseString(document
)
420 # Get XML elements by the given content.
421 element
= xmldoc
.getElementsByTagName(content
)
423 # If no element has been found, we directly can return None.
427 # Only get the first child from an element, even there are more than one.
428 firstchild
= element
[0].firstChild
430 # Get the value of the child.
431 value
= firstchild
.nodeValue
437 class DDNSProviderAllInkl(DDNSProvider
):
438 handle
= "all-inkl.com"
439 name
= "All-inkl.com"
440 website
= "http://all-inkl.com/"
441 protocols
= ("ipv4",)
443 # There are only information provided by the vendor how to
444 # perform an update on a FRITZ Box. Grab requried informations
446 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
448 url
= "http://dyndns.kasserver.com"
449 can_remove_records
= False
452 # There is no additional data required so we directly can
454 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
)
456 # Get the full response message.
457 output
= response
.read()
459 # Handle success messages.
460 if output
.startswith("good") or output
.startswith("nochg"):
463 # If we got here, some other update error happened.
464 raise DDNSUpdateError
467 class DDNSProviderBindNsupdate(DDNSProvider
):
469 name
= "BIND nsupdate utility"
470 website
= "http://en.wikipedia.org/wiki/Nsupdate"
476 # Search if the nsupdate utility is available
477 paths
= os
.environ
.get("PATH")
479 for path
in paths
.split(":"):
480 executable
= os
.path
.join(path
, "nsupdate")
482 if os
.path
.exists(executable
):
488 scriptlet
= self
.__make
_scriptlet
()
490 # -v enables TCP hence we transfer keys and other data that may
491 # exceed the size of one packet.
492 # -t sets the timeout
493 command
= ["nsupdate", "-v", "-t", "60"]
495 p
= subprocess
.Popen(command
, shell
=True,
496 stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
498 stdout
, stderr
= p
.communicate(scriptlet
)
500 if p
.returncode
== 0:
503 raise DDNSError("nsupdate terminated with error code: %s\n %s" % (p
.returncode
, stderr
))
505 def __make_scriptlet(self
):
508 # Set a different server the update is sent to.
509 server
= self
.get("server", None)
511 scriptlet
.append("server %s" % server
)
513 # Set the DNS zone the host should be added to.
514 zone
= self
.get("zone", None)
516 scriptlet
.append("zone %s" % zone
)
518 key
= self
.get("key", None)
520 secret
= self
.get("secret")
522 scriptlet
.append("key %s %s" % (key
, secret
))
524 ttl
= self
.get("ttl", self
.DEFAULT_TTL
)
526 # Perform an update for each supported protocol.
527 for rrtype
, proto
in (("AAAA", "ipv6"), ("A", "ipv4")):
528 address
= self
.get_address(proto
)
532 scriptlet
.append("update delete %s. %s" % (self
.hostname
, rrtype
))
533 scriptlet
.append("update add %s. %s %s %s" % \
534 (self
.hostname
, ttl
, rrtype
, address
))
536 # Send the actions to the server.
537 scriptlet
.append("send")
538 scriptlet
.append("quit")
540 logger
.debug(_("Scriptlet:"))
541 for line
in scriptlet
:
542 # Masquerade the line with the secret key.
543 if line
.startswith("key"):
544 line
= "key **** ****"
546 logger
.debug(" %s" % line
)
548 return "\n".join(scriptlet
)
551 class DDNSProviderChangeIP(DDNSProvider
):
552 handle
= "changeip.com"
553 name
= "ChangeIP.com"
554 website
= "https://changeip.com"
555 protocols
= ("ipv4",)
557 # Detailed information about the update api can be found here.
558 # http://www.changeip.com/accounts/knowledgebase.php?action=displayarticle&id=34
560 url
= "https://nic.changeip.com/nic/update"
561 can_remove_records
= False
563 def update_protocol(self
, proto
):
565 "hostname" : self
.hostname
,
566 "myip" : self
.get_address(proto
),
569 # Send update to the server.
571 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
574 # Handle error codes.
575 except urllib2
.HTTPError
, e
:
577 raise DDNSRequestError(_("Domain not found."))
581 # Handle success message.
582 if response
.code
== 200:
585 # If we got here, some other update error happened.
586 raise DDNSUpdateError(_("Server response: %s") % output
)
589 class DDNSProviderDesecIO(DDNSProtocolDynDNS2
, DDNSProvider
):
592 website
= "https://www.desec.io"
593 protocols
= ("ipv6", "ipv4",)
595 # ipv4 / ipv6 records are automatically removed when the update
596 # request originates from the respectively other protocol and no
597 # address is explicitly provided for the unused protocol.
599 url
= "https://update.dedyn.io"
601 # desec.io sends the IPv6 and IPv4 address in one request
604 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, "ipv4")
606 # This one supports IPv6
607 myipv6
= self
.get_address("ipv6")
609 # Add update information if we have an IPv6 address.
611 data
["myipv6"] = myipv6
613 self
.send_request(data
)
616 class DDNSProviderDDNSS(DDNSProvider
):
619 website
= "http://www.ddnss.de"
620 protocols
= ("ipv4",)
622 # Detailed information about how to send the update request and possible response
623 # codes can be obtained from here.
624 # http://www.ddnss.de/info.php
625 # http://www.megacomputing.de/2014/08/dyndns-service-response-time/#more-919
627 url
= "http://www.ddnss.de/upd.php"
628 can_remove_records
= False
630 def update_protocol(self
, proto
):
632 "ip" : self
.get_address(proto
),
633 "host" : self
.hostname
,
636 # Check if a token has been set.
638 data
["key"] = self
.token
640 # Check if username and hostname are given.
641 elif self
.username
and self
.password
:
643 "user" : self
.username
,
644 "pwd" : self
.password
,
647 # Raise an error if no auth details are given.
649 raise DDNSConfigurationError
651 # Send update to the server.
652 response
= self
.send_request(self
.url
, data
=data
)
654 # This provider sends the response code as part of the header.
655 header
= response
.info()
657 # Get status information from the header.
658 output
= header
.getheader('ddnss-response')
660 # Handle success messages.
661 if output
== "good" or output
== "nochg":
664 # Handle error codes.
665 if output
== "badauth":
666 raise DDNSAuthenticationError
667 elif output
== "notfqdn":
668 raise DDNSRequestError(_("No valid FQDN was given"))
669 elif output
== "nohost":
670 raise DDNSRequestError(_("Specified host does not exist"))
671 elif output
== "911":
672 raise DDNSInternalServerError
673 elif output
== "dnserr":
674 raise DDNSInternalServerError(_("DNS error encountered"))
675 elif output
== "disabled":
676 raise DDNSRequestError(_("Account disabled or locked"))
678 # If we got here, some other update error happened.
679 raise DDNSUpdateError
682 class DDNSProviderDHS(DDNSProvider
):
684 name
= "DHS International"
685 website
= "http://dhs.org/"
686 protocols
= ("ipv4",)
688 # No information about the used update api provided on webpage,
689 # grabed from source code of ez-ipudate.
691 url
= "http://members.dhs.org/nic/hosts"
692 can_remove_records
= False
694 def update_protocol(self
, proto
):
696 "domain" : self
.hostname
,
697 "ip" : self
.get_address(proto
),
699 "hostcmdstage" : "2",
703 # Send update to the server.
704 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
707 # Handle success messages.
708 if response
.code
== 200:
711 # If we got here, some other update error happened.
712 raise DDNSUpdateError
715 class DDNSProviderDNSpark(DDNSProvider
):
716 handle
= "dnspark.com"
718 website
= "http://dnspark.com/"
719 protocols
= ("ipv4",)
721 # Informations to the used api can be found here:
722 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
724 url
= "https://control.dnspark.com/api/dynamic/update.php"
725 can_remove_records
= False
727 def update_protocol(self
, proto
):
729 "domain" : self
.hostname
,
730 "ip" : self
.get_address(proto
),
733 # Send update to the server.
734 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
737 # Get the full response message.
738 output
= response
.read()
740 # Handle success messages.
741 if output
.startswith("ok") or output
.startswith("nochange"):
744 # Handle error codes.
745 if output
== "unauth":
746 raise DDNSAuthenticationError
747 elif output
== "abuse":
749 elif output
== "blocked":
750 raise DDNSBlockedError
751 elif output
== "nofqdn":
752 raise DDNSRequestError(_("No valid FQDN was given"))
753 elif output
== "nohost":
754 raise DDNSRequestError(_("Invalid hostname specified"))
755 elif output
== "notdyn":
756 raise DDNSRequestError(_("Hostname not marked as a dynamic host"))
757 elif output
== "invalid":
758 raise DDNSRequestError(_("Invalid IP address has been sent"))
760 # If we got here, some other update error happened.
761 raise DDNSUpdateError
764 class DDNSProviderDtDNS(DDNSProvider
):
767 website
= "http://dtdns.com/"
768 protocols
= ("ipv4",)
770 # Information about the format of the HTTPS request is to be found
771 # http://www.dtdns.com/dtsite/updatespec
773 url
= "https://www.dtdns.com/api/autodns.cfm"
774 can_remove_records
= False
776 def update_protocol(self
, proto
):
778 "ip" : self
.get_address(proto
),
779 "id" : self
.hostname
,
783 # Send update to the server.
784 response
= self
.send_request(self
.url
, data
=data
)
786 # Get the full response message.
787 output
= response
.read()
789 # Remove all leading and trailing whitespace.
790 output
= output
.strip()
792 # Handle success messages.
793 if "now points to" in output
:
796 # Handle error codes.
797 if output
== "No hostname to update was supplied.":
798 raise DDNSRequestError(_("No hostname specified"))
800 elif output
== "The hostname you supplied is not valid.":
801 raise DDNSRequestError(_("Invalid hostname specified"))
803 elif output
== "The password you supplied is not valid.":
804 raise DDNSAuthenticationError
806 elif output
== "Administration has disabled this account.":
807 raise DDNSRequestError(_("Account has been disabled"))
809 elif output
== "Illegal character in IP.":
810 raise DDNSRequestError(_("Invalid IP address has been sent"))
812 elif output
== "Too many failed requests.":
813 raise DDNSRequestError(_("Too many failed requests"))
815 # If we got here, some other update error happened.
816 raise DDNSUpdateError
819 class DDNSProviderDuckDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
820 handle
= "duckdns.org"
822 website
= "http://www.duckdns.org/"
823 protocols
= ("ipv4",)
825 # Information about the format of the request is to be found
826 # https://www.duckdns.org/install.jsp
828 url
= "https://www.duckdns.org/nic/update"
831 class DDNSProviderDynDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
832 handle
= "dyndns.org"
834 website
= "http://dyn.com/dns/"
835 protocols
= ("ipv4",)
837 # Information about the format of the request is to be found
838 # http://http://dyn.com/support/developers/api/perform-update/
839 # http://dyn.com/support/developers/api/return-codes/
841 url
= "https://members.dyndns.org/nic/update"
844 class DDNSProviderDomainOffensive(DDNSProtocolDynDNS2
, DDNSProvider
):
846 name
= "Domain-Offensive"
847 website
= "https://www.do.de/"
848 protocols
= ("ipv6", "ipv4")
850 # Detailed information about the request and response codes
851 # are available on the providers webpage.
852 # https://www.do.de/wiki/FlexDNS_-_Entwickler
854 url
= "https://ddns.do.de/"
857 class DDNSProviderDynU(DDNSProtocolDynDNS2
, DDNSProvider
):
860 website
= "http://dynu.com/"
861 protocols
= ("ipv6", "ipv4",)
863 # Detailed information about the request and response codes
864 # are available on the providers webpage.
865 # http://dynu.com/Default.aspx?page=dnsapi
867 url
= "https://api.dynu.com/nic/update"
869 # DynU sends the IPv6 and IPv4 address in one request
872 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, "ipv4")
874 # This one supports IPv6
875 myipv6
= self
.get_address("ipv6")
877 # Add update information if we have an IPv6 address.
879 data
["myipv6"] = myipv6
881 self
.send_request(data
)
884 class DDNSProviderEasyDNS(DDNSProvider
):
885 handle
= "easydns.com"
887 website
= "http://www.easydns.com/"
888 protocols
= ("ipv4",)
890 # Detailed information about the request and response codes
891 # (API 1.3) are available on the providers webpage.
892 # https://fusion.easydns.com/index.php?/Knowledgebase/Article/View/102/7/dynamic-dns
894 url
= "http://api.cp.easydns.com/dyn/tomato.php"
896 def update_protocol(self
, proto
):
898 "myip" : self
.get_address(proto
, "-"),
899 "hostname" : self
.hostname
,
902 # Send update to the server.
903 response
= self
.send_request(self
.url
, data
=data
,
904 username
=self
.username
, password
=self
.password
)
906 # Get the full response message.
907 output
= response
.read()
909 # Remove all leading and trailing whitespace.
910 output
= output
.strip()
912 # Handle success messages.
913 if output
.startswith("NOERROR"):
916 # Handle error codes.
917 if output
.startswith("NOACCESS"):
918 raise DDNSAuthenticationError
920 elif output
.startswith("NOSERVICE"):
921 raise DDNSRequestError(_("Dynamic DNS is not turned on for this domain"))
923 elif output
.startswith("ILLEGAL INPUT"):
924 raise DDNSRequestError(_("Invalid data has been sent"))
926 elif output
.startswith("TOOSOON"):
927 raise DDNSRequestError(_("Too frequent update requests have been sent"))
929 # If we got here, some other update error happened.
930 raise DDNSUpdateError
933 class DDNSProviderDomopoli(DDNSProtocolDynDNS2
, DDNSProvider
):
934 handle
= "domopoli.de"
936 website
= "http://domopoli.de/"
937 protocols
= ("ipv4",)
939 # https://www.domopoli.de/?page=howto#DynDns_start
941 url
= "http://dyndns.domopoli.de/nic/update"
944 class DDNSProviderDynsNet(DDNSProvider
):
947 website
= "http://www.dyns.net/"
948 protocols
= ("ipv4",)
949 can_remove_records
= False
951 # There is very detailed informatio about how to send the update request and
952 # the possible response codes. (Currently we are using the v1.1 proto)
953 # http://www.dyns.net/documentation/technical/protocol/
955 url
= "http://www.dyns.net/postscript011.php"
957 def update_protocol(self
, proto
):
959 "ip" : self
.get_address(proto
),
960 "host" : self
.hostname
,
961 "username" : self
.username
,
962 "password" : self
.password
,
965 # Send update to the server.
966 response
= self
.send_request(self
.url
, data
=data
)
968 # Get the full response message.
969 output
= response
.read()
971 # Handle success messages.
972 if output
.startswith("200"):
975 # Handle error codes.
976 if output
.startswith("400"):
977 raise DDNSRequestError(_("Malformed request has been sent"))
978 elif output
.startswith("401"):
979 raise DDNSAuthenticationError
980 elif output
.startswith("402"):
981 raise DDNSRequestError(_("Too frequent update requests have been sent"))
982 elif output
.startswith("403"):
983 raise DDNSInternalServerError
985 # If we got here, some other update error happened.
986 raise DDNSUpdateError(_("Server response: %s") % output
)
989 class DDNSProviderEnomCom(DDNSResponseParserXML
, DDNSProvider
):
992 website
= "http://www.enom.com/"
993 protocols
= ("ipv4",)
995 # There are very detailed information about how to send an update request and
997 # http://www.enom.com/APICommandCatalog/
999 url
= "https://dynamic.name-services.com/interface.asp"
1000 can_remove_records
= False
1002 def update_protocol(self
, proto
):
1004 "command" : "setdnshost",
1005 "responsetype" : "xml",
1006 "address" : self
.get_address(proto
),
1007 "domainpassword" : self
.password
,
1008 "zone" : self
.hostname
1011 # Send update to the server.
1012 response
= self
.send_request(self
.url
, data
=data
)
1014 # Get the full response message.
1015 output
= response
.read()
1017 # Handle success messages.
1018 if self
.get_xml_tag_value(output
, "ErrCount") == "0":
1021 # Handle error codes.
1022 errorcode
= self
.get_xml_tag_value(output
, "ResponseNumber")
1024 if errorcode
== "304155":
1025 raise DDNSAuthenticationError
1026 elif errorcode
== "304153":
1027 raise DDNSRequestError(_("Domain not found"))
1029 # If we got here, some other update error happened.
1030 raise DDNSUpdateError
1033 class DDNSProviderEntryDNS(DDNSProvider
):
1034 handle
= "entrydns.net"
1036 website
= "http://entrydns.net/"
1037 protocols
= ("ipv4",)
1039 # Some very tiny details about their so called "Simple API" can be found
1040 # here: https://entrydns.net/help
1041 url
= "https://entrydns.net/records/modify"
1042 can_remove_records
= False
1044 def update_protocol(self
, proto
):
1046 "ip" : self
.get_address(proto
),
1049 # Add auth token to the update url.
1050 url
= "%s/%s" % (self
.url
, self
.token
)
1052 # Send update to the server.
1054 response
= self
.send_request(url
, data
=data
)
1056 # Handle error codes
1057 except urllib2
.HTTPError
, e
:
1059 raise DDNSAuthenticationError
1062 raise DDNSRequestError(_("An invalid IP address was submitted"))
1066 # Handle success messages.
1067 if response
.code
== 200:
1070 # If we got here, some other update error happened.
1071 raise DDNSUpdateError
1074 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider
):
1075 handle
= "freedns.afraid.org"
1076 name
= "freedns.afraid.org"
1077 website
= "http://freedns.afraid.org/"
1079 # No information about the request or response could be found on the vendor
1080 # page. All used values have been collected by testing.
1081 url
= "https://freedns.afraid.org/dynamic/update.php"
1082 can_remove_records
= False
1084 def update_protocol(self
, proto
):
1086 "address" : self
.get_address(proto
),
1089 # Add auth token to the update url.
1090 url
= "%s?%s" % (self
.url
, self
.token
)
1092 # Send update to the server.
1093 response
= self
.send_request(url
, data
=data
)
1095 # Get the full response message.
1096 output
= response
.read()
1098 # Handle success messages.
1099 if output
.startswith("Updated") or "has not changed" in output
:
1102 # Handle error codes.
1103 if output
== "ERROR: Unable to locate this record":
1104 raise DDNSAuthenticationError
1105 elif "is an invalid IP address" in output
:
1106 raise DDNSRequestError(_("Invalid IP address has been sent"))
1108 # If we got here, some other update error happened.
1109 raise DDNSUpdateError
1112 class DDNSProviderJoker(DDNSProtocolDynDNS2
, DDNSProvider
):
1113 handle
= "joker.com"
1114 name
= "Joker.com Dynamic DNS"
1115 website
= "https://joker.com/"
1116 protocols
= ("ipv4",)
1118 # Information about the request can be found here:
1119 # https://joker.com/faq/content/11/427/en/what-is-dynamic-dns-dyndns.html
1120 # Using DynDNS V2 protocol over HTTPS here
1122 url
= "https://svc.joker.com/nic/update"
1125 class DDNSProviderGoogle(DDNSProtocolDynDNS2
, DDNSProvider
):
1126 handle
= "domains.google.com"
1127 name
= "Google Domains"
1128 website
= "https://domains.google.com/"
1129 protocols
= ("ipv4",)
1131 # Information about the format of the HTTP request is to be found
1132 # here: https://support.google.com/domains/answer/6147083?hl=en
1134 url
= "https://domains.google.com/nic/update"
1137 class DDNSProviderLightningWireLabs(DDNSProvider
):
1138 handle
= "dns.lightningwirelabs.com"
1139 name
= "Lightning Wire Labs DNS Service"
1140 website
= "http://dns.lightningwirelabs.com/"
1142 # Information about the format of the HTTPS request is to be found
1143 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
1145 url
= "https://dns.lightningwirelabs.com/update"
1149 "hostname" : self
.hostname
,
1150 "address6" : self
.get_address("ipv6", "-"),
1151 "address4" : self
.get_address("ipv4", "-"),
1154 # Check if a token has been set.
1156 data
["token"] = self
.token
1158 # Check for username and password.
1159 elif self
.username
and self
.password
:
1161 "username" : self
.username
,
1162 "password" : self
.password
,
1165 # Raise an error if no auth details are given.
1167 raise DDNSConfigurationError
1169 # Send update to the server.
1170 response
= self
.send_request(self
.url
, data
=data
)
1172 # Handle success messages.
1173 if response
.code
== 200:
1176 # If we got here, some other update error happened.
1177 raise DDNSUpdateError
1180 class DDNSProviderLoopia(DDNSProtocolDynDNS2
, DDNSProvider
):
1181 handle
= "loopia.se"
1183 website
= "https://www.loopia.com"
1184 protocols
= ("ipv4",)
1186 # Information about the format of the HTTP request is to be found
1187 # here: https://support.loopia.com/wiki/About_the_DynDNS_support
1189 url
= "https://dns.loopia.se/XDynDNSServer/XDynDNS.php"
1192 class DDNSProviderMyOnlinePortal(DDNSProtocolDynDNS2
, DDNSProvider
):
1193 handle
= "myonlineportal.net"
1194 name
= "myonlineportal.net"
1195 website
= "https:/myonlineportal.net/"
1197 # Information about the request and response can be obtained here:
1198 # https://myonlineportal.net/howto_dyndns
1200 url
= "https://myonlineportal.net/updateddns"
1202 def prepare_request_data(self
, proto
):
1204 "hostname" : self
.hostname
,
1205 "ip" : self
.get_address(proto
),
1211 class DDNSProviderNamecheap(DDNSResponseParserXML
, DDNSProvider
):
1212 handle
= "namecheap.com"
1214 website
= "http://namecheap.com"
1215 protocols
= ("ipv4",)
1217 # Information about the format of the HTTP request is to be found
1218 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
1219 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
1221 url
= "https://dynamicdns.park-your-domain.com/update"
1222 can_remove_records
= False
1224 def update_protocol(self
, proto
):
1225 # Namecheap requires the hostname splitted into a host and domain part.
1226 host
, domain
= self
.hostname
.split(".", 1)
1228 # Get and store curent IP address.
1229 address
= self
.get_address(proto
)
1233 "password" : self
.password
,
1238 # Send update to the server.
1239 response
= self
.send_request(self
.url
, data
=data
)
1241 # Get the full response message.
1242 output
= response
.read()
1244 # Handle success messages.
1245 if self
.get_xml_tag_value(output
, "IP") == address
:
1248 # Handle error codes.
1249 errorcode
= self
.get_xml_tag_value(output
, "ResponseNumber")
1251 if errorcode
== "304156":
1252 raise DDNSAuthenticationError
1253 elif errorcode
== "316153":
1254 raise DDNSRequestError(_("Domain not found"))
1255 elif errorcode
== "316154":
1256 raise DDNSRequestError(_("Domain not active"))
1257 elif errorcode
in ("380098", "380099"):
1258 raise DDNSInternalServerError
1260 # If we got here, some other update error happened.
1261 raise DDNSUpdateError
1264 class DDNSProviderNOIP(DDNSProtocolDynDNS2
, DDNSProvider
):
1265 handle
= "no-ip.com"
1267 website
= "http://www.no-ip.com/"
1268 protocols
= ("ipv4",)
1270 # Information about the format of the HTTP request is to be found
1271 # here: http://www.no-ip.com/integrate/request and
1272 # here: http://www.no-ip.com/integrate/response
1274 url
= "http://dynupdate.no-ip.com/nic/update"
1276 def prepare_request_data(self
, proto
):
1277 assert proto
== "ipv4"
1280 "hostname" : self
.hostname
,
1281 "address" : self
.get_address(proto
),
1287 class DDNSProviderNowDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1288 handle
= "now-dns.com"
1290 website
= "http://now-dns.com/"
1291 protocols
= ("ipv6", "ipv4")
1293 # Information about the format of the request is to be found
1294 # but only can be accessed by register an account and login
1295 # https://now-dns.com/?m=api
1297 url
= "https://now-dns.com/update"
1300 class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2
, DDNSProvider
):
1301 handle
= "nsupdate.info"
1302 name
= "nsupdate.info"
1303 website
= "http://nsupdate.info/"
1304 protocols
= ("ipv6", "ipv4",)
1306 # Information about the format of the HTTP request can be found
1307 # after login on the provider user interface and here:
1308 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
1310 url
= "https://nsupdate.info/nic/update"
1312 # TODO nsupdate.info can actually do this, but the functionality
1313 # has not been implemented here, yet.
1314 can_remove_records
= False
1316 # After a failed update, there will be no retries
1317 # https://bugzilla.ipfire.org/show_bug.cgi?id=10603
1318 holdoff_failure_days
= None
1320 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
1321 # and for the password a so called secret.
1324 return self
.get("hostname")
1328 return self
.token
or self
.get("secret")
1330 def prepare_request_data(self
, proto
):
1332 "myip" : self
.get_address(proto
),
1338 class DDNSProviderOpenDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1339 handle
= "opendns.com"
1341 website
= "http://www.opendns.com"
1343 # Detailed information about the update request and possible
1344 # response codes can be obtained from here:
1345 # https://support.opendns.com/entries/23891440
1347 url
= "https://updates.opendns.com/nic/update"
1349 def prepare_request_data(self
, proto
):
1351 "hostname" : self
.hostname
,
1352 "myip" : self
.get_address(proto
),
1358 class DDNSProviderOVH(DDNSProtocolDynDNS2
, DDNSProvider
):
1361 website
= "http://www.ovh.com/"
1362 protocols
= ("ipv4",)
1364 # OVH only provides very limited information about how to
1365 # update a DynDNS host. They only provide the update url
1366 # on the their german subpage.
1368 # http://hilfe.ovh.de/DomainDynHost
1370 url
= "https://www.ovh.com/nic/update"
1372 def prepare_request_data(self
, proto
):
1373 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1375 "system" : "dyndns",
1381 class DDNSProviderRegfish(DDNSProvider
):
1382 handle
= "regfish.com"
1383 name
= "Regfish GmbH"
1384 website
= "http://www.regfish.com/"
1386 # A full documentation to the providers api can be found here
1387 # but is only available in german.
1388 # https://www.regfish.de/domains/dyndns/dokumentation
1390 url
= "https://dyndns.regfish.de/"
1391 can_remove_records
= False
1395 "fqdn" : self
.hostname
,
1398 # Check if we update an IPv6 address.
1399 address6
= self
.get_address("ipv6")
1401 data
["ipv6"] = address6
1403 # Check if we update an IPv4 address.
1404 address4
= self
.get_address("ipv4")
1406 data
["ipv4"] = address4
1408 # Raise an error if none address is given.
1409 if not data
.has_key("ipv6") and not data
.has_key("ipv4"):
1410 raise DDNSConfigurationError
1412 # Check if a token has been set.
1414 data
["token"] = self
.token
1416 # Raise an error if no token and no useranem and password
1418 elif not self
.username
and not self
.password
:
1419 raise DDNSConfigurationError(_("No Auth details specified"))
1421 # HTTP Basic Auth is only allowed if no token is used.
1423 # Send update to the server.
1424 response
= self
.send_request(self
.url
, data
=data
)
1426 # Send update to the server.
1427 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
1430 # Get the full response message.
1431 output
= response
.read()
1433 # Handle success messages.
1434 if "100" in output
or "101" in output
:
1437 # Handle error codes.
1438 if "401" or "402" in output
:
1439 raise DDNSAuthenticationError
1440 elif "408" in output
:
1441 raise DDNSRequestError(_("Invalid IPv4 address has been sent"))
1442 elif "409" in output
:
1443 raise DDNSRequestError(_("Invalid IPv6 address has been sent"))
1444 elif "412" in output
:
1445 raise DDNSRequestError(_("No valid FQDN was given"))
1446 elif "414" in output
:
1447 raise DDNSInternalServerError
1449 # If we got here, some other update error happened.
1450 raise DDNSUpdateError
1453 class DDNSProviderSchokokeksDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1454 handle
= "schokokeks.org"
1456 website
= "http://www.schokokeks.org/"
1457 protocols
= ("ipv4",)
1459 # Information about the format of the request is to be found
1460 # https://wiki.schokokeks.org/DynDNS
1461 url
= "https://dyndns.schokokeks.org/nic/update"
1464 class DDNSProviderSelfhost(DDNSProtocolDynDNS2
, DDNSProvider
):
1465 handle
= "selfhost.de"
1466 name
= "Selfhost.de"
1467 website
= "http://www.selfhost.de/"
1468 protocols
= ("ipv4",)
1470 url
= "https://carol.selfhost.de/nic/update"
1472 def prepare_request_data(self
, proto
):
1473 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1481 class DDNSProviderServercow(DDNSProvider
):
1482 handle
= "servercow.de"
1483 name
= "servercow.de"
1484 website
= "https://servercow.de/"
1485 protocols
= ("ipv4", "ipv6")
1487 url
= "https://www.servercow.de/dnsupdate/update.php"
1488 can_remove_records
= False
1490 def update_protocol(self
, proto
):
1492 "ipaddr" : self
.get_address(proto
),
1493 "hostname" : self
.hostname
,
1494 "username" : self
.username
,
1495 "pass" : self
.password
,
1498 # Send request to provider
1499 response
= self
.send_request(self
.url
, data
=data
)
1502 output
= response
.read()
1504 # Server responds with OK if update was successful
1505 if output
.startswith("OK"):
1509 elif output
.startswith("FAILED - Authentication failed"):
1510 raise DDNSAuthenticationError
1512 # If we got here, some other update error happened
1513 raise DDNSUpdateError(output
)
1516 class DDNSProviderSPDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1517 handle
= "spdns.org"
1519 website
= "https://www.spdyn.de/"
1521 # Detailed information about request and response codes are provided
1522 # by the vendor. They are using almost the same mechanism and status
1523 # codes as dyndns.org so we can inherit all those stuff.
1525 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
1526 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
1528 url
= "https://update.spdyn.de/nic/update"
1532 return self
.get("username") or self
.hostname
1536 return self
.get("password") or self
.token
1539 class DDNSProviderStrato(DDNSProtocolDynDNS2
, DDNSProvider
):
1540 handle
= "strato.com"
1542 website
= "http:/www.strato.com/"
1543 protocols
= ("ipv4",)
1545 # Information about the request and response can be obtained here:
1546 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
1548 url
= "https://dyndns.strato.com/nic/update"
1550 def prepare_request_data(self
, proto
):
1551 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1554 "backupmx" : "NOCHG"
1560 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1561 handle
= "twodns.de"
1563 website
= "http://www.twodns.de"
1564 protocols
= ("ipv4",)
1566 # Detailed information about the request can be found here
1567 # http://twodns.de/en/faqs
1568 # http://twodns.de/en/api
1570 url
= "https://update.twodns.de/update"
1572 def prepare_request_data(self
, proto
):
1573 assert proto
== "ipv4"
1576 "ip" : self
.get_address(proto
),
1577 "hostname" : self
.hostname
1583 class DDNSProviderUdmedia(DDNSProtocolDynDNS2
, DDNSProvider
):
1584 handle
= "udmedia.de"
1585 name
= "Udmedia GmbH"
1586 website
= "http://www.udmedia.de"
1587 protocols
= ("ipv4",)
1589 # Information about the request can be found here
1590 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1592 url
= "https://www.udmedia.de/nic/update"
1595 class DDNSProviderVariomedia(DDNSProtocolDynDNS2
, DDNSProvider
):
1596 handle
= "variomedia.de"
1598 website
= "http://www.variomedia.de/"
1599 protocols
= ("ipv6", "ipv4",)
1601 # Detailed information about the request can be found here
1602 # https://dyndns.variomedia.de/
1604 url
= "https://dyndns.variomedia.de/nic/update"
1606 def prepare_request_data(self
, proto
):
1608 "hostname" : self
.hostname
,
1609 "myip" : self
.get_address(proto
),
1615 class DDNSProviderXLhost(DDNSProtocolDynDNS2
, DDNSProvider
):
1616 handle
= "xlhost.de"
1618 website
= "http://xlhost.de/"
1619 protocols
= ("ipv4",)
1621 # Information about the format of the HTTP request is to be found
1622 # here: https://xlhost.de/faq/index_html?topicId=CQA2ELIPO4SQ
1624 url
= "https://nsupdate.xlhost.de/"
1627 class DDNSProviderZoneedit(DDNSProvider
):
1628 handle
= "zoneedit.com"
1630 website
= "http://www.zoneedit.com"
1631 protocols
= ("ipv4",)
1633 # Detailed information about the request and the response codes can be
1635 # http://www.zoneedit.com/doc/api/other.html
1636 # http://www.zoneedit.com/faq.html
1638 url
= "https://dynamic.zoneedit.com/auth/dynamic.html"
1640 def update_protocol(self
, proto
):
1642 "dnsto" : self
.get_address(proto
),
1643 "host" : self
.hostname
1646 # Send update to the server.
1647 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
1650 # Get the full response message.
1651 output
= response
.read()
1653 # Handle success messages.
1654 if output
.startswith("<SUCCESS"):
1657 # Handle error codes.
1658 if output
.startswith("invalid login"):
1659 raise DDNSAuthenticationError
1660 elif output
.startswith("<ERROR CODE=\"704\""):
1661 raise DDNSRequestError(_("No valid FQDN was given"))
1662 elif output
.startswith("<ERROR CODE=\"702\""):
1663 raise DDNSRequestError(_("Too frequent update requests have been sent"))
1665 # If we got here, some other update error happened.
1666 raise DDNSUpdateError
1669 class DDNSProviderDNSmadeEasy(DDNSProvider
):
1670 handle
= "dnsmadeeasy.com"
1671 name
= "DNSmadeEasy.com"
1672 website
= "http://www.dnsmadeeasy.com/"
1673 protocols
= ("ipv4",)
1675 # DNS Made Easy Nameserver Provider also offering Dynamic DNS
1676 # Documentation can be found here:
1677 # http://www.dnsmadeeasy.com/dynamic-dns/
1679 url
= "https://cp.dnsmadeeasy.com/servlet/updateip?"
1680 can_remove_records
= False
1682 def update_protocol(self
, proto
):
1684 "ip" : self
.get_address(proto
),
1685 "id" : self
.hostname
,
1686 "username" : self
.username
,
1687 "password" : self
.password
,
1690 # Send update to the server.
1691 response
= self
.send_request(self
.url
, data
=data
)
1693 # Get the full response message.
1694 output
= response
.read()
1696 # Handle success messages.
1697 if output
.startswith("success") or output
.startswith("error-record-ip-same"):
1700 # Handle error codes.
1701 if output
.startswith("error-auth-suspend"):
1702 raise DDNSRequestError(_("Account has been suspended"))
1704 elif output
.startswith("error-auth-voided"):
1705 raise DDNSRequestError(_("Account has been revoked"))
1707 elif output
.startswith("error-record-invalid"):
1708 raise DDNSRequestError(_("Specified host does not exist"))
1710 elif output
.startswith("error-auth"):
1711 raise DDNSAuthenticationError
1713 # If we got here, some other update error happened.
1714 raise DDNSUpdateError(_("Server response: %s") % output
)
1717 class DDNSProviderZZZZ(DDNSProvider
):
1720 website
= "https://zzzz.io"
1721 protocols
= ("ipv6", "ipv4",)
1723 # Detailed information about the update request can be found here:
1724 # https://zzzz.io/faq/
1726 # Details about the possible response codes have been provided in the bugtracker:
1727 # https://bugzilla.ipfire.org/show_bug.cgi?id=10584#c2
1729 url
= "https://zzzz.io/api/v1/update"
1730 can_remove_records
= False
1732 def update_protocol(self
, proto
):
1734 "ip" : self
.get_address(proto
),
1735 "token" : self
.token
,
1739 data
["type"] = "aaaa"
1741 # zzzz uses the host from the full hostname as part
1742 # of the update url.
1743 host
, domain
= self
.hostname
.split(".", 1)
1745 # Add host value to the update url.
1746 url
= "%s/%s" % (self
.url
, host
)
1748 # Send update to the server.
1750 response
= self
.send_request(url
, data
=data
)
1752 # Handle error codes.
1753 except DDNSNotFound
:
1754 raise DDNSRequestError(_("Invalid hostname specified"))
1756 # Handle success messages.
1757 if response
.code
== 200:
1760 # If we got here, some other update error happened.
1761 raise DDNSUpdateError