]>
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 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 DDNSProviderDDNSS(DDNSProvider
):
592 website
= "http://www.ddnss.de"
593 protocols
= ("ipv4",)
595 # Detailed information about how to send the update request and possible response
596 # codes can be obtained from here.
597 # http://www.ddnss.de/info.php
598 # http://www.megacomputing.de/2014/08/dyndns-service-response-time/#more-919
600 url
= "http://www.ddnss.de/upd.php"
601 can_remove_records
= False
603 def update_protocol(self
, proto
):
605 "ip" : self
.get_address(proto
),
606 "host" : self
.hostname
,
609 # Check if a token has been set.
611 data
["key"] = self
.token
613 # Check if username and hostname are given.
614 elif self
.username
and self
.password
:
616 "user" : self
.username
,
617 "pwd" : self
.password
,
620 # Raise an error if no auth details are given.
622 raise DDNSConfigurationError
624 # Send update to the server.
625 response
= self
.send_request(self
.url
, data
=data
)
627 # This provider sends the response code as part of the header.
628 header
= response
.info()
630 # Get status information from the header.
631 output
= header
.getheader('ddnss-response')
633 # Handle success messages.
634 if output
== "good" or output
== "nochg":
637 # Handle error codes.
638 if output
== "badauth":
639 raise DDNSAuthenticationError
640 elif output
== "notfqdn":
641 raise DDNSRequestError(_("No valid FQDN was given."))
642 elif output
== "nohost":
643 raise DDNSRequestError(_("Specified host does not exist."))
644 elif output
== "911":
645 raise DDNSInternalServerError
646 elif output
== "dnserr":
647 raise DDNSInternalServerError(_("DNS error encountered."))
648 elif output
== "disabled":
649 raise DDNSRequestError(_("Account disabled or locked."))
651 # If we got here, some other update error happened.
652 raise DDNSUpdateError
655 class DDNSProviderDHS(DDNSProvider
):
657 name
= "DHS International"
658 website
= "http://dhs.org/"
659 protocols
= ("ipv4",)
661 # No information about the used update api provided on webpage,
662 # grabed from source code of ez-ipudate.
664 url
= "http://members.dhs.org/nic/hosts"
665 can_remove_records
= False
667 def update_protocol(self
, proto
):
669 "domain" : self
.hostname
,
670 "ip" : self
.get_address(proto
),
672 "hostcmdstage" : "2",
676 # Send update to the server.
677 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
680 # Handle success messages.
681 if response
.code
== 200:
684 # If we got here, some other update error happened.
685 raise DDNSUpdateError
688 class DDNSProviderDNSpark(DDNSProvider
):
689 handle
= "dnspark.com"
691 website
= "http://dnspark.com/"
692 protocols
= ("ipv4",)
694 # Informations to the used api can be found here:
695 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
697 url
= "https://control.dnspark.com/api/dynamic/update.php"
698 can_remove_records
= False
700 def update_protocol(self
, proto
):
702 "domain" : self
.hostname
,
703 "ip" : self
.get_address(proto
),
706 # Send update to the server.
707 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
710 # Get the full response message.
711 output
= response
.read()
713 # Handle success messages.
714 if output
.startswith("ok") or output
.startswith("nochange"):
717 # Handle error codes.
718 if output
== "unauth":
719 raise DDNSAuthenticationError
720 elif output
== "abuse":
722 elif output
== "blocked":
723 raise DDNSBlockedError
724 elif output
== "nofqdn":
725 raise DDNSRequestError(_("No valid FQDN was given."))
726 elif output
== "nohost":
727 raise DDNSRequestError(_("Invalid hostname specified."))
728 elif output
== "notdyn":
729 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
730 elif output
== "invalid":
731 raise DDNSRequestError(_("Invalid IP address has been sent."))
733 # If we got here, some other update error happened.
734 raise DDNSUpdateError
737 class DDNSProviderDtDNS(DDNSProvider
):
740 website
= "http://dtdns.com/"
741 protocols
= ("ipv4",)
743 # Information about the format of the HTTPS request is to be found
744 # http://www.dtdns.com/dtsite/updatespec
746 url
= "https://www.dtdns.com/api/autodns.cfm"
747 can_remove_records
= False
749 def update_protocol(self
, proto
):
751 "ip" : self
.get_address(proto
),
752 "id" : self
.hostname
,
756 # Send update to the server.
757 response
= self
.send_request(self
.url
, data
=data
)
759 # Get the full response message.
760 output
= response
.read()
762 # Remove all leading and trailing whitespace.
763 output
= output
.strip()
765 # Handle success messages.
766 if "now points to" in output
:
769 # Handle error codes.
770 if output
== "No hostname to update was supplied.":
771 raise DDNSRequestError(_("No hostname specified."))
773 elif output
== "The hostname you supplied is not valid.":
774 raise DDNSRequestError(_("Invalid hostname specified."))
776 elif output
== "The password you supplied is not valid.":
777 raise DDNSAuthenticationError
779 elif output
== "Administration has disabled this account.":
780 raise DDNSRequestError(_("Account has been disabled."))
782 elif output
== "Illegal character in IP.":
783 raise DDNSRequestError(_("Invalid IP address has been sent."))
785 elif output
== "Too many failed requests.":
786 raise DDNSRequestError(_("Too many failed requests."))
788 # If we got here, some other update error happened.
789 raise DDNSUpdateError
792 class DDNSProviderDynDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
793 handle
= "dyndns.org"
795 website
= "http://dyn.com/dns/"
796 protocols
= ("ipv4",)
798 # Information about the format of the request is to be found
799 # http://http://dyn.com/support/developers/api/perform-update/
800 # http://dyn.com/support/developers/api/return-codes/
802 url
= "https://members.dyndns.org/nic/update"
805 class DDNSProviderDynU(DDNSProtocolDynDNS2
, DDNSProvider
):
808 website
= "http://dynu.com/"
809 protocols
= ("ipv6", "ipv4",)
811 # Detailed information about the request and response codes
812 # are available on the providers webpage.
813 # http://dynu.com/Default.aspx?page=dnsapi
815 url
= "https://api.dynu.com/nic/update"
817 # DynU sends the IPv6 and IPv4 address in one request
820 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, "ipv4")
822 # This one supports IPv6
823 myipv6
= self
.get_address("ipv6")
825 # Add update information if we have an IPv6 address.
827 data
["myipv6"] = myipv6
829 self
.send_request(data
)
832 class DDNSProviderEasyDNS(DDNSProvider
):
833 handle
= "easydns.com"
835 website
= "http://www.easydns.com/"
836 protocols
= ("ipv4",)
838 # Detailed information about the request and response codes
839 # (API 1.3) are available on the providers webpage.
840 # https://fusion.easydns.com/index.php?/Knowledgebase/Article/View/102/7/dynamic-dns
842 url
= "http://api.cp.easydns.com/dyn/tomato.php"
844 def update_protocol(self
, proto
):
846 "myip" : self
.get_address(proto
, "-"),
847 "hostname" : self
.hostname
,
850 # Send update to the server.
851 response
= self
.send_request(self
.url
, data
=data
,
852 username
=self
.username
, password
=self
.password
)
854 # Get the full response message.
855 output
= response
.read()
857 # Remove all leading and trailing whitespace.
858 output
= output
.strip()
860 # Handle success messages.
861 if output
.startswith("NOERROR"):
864 # Handle error codes.
865 if output
.startswith("NOACCESS"):
866 raise DDNSAuthenticationError
868 elif output
.startswith("NOSERVICE"):
869 raise DDNSRequestError(_("Dynamic DNS is not turned on for this domain."))
871 elif output
.startswith("ILLEGAL INPUT"):
872 raise DDNSRequestError(_("Invalid data has been sent."))
874 elif output
.startswith("TOOSOON"):
875 raise DDNSRequestError(_("Too frequent update requests have been sent."))
877 # If we got here, some other update error happened.
878 raise DDNSUpdateError
881 class DDNSProviderDomopoli(DDNSProtocolDynDNS2
, DDNSProvider
):
882 handle
= "domopoli.de"
884 website
= "http://domopoli.de/"
885 protocols
= ("ipv4",)
887 # https://www.domopoli.de/?page=howto#DynDns_start
889 url
= "http://dyndns.domopoli.de/nic/update"
892 class DDNSProviderDynsNet(DDNSProvider
):
895 website
= "http://www.dyns.net/"
896 protocols
= ("ipv4",)
897 can_remove_records
= False
899 # There is very detailed informatio about how to send the update request and
900 # the possible response codes. (Currently we are using the v1.1 proto)
901 # http://www.dyns.net/documentation/technical/protocol/
903 url
= "http://www.dyns.net/postscript011.php"
905 def update_protocol(self
, proto
):
907 "ip" : self
.get_address(proto
),
908 "host" : self
.hostname
,
909 "username" : self
.username
,
910 "password" : self
.password
,
913 # Send update to the server.
914 response
= self
.send_request(self
.url
, data
=data
)
916 # Get the full response message.
917 output
= response
.read()
919 # Handle success messages.
920 if output
.startswith("200"):
923 # Handle error codes.
924 if output
.startswith("400"):
925 raise DDNSRequestError(_("Malformed request has been sent."))
926 elif output
.startswith("401"):
927 raise DDNSAuthenticationError
928 elif output
.startswith("402"):
929 raise DDNSRequestError(_("Too frequent update requests have been sent."))
930 elif output
.startswith("403"):
931 raise DDNSInternalServerError
933 # If we got here, some other update error happened.
934 raise DDNSUpdateError(_("Server response: %s") % output
)
937 class DDNSProviderEnomCom(DDNSResponseParserXML
, DDNSProvider
):
940 website
= "http://www.enom.com/"
941 protocols
= ("ipv4",)
943 # There are very detailed information about how to send an update request and
945 # http://www.enom.com/APICommandCatalog/
947 url
= "https://dynamic.name-services.com/interface.asp"
948 can_remove_records
= False
950 def update_protocol(self
, proto
):
952 "command" : "setdnshost",
953 "responsetype" : "xml",
954 "address" : self
.get_address(proto
),
955 "domainpassword" : self
.password
,
956 "zone" : self
.hostname
959 # Send update to the server.
960 response
= self
.send_request(self
.url
, data
=data
)
962 # Get the full response message.
963 output
= response
.read()
965 # Handle success messages.
966 if self
.get_xml_tag_value(output
, "ErrCount") == "0":
969 # Handle error codes.
970 errorcode
= self
.get_xml_tag_value(output
, "ResponseNumber")
972 if errorcode
== "304155":
973 raise DDNSAuthenticationError
974 elif errorcode
== "304153":
975 raise DDNSRequestError(_("Domain not found."))
977 # If we got here, some other update error happened.
978 raise DDNSUpdateError
981 class DDNSProviderEntryDNS(DDNSProvider
):
982 handle
= "entrydns.net"
984 website
= "http://entrydns.net/"
985 protocols
= ("ipv4",)
987 # Some very tiny details about their so called "Simple API" can be found
988 # here: https://entrydns.net/help
989 url
= "https://entrydns.net/records/modify"
990 can_remove_records
= False
992 def update_protocol(self
, proto
):
994 "ip" : self
.get_address(proto
),
997 # Add auth token to the update url.
998 url
= "%s/%s" % (self
.url
, self
.token
)
1000 # Send update to the server.
1002 response
= self
.send_request(url
, data
=data
)
1004 # Handle error codes
1005 except urllib2
.HTTPError
, e
:
1007 raise DDNSAuthenticationError
1010 raise DDNSRequestError(_("An invalid IP address was submitted"))
1014 # Handle success messages.
1015 if response
.code
== 200:
1018 # If we got here, some other update error happened.
1019 raise DDNSUpdateError
1022 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider
):
1023 handle
= "freedns.afraid.org"
1024 name
= "freedns.afraid.org"
1025 website
= "http://freedns.afraid.org/"
1027 # No information about the request or response could be found on the vendor
1028 # page. All used values have been collected by testing.
1029 url
= "https://freedns.afraid.org/dynamic/update.php"
1030 can_remove_records
= False
1032 def update_protocol(self
, proto
):
1034 "address" : self
.get_address(proto
),
1037 # Add auth token to the update url.
1038 url
= "%s?%s" % (self
.url
, self
.token
)
1040 # Send update to the server.
1041 response
= self
.send_request(url
, data
=data
)
1043 # Get the full response message.
1044 output
= response
.read()
1046 # Handle success messages.
1047 if output
.startswith("Updated") or "has not changed" in output
:
1050 # Handle error codes.
1051 if output
== "ERROR: Unable to locate this record":
1052 raise DDNSAuthenticationError
1053 elif "is an invalid IP address" in output
:
1054 raise DDNSRequestError(_("Invalid IP address has been sent."))
1056 # If we got here, some other update error happened.
1057 raise DDNSUpdateError
1060 class DDNSProviderJoker(DDNSProtocolDynDNS2
, DDNSProvider
):
1061 handle
= "joker.com"
1062 name
= "Joker.com Dynamic DNS"
1063 website
= "https://joker.com/"
1064 protocols
= ("ipv4",)
1066 # Information about the request can be found here:
1067 # https://joker.com/faq/content/11/427/en/what-is-dynamic-dns-dyndns.html
1068 # Using DynDNS V2 protocol over HTTPS here
1070 url
= "https://svc.joker.com/nic/update"
1073 class DDNSProviderGoogle(DDNSProtocolDynDNS2
, DDNSProvider
):
1074 handle
= "domains.google.com"
1075 name
= "Google Domains"
1076 website
= "https://domains.google.com/"
1077 protocols
= ("ipv4",)
1079 # Information about the format of the HTTP request is to be found
1080 # here: https://support.google.com/domains/answer/6147083?hl=en
1082 url
= "https://domains.google.com/nic/update"
1085 class DDNSProviderLightningWireLabs(DDNSProvider
):
1086 handle
= "dns.lightningwirelabs.com"
1087 name
= "Lightning Wire Labs DNS Service"
1088 website
= "http://dns.lightningwirelabs.com/"
1090 # Information about the format of the HTTPS request is to be found
1091 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
1093 url
= "https://dns.lightningwirelabs.com/update"
1097 "hostname" : self
.hostname
,
1098 "address6" : self
.get_address("ipv6", "-"),
1099 "address4" : self
.get_address("ipv4", "-"),
1102 # Check if a token has been set.
1104 data
["token"] = self
.token
1106 # Check for username and password.
1107 elif self
.username
and self
.password
:
1109 "username" : self
.username
,
1110 "password" : self
.password
,
1113 # Raise an error if no auth details are given.
1115 raise DDNSConfigurationError
1117 # Send update to the server.
1118 response
= self
.send_request(self
.url
, data
=data
)
1120 # Handle success messages.
1121 if response
.code
== 200:
1124 # If we got here, some other update error happened.
1125 raise DDNSUpdateError
1128 class DDNSProviderLoopia(DDNSProtocolDynDNS2
, DDNSProvider
):
1129 handle
= "loopia.se"
1131 website
= "https://www.loopia.com"
1132 protocols
= ("ipv4",)
1134 # Information about the format of the HTTP request is to be found
1135 # here: https://support.loopia.com/wiki/About_the_DynDNS_support
1137 url
= "https://dns.loopia.se/XDynDNSServer/XDynDNS.php"
1140 class DDNSProviderMyOnlinePortal(DDNSProtocolDynDNS2
, DDNSProvider
):
1141 handle
= "myonlineportal.net"
1142 name
= "myonlineportal.net"
1143 website
= "https:/myonlineportal.net/"
1145 # Information about the request and response can be obtained here:
1146 # https://myonlineportal.net/howto_dyndns
1148 url
= "https://myonlineportal.net/updateddns"
1150 def prepare_request_data(self
, proto
):
1152 "hostname" : self
.hostname
,
1153 "ip" : self
.get_address(proto
),
1159 class DDNSProviderNamecheap(DDNSResponseParserXML
, DDNSProvider
):
1160 handle
= "namecheap.com"
1162 website
= "http://namecheap.com"
1163 protocols
= ("ipv4",)
1165 # Information about the format of the HTTP request is to be found
1166 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
1167 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
1169 url
= "https://dynamicdns.park-your-domain.com/update"
1170 can_remove_records
= False
1172 def update_protocol(self
, proto
):
1173 # Namecheap requires the hostname splitted into a host and domain part.
1174 host
, domain
= self
.hostname
.split(".", 1)
1177 "ip" : self
.get_address(proto
),
1178 "password" : self
.password
,
1183 # Send update to the server.
1184 response
= self
.send_request(self
.url
, data
=data
)
1186 # Get the full response message.
1187 output
= response
.read()
1189 # Handle success messages.
1190 if self
.get_xml_tag_value(output
, "IP") == address
:
1193 # Handle error codes.
1194 errorcode
= self
.get_xml_tag_value(output
, "ResponseNumber")
1196 if errorcode
== "304156":
1197 raise DDNSAuthenticationError
1198 elif errorcode
== "316153":
1199 raise DDNSRequestError(_("Domain not found."))
1200 elif errorcode
== "316154":
1201 raise DDNSRequestError(_("Domain not active."))
1202 elif errorcode
in ("380098", "380099"):
1203 raise DDNSInternalServerError
1205 # If we got here, some other update error happened.
1206 raise DDNSUpdateError
1209 class DDNSProviderNOIP(DDNSProtocolDynDNS2
, DDNSProvider
):
1210 handle
= "no-ip.com"
1212 website
= "http://www.no-ip.com/"
1213 protocols
= ("ipv4",)
1215 # Information about the format of the HTTP request is to be found
1216 # here: http://www.no-ip.com/integrate/request and
1217 # here: http://www.no-ip.com/integrate/response
1219 url
= "http://dynupdate.no-ip.com/nic/update"
1221 def prepare_request_data(self
, proto
):
1222 assert proto
== "ipv4"
1225 "hostname" : self
.hostname
,
1226 "address" : self
.get_address(proto
),
1232 class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2
, DDNSProvider
):
1233 handle
= "nsupdate.info"
1234 name
= "nsupdate.info"
1235 website
= "http://nsupdate.info/"
1236 protocols
= ("ipv6", "ipv4",)
1238 # Information about the format of the HTTP request can be found
1239 # after login on the provider user interface and here:
1240 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
1242 url
= "https://nsupdate.info/nic/update"
1244 # TODO nsupdate.info can actually do this, but the functionality
1245 # has not been implemented here, yet.
1246 can_remove_records
= False
1248 # After a failed update, there will be no retries
1249 # https://bugzilla.ipfire.org/show_bug.cgi?id=10603
1250 holdoff_failure_days
= None
1252 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
1253 # and for the password a so called secret.
1256 return self
.get("hostname")
1260 return self
.token
or self
.get("secret")
1262 def prepare_request_data(self
, proto
):
1264 "myip" : self
.get_address(proto
),
1270 class DDNSProviderOpenDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1271 handle
= "opendns.com"
1273 website
= "http://www.opendns.com"
1275 # Detailed information about the update request and possible
1276 # response codes can be obtained from here:
1277 # https://support.opendns.com/entries/23891440
1279 url
= "https://updates.opendns.com/nic/update"
1281 def prepare_request_data(self
, proto
):
1283 "hostname" : self
.hostname
,
1284 "myip" : self
.get_address(proto
),
1290 class DDNSProviderOVH(DDNSProtocolDynDNS2
, DDNSProvider
):
1293 website
= "http://www.ovh.com/"
1294 protocols
= ("ipv4",)
1296 # OVH only provides very limited information about how to
1297 # update a DynDNS host. They only provide the update url
1298 # on the their german subpage.
1300 # http://hilfe.ovh.de/DomainDynHost
1302 url
= "https://www.ovh.com/nic/update"
1304 def prepare_request_data(self
, proto
):
1305 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1307 "system" : "dyndns",
1313 class DDNSProviderRegfish(DDNSProvider
):
1314 handle
= "regfish.com"
1315 name
= "Regfish GmbH"
1316 website
= "http://www.regfish.com/"
1318 # A full documentation to the providers api can be found here
1319 # but is only available in german.
1320 # https://www.regfish.de/domains/dyndns/dokumentation
1322 url
= "https://dyndns.regfish.de/"
1323 can_remove_records
= False
1327 "fqdn" : self
.hostname
,
1330 # Check if we update an IPv6 address.
1331 address6
= self
.get_address("ipv6")
1333 data
["ipv6"] = address6
1335 # Check if we update an IPv4 address.
1336 address4
= self
.get_address("ipv4")
1338 data
["ipv4"] = address4
1340 # Raise an error if none address is given.
1341 if not data
.has_key("ipv6") and not data
.has_key("ipv4"):
1342 raise DDNSConfigurationError
1344 # Check if a token has been set.
1346 data
["token"] = self
.token
1348 # Raise an error if no token and no useranem and password
1350 elif not self
.username
and not self
.password
:
1351 raise DDNSConfigurationError(_("No Auth details specified."))
1353 # HTTP Basic Auth is only allowed if no token is used.
1355 # Send update to the server.
1356 response
= self
.send_request(self
.url
, data
=data
)
1358 # Send update to the server.
1359 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
1362 # Get the full response message.
1363 output
= response
.read()
1365 # Handle success messages.
1366 if "100" in output
or "101" in output
:
1369 # Handle error codes.
1370 if "401" or "402" in output
:
1371 raise DDNSAuthenticationError
1372 elif "408" in output
:
1373 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
1374 elif "409" in output
:
1375 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
1376 elif "412" in output
:
1377 raise DDNSRequestError(_("No valid FQDN was given."))
1378 elif "414" in output
:
1379 raise DDNSInternalServerError
1381 # If we got here, some other update error happened.
1382 raise DDNSUpdateError
1385 class DDNSProviderSelfhost(DDNSProtocolDynDNS2
, DDNSProvider
):
1386 handle
= "selfhost.de"
1387 name
= "Selfhost.de"
1388 website
= "http://www.selfhost.de/"
1389 protocols
= ("ipv4",)
1391 url
= "https://carol.selfhost.de/nic/update"
1393 def prepare_request_data(self
, proto
):
1394 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1402 class DDNSProviderSPDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1403 handle
= "spdns.org"
1405 website
= "http://spdns.org/"
1407 # Detailed information about request and response codes are provided
1408 # by the vendor. They are using almost the same mechanism and status
1409 # codes as dyndns.org so we can inherit all those stuff.
1411 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
1412 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
1414 url
= "https://update.spdns.de/nic/update"
1418 return self
.get("username") or self
.hostname
1422 return self
.get("password") or self
.token
1425 class DDNSProviderStrato(DDNSProtocolDynDNS2
, DDNSProvider
):
1426 handle
= "strato.com"
1428 website
= "http:/www.strato.com/"
1429 protocols
= ("ipv4",)
1431 # Information about the request and response can be obtained here:
1432 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
1434 url
= "https://dyndns.strato.com/nic/update"
1436 def prepare_request_data(self
, proto
):
1437 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1440 "backupmx" : "NOCHG"
1446 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1447 handle
= "twodns.de"
1449 website
= "http://www.twodns.de"
1450 protocols
= ("ipv4",)
1452 # Detailed information about the request can be found here
1453 # http://twodns.de/en/faqs
1454 # http://twodns.de/en/api
1456 url
= "https://update.twodns.de/update"
1458 def prepare_request_data(self
, proto
):
1459 assert proto
== "ipv4"
1462 "ip" : self
.get_address(proto
),
1463 "hostname" : self
.hostname
1469 class DDNSProviderUdmedia(DDNSProtocolDynDNS2
, DDNSProvider
):
1470 handle
= "udmedia.de"
1471 name
= "Udmedia GmbH"
1472 website
= "http://www.udmedia.de"
1473 protocols
= ("ipv4",)
1475 # Information about the request can be found here
1476 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1478 url
= "https://www.udmedia.de/nic/update"
1481 class DDNSProviderVariomedia(DDNSProtocolDynDNS2
, DDNSProvider
):
1482 handle
= "variomedia.de"
1484 website
= "http://www.variomedia.de/"
1485 protocols
= ("ipv6", "ipv4",)
1487 # Detailed information about the request can be found here
1488 # https://dyndns.variomedia.de/
1490 url
= "https://dyndns.variomedia.de/nic/update"
1492 def prepare_request_data(self
, proto
):
1494 "hostname" : self
.hostname
,
1495 "myip" : self
.get_address(proto
),
1501 class DDNSProviderXLhost(DDNSProtocolDynDNS2
, DDNSProvider
):
1502 handle
= "xlhost.de"
1504 website
= "http://xlhost.de/"
1505 protocols
= ("ipv4",)
1507 # Information about the format of the HTTP request is to be found
1508 # here: https://xlhost.de/faq/index_html?topicId=CQA2ELIPO4SQ
1510 url
= "https://nsupdate.xlhost.de/"
1513 class DDNSProviderZoneedit(DDNSProvider
):
1514 handle
= "zoneedit.com"
1516 website
= "http://www.zoneedit.com"
1517 protocols
= ("ipv4",)
1519 # Detailed information about the request and the response codes can be
1521 # http://www.zoneedit.com/doc/api/other.html
1522 # http://www.zoneedit.com/faq.html
1524 url
= "https://dynamic.zoneedit.com/auth/dynamic.html"
1526 def update_protocol(self
, proto
):
1528 "dnsto" : self
.get_address(proto
),
1529 "host" : self
.hostname
1532 # Send update to the server.
1533 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
1536 # Get the full response message.
1537 output
= response
.read()
1539 # Handle success messages.
1540 if output
.startswith("<SUCCESS"):
1543 # Handle error codes.
1544 if output
.startswith("invalid login"):
1545 raise DDNSAuthenticationError
1546 elif output
.startswith("<ERROR CODE=\"704\""):
1547 raise DDNSRequestError(_("No valid FQDN was given."))
1548 elif output
.startswith("<ERROR CODE=\"702\""):
1549 raise DDNSInternalServerError
1551 # If we got here, some other update error happened.
1552 raise DDNSUpdateError
1555 class DDNSProviderZZZZ(DDNSProvider
):
1558 website
= "https://zzzz.io"
1559 protocols
= ("ipv6", "ipv4",)
1561 # Detailed information about the update request can be found here:
1562 # https://zzzz.io/faq/
1564 # Details about the possible response codes have been provided in the bugtracker:
1565 # https://bugzilla.ipfire.org/show_bug.cgi?id=10584#c2
1567 url
= "https://zzzz.io/api/v1/update"
1568 can_remove_records
= False
1570 def update_protocol(self
, proto
):
1572 "ip" : self
.get_address(proto
),
1573 "token" : self
.token
,
1577 data
["type"] = "aaaa"
1579 # zzzz uses the host from the full hostname as part
1580 # of the update url.
1581 host
, domain
= self
.hostname
.split(".", 1)
1583 # Add host value to the update url.
1584 url
= "%s/%s" % (self
.url
, host
)
1586 # Send update to the server.
1588 response
= self
.send_request(url
, data
=data
)
1590 # Handle error codes.
1591 except DDNSNotFound
:
1592 raise DDNSRequestError(_("Invalid hostname specified"))
1594 # Handle success messages.
1595 if response
.code
== 200:
1598 # If we got here, some other update error happened.
1599 raise DDNSUpdateError