]>
git.ipfire.org Git - people/stevee/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 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 DDNSProviderDynU(DDNSProtocolDynDNS2
, DDNSProvider
):
847 website
= "http://dynu.com/"
848 protocols
= ("ipv6", "ipv4",)
850 # Detailed information about the request and response codes
851 # are available on the providers webpage.
852 # http://dynu.com/Default.aspx?page=dnsapi
854 url
= "https://api.dynu.com/nic/update"
856 # DynU sends the IPv6 and IPv4 address in one request
859 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, "ipv4")
861 # This one supports IPv6
862 myipv6
= self
.get_address("ipv6")
864 # Add update information if we have an IPv6 address.
866 data
["myipv6"] = myipv6
868 self
.send_request(data
)
871 class DDNSProviderEasyDNS(DDNSProvider
):
872 handle
= "easydns.com"
874 website
= "http://www.easydns.com/"
875 protocols
= ("ipv4",)
877 # Detailed information about the request and response codes
878 # (API 1.3) are available on the providers webpage.
879 # https://fusion.easydns.com/index.php?/Knowledgebase/Article/View/102/7/dynamic-dns
881 url
= "http://api.cp.easydns.com/dyn/tomato.php"
883 def update_protocol(self
, proto
):
885 "myip" : self
.get_address(proto
, "-"),
886 "hostname" : self
.hostname
,
889 # Send update to the server.
890 response
= self
.send_request(self
.url
, data
=data
,
891 username
=self
.username
, password
=self
.password
)
893 # Get the full response message.
894 output
= response
.read()
896 # Remove all leading and trailing whitespace.
897 output
= output
.strip()
899 # Handle success messages.
900 if output
.startswith("NOERROR"):
903 # Handle error codes.
904 if output
.startswith("NOACCESS"):
905 raise DDNSAuthenticationError
907 elif output
.startswith("NOSERVICE"):
908 raise DDNSRequestError(_("Dynamic DNS is not turned on for this domain"))
910 elif output
.startswith("ILLEGAL INPUT"):
911 raise DDNSRequestError(_("Invalid data has been sent"))
913 elif output
.startswith("TOOSOON"):
914 raise DDNSRequestError(_("Too frequent update requests have been sent"))
916 # If we got here, some other update error happened.
917 raise DDNSUpdateError
920 class DDNSProviderDomopoli(DDNSProtocolDynDNS2
, DDNSProvider
):
921 handle
= "domopoli.de"
923 website
= "http://domopoli.de/"
924 protocols
= ("ipv4",)
926 # https://www.domopoli.de/?page=howto#DynDns_start
928 url
= "http://dyndns.domopoli.de/nic/update"
931 class DDNSProviderDynsNet(DDNSProvider
):
934 website
= "http://www.dyns.net/"
935 protocols
= ("ipv4",)
936 can_remove_records
= False
938 # There is very detailed informatio about how to send the update request and
939 # the possible response codes. (Currently we are using the v1.1 proto)
940 # http://www.dyns.net/documentation/technical/protocol/
942 url
= "http://www.dyns.net/postscript011.php"
944 def update_protocol(self
, proto
):
946 "ip" : self
.get_address(proto
),
947 "host" : self
.hostname
,
948 "username" : self
.username
,
949 "password" : self
.password
,
952 # Send update to the server.
953 response
= self
.send_request(self
.url
, data
=data
)
955 # Get the full response message.
956 output
= response
.read()
958 # Handle success messages.
959 if output
.startswith("200"):
962 # Handle error codes.
963 if output
.startswith("400"):
964 raise DDNSRequestError(_("Malformed request has been sent"))
965 elif output
.startswith("401"):
966 raise DDNSAuthenticationError
967 elif output
.startswith("402"):
968 raise DDNSRequestError(_("Too frequent update requests have been sent"))
969 elif output
.startswith("403"):
970 raise DDNSInternalServerError
972 # If we got here, some other update error happened.
973 raise DDNSUpdateError(_("Server response: %s") % output
)
976 class DDNSProviderEnomCom(DDNSResponseParserXML
, DDNSProvider
):
979 website
= "http://www.enom.com/"
980 protocols
= ("ipv4",)
982 # There are very detailed information about how to send an update request and
984 # http://www.enom.com/APICommandCatalog/
986 url
= "https://dynamic.name-services.com/interface.asp"
987 can_remove_records
= False
989 def update_protocol(self
, proto
):
991 "command" : "setdnshost",
992 "responsetype" : "xml",
993 "address" : self
.get_address(proto
),
994 "domainpassword" : self
.password
,
995 "zone" : self
.hostname
998 # Send update to the server.
999 response
= self
.send_request(self
.url
, data
=data
)
1001 # Get the full response message.
1002 output
= response
.read()
1004 # Handle success messages.
1005 if self
.get_xml_tag_value(output
, "ErrCount") == "0":
1008 # Handle error codes.
1009 errorcode
= self
.get_xml_tag_value(output
, "ResponseNumber")
1011 if errorcode
== "304155":
1012 raise DDNSAuthenticationError
1013 elif errorcode
== "304153":
1014 raise DDNSRequestError(_("Domain not found"))
1016 # If we got here, some other update error happened.
1017 raise DDNSUpdateError
1020 class DDNSProviderEntryDNS(DDNSProvider
):
1021 handle
= "entrydns.net"
1023 website
= "http://entrydns.net/"
1024 protocols
= ("ipv4",)
1026 # Some very tiny details about their so called "Simple API" can be found
1027 # here: https://entrydns.net/help
1028 url
= "https://entrydns.net/records/modify"
1029 can_remove_records
= False
1031 def update_protocol(self
, proto
):
1033 "ip" : self
.get_address(proto
),
1036 # Add auth token to the update url.
1037 url
= "%s/%s" % (self
.url
, self
.token
)
1039 # Send update to the server.
1041 response
= self
.send_request(url
, data
=data
)
1043 # Handle error codes
1044 except urllib2
.HTTPError
, e
:
1046 raise DDNSAuthenticationError
1049 raise DDNSRequestError(_("An invalid IP address was submitted"))
1053 # Handle success messages.
1054 if response
.code
== 200:
1057 # If we got here, some other update error happened.
1058 raise DDNSUpdateError
1061 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider
):
1062 handle
= "freedns.afraid.org"
1063 name
= "freedns.afraid.org"
1064 website
= "http://freedns.afraid.org/"
1066 # No information about the request or response could be found on the vendor
1067 # page. All used values have been collected by testing.
1068 url
= "https://freedns.afraid.org/dynamic/update.php"
1069 can_remove_records
= False
1071 def update_protocol(self
, proto
):
1073 "address" : self
.get_address(proto
),
1076 # Add auth token to the update url.
1077 url
= "%s?%s" % (self
.url
, self
.token
)
1079 # Send update to the server.
1080 response
= self
.send_request(url
, data
=data
)
1082 # Get the full response message.
1083 output
= response
.read()
1085 # Handle success messages.
1086 if output
.startswith("Updated") or "has not changed" in output
:
1089 # Handle error codes.
1090 if output
== "ERROR: Unable to locate this record":
1091 raise DDNSAuthenticationError
1092 elif "is an invalid IP address" in output
:
1093 raise DDNSRequestError(_("Invalid IP address has been sent"))
1095 # If we got here, some other update error happened.
1096 raise DDNSUpdateError
1099 class DDNSProviderJoker(DDNSProtocolDynDNS2
, DDNSProvider
):
1100 handle
= "joker.com"
1101 name
= "Joker.com Dynamic DNS"
1102 website
= "https://joker.com/"
1103 protocols
= ("ipv4",)
1105 # Information about the request can be found here:
1106 # https://joker.com/faq/content/11/427/en/what-is-dynamic-dns-dyndns.html
1107 # Using DynDNS V2 protocol over HTTPS here
1109 url
= "https://svc.joker.com/nic/update"
1112 class DDNSProviderGoogle(DDNSProtocolDynDNS2
, DDNSProvider
):
1113 handle
= "domains.google.com"
1114 name
= "Google Domains"
1115 website
= "https://domains.google.com/"
1116 protocols
= ("ipv4",)
1118 # Information about the format of the HTTP request is to be found
1119 # here: https://support.google.com/domains/answer/6147083?hl=en
1121 url
= "https://domains.google.com/nic/update"
1124 class DDNSProviderLightningWireLabs(DDNSProvider
):
1125 handle
= "dns.lightningwirelabs.com"
1126 name
= "Lightning Wire Labs DNS Service"
1127 website
= "http://dns.lightningwirelabs.com/"
1129 # Information about the format of the HTTPS request is to be found
1130 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
1132 url
= "https://dns.lightningwirelabs.com/update"
1136 "hostname" : self
.hostname
,
1137 "address6" : self
.get_address("ipv6", "-"),
1138 "address4" : self
.get_address("ipv4", "-"),
1141 # Check if a token has been set.
1143 data
["token"] = self
.token
1145 # Check for username and password.
1146 elif self
.username
and self
.password
:
1148 "username" : self
.username
,
1149 "password" : self
.password
,
1152 # Raise an error if no auth details are given.
1154 raise DDNSConfigurationError
1156 # Send update to the server.
1157 response
= self
.send_request(self
.url
, data
=data
)
1159 # Handle success messages.
1160 if response
.code
== 200:
1163 # If we got here, some other update error happened.
1164 raise DDNSUpdateError
1167 class DDNSProviderLoopia(DDNSProtocolDynDNS2
, DDNSProvider
):
1168 handle
= "loopia.se"
1170 website
= "https://www.loopia.com"
1171 protocols
= ("ipv4",)
1173 # Information about the format of the HTTP request is to be found
1174 # here: https://support.loopia.com/wiki/About_the_DynDNS_support
1176 url
= "https://dns.loopia.se/XDynDNSServer/XDynDNS.php"
1179 class DDNSProviderMyOnlinePortal(DDNSProtocolDynDNS2
, DDNSProvider
):
1180 handle
= "myonlineportal.net"
1181 name
= "myonlineportal.net"
1182 website
= "https:/myonlineportal.net/"
1184 # Information about the request and response can be obtained here:
1185 # https://myonlineportal.net/howto_dyndns
1187 url
= "https://myonlineportal.net/updateddns"
1189 def prepare_request_data(self
, proto
):
1191 "hostname" : self
.hostname
,
1192 "ip" : self
.get_address(proto
),
1198 class DDNSProviderNamecheap(DDNSResponseParserXML
, DDNSProvider
):
1199 handle
= "namecheap.com"
1201 website
= "http://namecheap.com"
1202 protocols
= ("ipv4",)
1204 # Information about the format of the HTTP request is to be found
1205 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
1206 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
1208 url
= "https://dynamicdns.park-your-domain.com/update"
1209 can_remove_records
= False
1211 def update_protocol(self
, proto
):
1212 # Namecheap requires the hostname splitted into a host and domain part.
1213 host
, domain
= self
.hostname
.split(".", 1)
1215 # Get and store curent IP address.
1216 address
= self
.get_address(proto
)
1220 "password" : self
.password
,
1225 # Send update to the server.
1226 response
= self
.send_request(self
.url
, data
=data
)
1228 # Get the full response message.
1229 output
= response
.read()
1231 # Handle success messages.
1232 if self
.get_xml_tag_value(output
, "IP") == address
:
1235 # Handle error codes.
1236 errorcode
= self
.get_xml_tag_value(output
, "ResponseNumber")
1238 if errorcode
== "304156":
1239 raise DDNSAuthenticationError
1240 elif errorcode
== "316153":
1241 raise DDNSRequestError(_("Domain not found"))
1242 elif errorcode
== "316154":
1243 raise DDNSRequestError(_("Domain not active"))
1244 elif errorcode
in ("380098", "380099"):
1245 raise DDNSInternalServerError
1247 # If we got here, some other update error happened.
1248 raise DDNSUpdateError
1251 class DDNSProviderNOIP(DDNSProtocolDynDNS2
, DDNSProvider
):
1252 handle
= "no-ip.com"
1254 website
= "http://www.no-ip.com/"
1255 protocols
= ("ipv4",)
1257 # Information about the format of the HTTP request is to be found
1258 # here: http://www.no-ip.com/integrate/request and
1259 # here: http://www.no-ip.com/integrate/response
1261 url
= "http://dynupdate.no-ip.com/nic/update"
1263 def prepare_request_data(self
, proto
):
1264 assert proto
== "ipv4"
1267 "hostname" : self
.hostname
,
1268 "address" : self
.get_address(proto
),
1274 class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2
, DDNSProvider
):
1275 handle
= "nsupdate.info"
1276 name
= "nsupdate.info"
1277 website
= "http://nsupdate.info/"
1278 protocols
= ("ipv6", "ipv4",)
1280 # Information about the format of the HTTP request can be found
1281 # after login on the provider user interface and here:
1282 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
1284 url
= "https://nsupdate.info/nic/update"
1286 # TODO nsupdate.info can actually do this, but the functionality
1287 # has not been implemented here, yet.
1288 can_remove_records
= False
1290 # After a failed update, there will be no retries
1291 # https://bugzilla.ipfire.org/show_bug.cgi?id=10603
1292 holdoff_failure_days
= None
1294 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
1295 # and for the password a so called secret.
1298 return self
.get("hostname")
1302 return self
.token
or self
.get("secret")
1304 def prepare_request_data(self
, proto
):
1306 "myip" : self
.get_address(proto
),
1312 class DDNSProviderOpenDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1313 handle
= "opendns.com"
1315 website
= "http://www.opendns.com"
1317 # Detailed information about the update request and possible
1318 # response codes can be obtained from here:
1319 # https://support.opendns.com/entries/23891440
1321 url
= "https://updates.opendns.com/nic/update"
1323 def prepare_request_data(self
, proto
):
1325 "hostname" : self
.hostname
,
1326 "myip" : self
.get_address(proto
),
1332 class DDNSProviderOVH(DDNSProtocolDynDNS2
, DDNSProvider
):
1335 website
= "http://www.ovh.com/"
1336 protocols
= ("ipv4",)
1338 # OVH only provides very limited information about how to
1339 # update a DynDNS host. They only provide the update url
1340 # on the their german subpage.
1342 # http://hilfe.ovh.de/DomainDynHost
1344 url
= "https://www.ovh.com/nic/update"
1346 def prepare_request_data(self
, proto
):
1347 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1349 "system" : "dyndns",
1355 class DDNSProviderRegfish(DDNSProvider
):
1356 handle
= "regfish.com"
1357 name
= "Regfish GmbH"
1358 website
= "http://www.regfish.com/"
1360 # A full documentation to the providers api can be found here
1361 # but is only available in german.
1362 # https://www.regfish.de/domains/dyndns/dokumentation
1364 url
= "https://dyndns.regfish.de/"
1365 can_remove_records
= False
1369 "fqdn" : self
.hostname
,
1372 # Check if we update an IPv6 address.
1373 address6
= self
.get_address("ipv6")
1375 data
["ipv6"] = address6
1377 # Check if we update an IPv4 address.
1378 address4
= self
.get_address("ipv4")
1380 data
["ipv4"] = address4
1382 # Raise an error if none address is given.
1383 if not data
.has_key("ipv6") and not data
.has_key("ipv4"):
1384 raise DDNSConfigurationError
1386 # Check if a token has been set.
1388 data
["token"] = self
.token
1390 # Raise an error if no token and no useranem and password
1392 elif not self
.username
and not self
.password
:
1393 raise DDNSConfigurationError(_("No Auth details specified"))
1395 # HTTP Basic Auth is only allowed if no token is used.
1397 # Send update to the server.
1398 response
= self
.send_request(self
.url
, data
=data
)
1400 # Send update to the server.
1401 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
1404 # Get the full response message.
1405 output
= response
.read()
1407 # Handle success messages.
1408 if "100" in output
or "101" in output
:
1411 # Handle error codes.
1412 if "401" or "402" in output
:
1413 raise DDNSAuthenticationError
1414 elif "408" in output
:
1415 raise DDNSRequestError(_("Invalid IPv4 address has been sent"))
1416 elif "409" in output
:
1417 raise DDNSRequestError(_("Invalid IPv6 address has been sent"))
1418 elif "412" in output
:
1419 raise DDNSRequestError(_("No valid FQDN was given"))
1420 elif "414" in output
:
1421 raise DDNSInternalServerError
1423 # If we got here, some other update error happened.
1424 raise DDNSUpdateError
1427 class DDNSProviderSchokokeksDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1428 handle
= "schokokeks.org"
1430 website
= "http://www.schokokeks.org/"
1431 protocols
= ("ipv4",)
1433 # Information about the format of the request is to be found
1434 # https://wiki.schokokeks.org/DynDNS
1435 url
= "https://dyndns.schokokeks.org/nic/update"
1438 class DDNSProviderSelfhost(DDNSProtocolDynDNS2
, DDNSProvider
):
1439 handle
= "selfhost.de"
1440 name
= "Selfhost.de"
1441 website
= "http://www.selfhost.de/"
1442 protocols
= ("ipv4",)
1444 url
= "https://carol.selfhost.de/nic/update"
1446 def prepare_request_data(self
, proto
):
1447 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1455 class DDNSProviderServercow(DDNSProvider
):
1456 handle
= "servercow.de"
1457 name
= "servercow.de"
1458 website
= "https://servercow.de/"
1459 protocols
= ("ipv4", "ipv6")
1461 url
= "https://www.servercow.de/dnsupdate/update.php"
1462 can_remove_records
= False
1464 def update_protocol(self
, proto
):
1466 "ipaddr" : self
.get_address(proto
),
1467 "hostname" : self
.hostname
,
1468 "username" : self
.username
,
1469 "pass" : self
.password
,
1472 # Send request to provider
1473 response
= self
.send_request(self
.url
, data
=data
)
1476 output
= response
.read()
1478 # Server responds with OK if update was successful
1479 if output
.startswith("OK"):
1483 elif output
.startswith("FAILED - Authentication failed"):
1484 raise DDNSAuthenticationError
1486 # If we got here, some other update error happened
1487 raise DDNSUpdateError(output
)
1490 class DDNSProviderSPDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1491 handle
= "spdns.org"
1493 website
= "https://www.spdyn.de/"
1495 # Detailed information about request and response codes are provided
1496 # by the vendor. They are using almost the same mechanism and status
1497 # codes as dyndns.org so we can inherit all those stuff.
1499 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
1500 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
1502 url
= "https://update.spdyn.de/nic/update"
1506 return self
.get("username") or self
.hostname
1510 return self
.get("password") or self
.token
1513 class DDNSProviderStrato(DDNSProtocolDynDNS2
, DDNSProvider
):
1514 handle
= "strato.com"
1516 website
= "http:/www.strato.com/"
1517 protocols
= ("ipv4",)
1519 # Information about the request and response can be obtained here:
1520 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
1522 url
= "https://dyndns.strato.com/nic/update"
1524 def prepare_request_data(self
, proto
):
1525 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1528 "backupmx" : "NOCHG"
1534 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1535 handle
= "twodns.de"
1537 website
= "http://www.twodns.de"
1538 protocols
= ("ipv4",)
1540 # Detailed information about the request can be found here
1541 # http://twodns.de/en/faqs
1542 # http://twodns.de/en/api
1544 url
= "https://update.twodns.de/update"
1546 def prepare_request_data(self
, proto
):
1547 assert proto
== "ipv4"
1550 "ip" : self
.get_address(proto
),
1551 "hostname" : self
.hostname
1557 class DDNSProviderUdmedia(DDNSProtocolDynDNS2
, DDNSProvider
):
1558 handle
= "udmedia.de"
1559 name
= "Udmedia GmbH"
1560 website
= "http://www.udmedia.de"
1561 protocols
= ("ipv4",)
1563 # Information about the request can be found here
1564 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1566 url
= "https://www.udmedia.de/nic/update"
1569 class DDNSProviderVariomedia(DDNSProtocolDynDNS2
, DDNSProvider
):
1570 handle
= "variomedia.de"
1572 website
= "http://www.variomedia.de/"
1573 protocols
= ("ipv6", "ipv4",)
1575 # Detailed information about the request can be found here
1576 # https://dyndns.variomedia.de/
1578 url
= "https://dyndns.variomedia.de/nic/update"
1580 def prepare_request_data(self
, proto
):
1582 "hostname" : self
.hostname
,
1583 "myip" : self
.get_address(proto
),
1589 class DDNSProviderXLhost(DDNSProtocolDynDNS2
, DDNSProvider
):
1590 handle
= "xlhost.de"
1592 website
= "http://xlhost.de/"
1593 protocols
= ("ipv4",)
1595 # Information about the format of the HTTP request is to be found
1596 # here: https://xlhost.de/faq/index_html?topicId=CQA2ELIPO4SQ
1598 url
= "https://nsupdate.xlhost.de/"
1601 class DDNSProviderZoneedit(DDNSProvider
):
1602 handle
= "zoneedit.com"
1604 website
= "http://www.zoneedit.com"
1605 protocols
= ("ipv4",)
1607 # Detailed information about the request and the response codes can be
1609 # http://www.zoneedit.com/doc/api/other.html
1610 # http://www.zoneedit.com/faq.html
1612 url
= "https://dynamic.zoneedit.com/auth/dynamic.html"
1614 def update_protocol(self
, proto
):
1616 "dnsto" : self
.get_address(proto
),
1617 "host" : self
.hostname
1620 # Send update to the server.
1621 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
1624 # Get the full response message.
1625 output
= response
.read()
1627 # Handle success messages.
1628 if output
.startswith("<SUCCESS"):
1631 # Handle error codes.
1632 if output
.startswith("invalid login"):
1633 raise DDNSAuthenticationError
1634 elif output
.startswith("<ERROR CODE=\"704\""):
1635 raise DDNSRequestError(_("No valid FQDN was given"))
1636 elif output
.startswith("<ERROR CODE=\"702\""):
1637 raise DDNSRequestError(_("Too frequent update requests have been sent"))
1639 # If we got here, some other update error happened.
1640 raise DDNSUpdateError
1643 class DDNSProviderDNSmadeEasy(DDNSProvider
):
1644 handle
= "dnsmadeeasy.com"
1645 name
= "DNSmadeEasy.com"
1646 website
= "http://www.dnsmadeeasy.com/"
1647 protocols
= ("ipv4",)
1649 # DNS Made Easy Nameserver Provider also offering Dynamic DNS
1650 # Documentation can be found here:
1651 # http://www.dnsmadeeasy.com/dynamic-dns/
1653 url
= "https://cp.dnsmadeeasy.com/servlet/updateip?"
1654 can_remove_records
= False
1656 def update_protocol(self
, proto
):
1658 "ip" : self
.get_address(proto
),
1659 "id" : self
.hostname
,
1660 "username" : self
.username
,
1661 "password" : self
.password
,
1664 # Send update to the server.
1665 response
= self
.send_request(self
.url
, data
=data
)
1667 # Get the full response message.
1668 output
= response
.read()
1670 # Handle success messages.
1671 if output
.startswith("success") or output
.startswith("error-record-ip-same"):
1674 # Handle error codes.
1675 if output
.startswith("error-auth-suspend"):
1676 raise DDNSRequestError(_("Account has been suspended"))
1678 elif output
.startswith("error-auth-voided"):
1679 raise DDNSRequestError(_("Account has been revoked"))
1681 elif output
.startswith("error-record-invalid"):
1682 raise DDNSRequestError(_("Specified host does not exist"))
1684 elif output
.startswith("error-auth"):
1685 raise DDNSAuthenticationError
1687 # If we got here, some other update error happened.
1688 raise DDNSUpdateError(_("Server response: %s") % output
)
1691 class DDNSProviderZZZZ(DDNSProvider
):
1694 website
= "https://zzzz.io"
1695 protocols
= ("ipv6", "ipv4",)
1697 # Detailed information about the update request can be found here:
1698 # https://zzzz.io/faq/
1700 # Details about the possible response codes have been provided in the bugtracker:
1701 # https://bugzilla.ipfire.org/show_bug.cgi?id=10584#c2
1703 url
= "https://zzzz.io/api/v1/update"
1704 can_remove_records
= False
1706 def update_protocol(self
, proto
):
1708 "ip" : self
.get_address(proto
),
1709 "token" : self
.token
,
1713 data
["type"] = "aaaa"
1715 # zzzz uses the host from the full hostname as part
1716 # of the update url.
1717 host
, domain
= self
.hostname
.split(".", 1)
1719 # Add host value to the update url.
1720 url
= "%s/%s" % (self
.url
, host
)
1722 # Send update to the server.
1724 response
= self
.send_request(url
, data
=data
)
1726 # Handle error codes.
1727 except DDNSNotFound
:
1728 raise DDNSRequestError(_("Invalid hostname specified"))
1730 # Handle success messages.
1731 if response
.code
== 200:
1734 # If we got here, some other update error happened.
1735 raise DDNSUpdateError