]>
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 # 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 except DDNSNetworkError
as e
:
170 # In case of any errors, log the failed request and
171 # raise the exception.
172 except DDNSError
as e
:
173 self
.core
.db
.log_failure(self
.hostname
, e
)
176 logger
.info(_("Dynamic DNS update for %(hostname)s (%(provider)s) successful") % \
177 { "hostname" : self
.hostname
, "provider" : self
.name
})
178 self
.core
.db
.log_success(self
.hostname
)
181 for protocol
in self
.protocols
:
182 if self
.have_address(protocol
):
183 self
.update_protocol(protocol
)
184 elif self
.can_remove_records
:
185 self
.remove_protocol(protocol
)
187 def update_protocol(self
, proto
):
188 raise NotImplementedError
190 def remove_protocol(self
, proto
):
191 if not self
.can_remove_records
:
192 raise RuntimeError, "can_remove_records is enabled, but remove_protocol() not implemented"
194 raise NotImplementedError
197 def requires_update(self
):
198 # If the IP addresses have changed, an update is required
199 if self
.ip_address_changed(self
.protocols
):
200 logger
.debug(_("An update for %(hostname)s (%(provider)s)"
201 " is performed because of an IP address change") % \
202 { "hostname" : self
.hostname
, "provider" : self
.name
})
206 # If the holdoff time has expired, an update is required, too
207 if self
.holdoff_time_expired():
208 logger
.debug(_("An update for %(hostname)s (%(provider)s)"
209 " is performed because the holdoff time has expired") % \
210 { "hostname" : self
.hostname
, "provider" : self
.name
})
214 # Otherwise, we don't need to perform an update
215 logger
.debug(_("No update required for %(hostname)s (%(provider)s)") % \
216 { "hostname" : self
.hostname
, "provider" : self
.name
})
221 def has_failure(self
):
223 Returns True when the last update has failed and no retry
224 should be performed, yet.
226 last_status
= self
.db
.last_update_status(self
.hostname
)
228 # Return False if the last update has not failed.
229 if not last_status
== "failure":
232 # If there is no holdoff time, we won't update ever again.
233 if self
.holdoff_failure_days
is None:
234 logger
.warning(_("An update has not been performed because earlier updates failed for %s") \
236 logger
.warning(_("There will be no retries"))
240 # Determine when the holdoff time ends
241 last_update
= self
.db
.last_update(self
.hostname
, status
=last_status
)
242 holdoff_end
= last_update
+ datetime
.timedelta(days
=self
.holdoff_failure_days
)
244 now
= datetime
.datetime
.utcnow()
245 if now
< holdoff_end
:
246 failure_message
= self
.db
.last_update_failure_message(self
.hostname
)
248 logger
.warning(_("An update has not been performed because earlier updates failed for %s") \
252 logger
.warning(_("Last failure message:"))
254 for line
in failure_message
.splitlines():
255 logger
.warning(" %s" % line
)
257 logger
.warning(_("Further updates will be withheld until %s") % holdoff_end
)
263 def ip_address_changed(self
, protos
):
265 Returns True if this host is already up to date
266 and does not need to change the IP address on the
270 addresses
= self
.core
.system
.resolve(self
.hostname
, proto
)
271 current_address
= self
.get_address(proto
)
273 # Handle if the system has not got any IP address from a protocol
274 # (i.e. had full dual-stack connectivity which it has not any more)
275 if current_address
is None:
276 # If addresses still exists in the DNS system and if this provider
277 # is able to remove records, we will do that.
278 if addresses
and self
.can_remove_records
:
281 # Otherwise, we cannot go on...
284 if not current_address
in addresses
:
289 def holdoff_time_expired(self
):
291 Returns true if the holdoff time has expired
292 and the host requires an update
294 # If no holdoff days is defined, we cannot go on
295 if not self
.holdoff_days
:
298 # Get the timestamp of the last successfull update
299 last_update
= self
.db
.last_update(self
.hostname
, status
="success")
301 # If no timestamp has been recorded, no update has been
302 # performed. An update should be performed now.
306 # Determine when the holdoff time ends
307 holdoff_end
= last_update
+ datetime
.timedelta(days
=self
.holdoff_days
)
309 now
= datetime
.datetime
.utcnow()
311 if now
>= holdoff_end
:
312 logger
.debug("The holdoff time has expired for %s" % self
.hostname
)
315 logger
.debug("Updates for %s are held off until %s" % \
316 (self
.hostname
, holdoff_end
))
319 def send_request(self
, *args
, **kwargs
):
321 Proxy connection to the send request
324 return self
.core
.system
.send_request(*args
, **kwargs
)
326 def get_address(self
, proto
, default
=None):
328 Proxy method to get the current IP address.
330 return self
.core
.system
.get_address(proto
) or default
332 def have_address(self
, proto
):
334 Returns True if an IP address for the given protocol
337 address
= self
.get_address(proto
)
345 class DDNSProtocolDynDNS2(object):
347 This is an abstract class that implements the DynDNS updater
348 protocol version 2. As this is a popular way to update dynamic
349 DNS records, this class is supposed make the provider classes
353 # Information about the format of the request is to be found
354 # http://dyn.com/support/developers/api/perform-update/
355 # http://dyn.com/support/developers/api/return-codes/
357 # The DynDNS protocol version 2 does not allow to remove records
358 can_remove_records
= False
360 def prepare_request_data(self
, proto
):
362 "hostname" : self
.hostname
,
363 "myip" : self
.get_address(proto
),
368 def update_protocol(self
, proto
):
369 data
= self
.prepare_request_data(proto
)
371 return self
.send_request(data
)
373 def send_request(self
, data
):
374 # Send update to the server.
375 response
= DDNSProvider
.send_request(self
, self
.url
, data
=data
,
376 username
=self
.username
, password
=self
.password
)
378 # Get the full response message.
379 output
= response
.read()
381 # Handle success messages.
382 if output
.startswith("good") or output
.startswith("nochg"):
385 # Handle error codes.
386 if output
== "badauth":
387 raise DDNSAuthenticationError
388 elif output
== "abuse":
390 elif output
== "notfqdn":
391 raise DDNSRequestError(_("No valid FQDN was given."))
392 elif output
== "nohost":
393 raise DDNSRequestError(_("Specified host does not exist."))
394 elif output
== "911":
395 raise DDNSInternalServerError
396 elif output
== "dnserr":
397 raise DDNSInternalServerError(_("DNS error encountered."))
398 elif output
== "badagent":
399 raise DDNSBlockedError
401 # If we got here, some other update error happened.
402 raise DDNSUpdateError(_("Server response: %s") % output
)
405 class DDNSResponseParserXML(object):
407 This class provides a parser for XML responses which
408 will be sent by various providers. This class uses the python
409 shipped XML minidom module to walk through the XML tree and return
413 def get_xml_tag_value(self
, document
, content
):
414 # Send input to the parser.
415 xmldoc
= xml
.dom
.minidom
.parseString(document
)
417 # Get XML elements by the given content.
418 element
= xmldoc
.getElementsByTagName(content
)
420 # If no element has been found, we directly can return None.
424 # Only get the first child from an element, even there are more than one.
425 firstchild
= element
[0].firstChild
427 # Get the value of the child.
428 value
= firstchild
.nodeValue
434 class DDNSProviderAllInkl(DDNSProvider
):
435 handle
= "all-inkl.com"
436 name
= "All-inkl.com"
437 website
= "http://all-inkl.com/"
438 protocols
= ("ipv4",)
440 # There are only information provided by the vendor how to
441 # perform an update on a FRITZ Box. Grab requried informations
443 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
445 url
= "http://dyndns.kasserver.com"
446 can_remove_records
= False
449 # There is no additional data required so we directly can
451 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
)
453 # Get the full response message.
454 output
= response
.read()
456 # Handle success messages.
457 if output
.startswith("good") or output
.startswith("nochg"):
460 # If we got here, some other update error happened.
461 raise DDNSUpdateError
464 class DDNSProviderBindNsupdate(DDNSProvider
):
466 name
= "BIND nsupdate utility"
467 website
= "http://en.wikipedia.org/wiki/Nsupdate"
473 # Search if the nsupdate utility is available
474 paths
= os
.environ
.get("PATH")
476 for path
in paths
.split(":"):
477 executable
= os
.path
.join(path
, "nsupdate")
479 if os
.path
.exists(executable
):
485 scriptlet
= self
.__make
_scriptlet
()
487 # -v enables TCP hence we transfer keys and other data that may
488 # exceed the size of one packet.
489 # -t sets the timeout
490 command
= ["nsupdate", "-v", "-t", "60"]
492 p
= subprocess
.Popen(command
, shell
=True,
493 stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
495 stdout
, stderr
= p
.communicate(scriptlet
)
497 if p
.returncode
== 0:
500 raise DDNSError("nsupdate terminated with error code: %s\n %s" % (p
.returncode
, stderr
))
502 def __make_scriptlet(self
):
505 # Set a different server the update is sent to.
506 server
= self
.get("server", None)
508 scriptlet
.append("server %s" % server
)
510 # Set the DNS zone the host should be added to.
511 zone
= self
.get("zone", None)
513 scriptlet
.append("zone %s" % zone
)
515 key
= self
.get("key", None)
517 secret
= self
.get("secret")
519 scriptlet
.append("key %s %s" % (key
, secret
))
521 ttl
= self
.get("ttl", self
.DEFAULT_TTL
)
523 # Perform an update for each supported protocol.
524 for rrtype
, proto
in (("AAAA", "ipv6"), ("A", "ipv4")):
525 address
= self
.get_address(proto
)
529 scriptlet
.append("update delete %s. %s" % (self
.hostname
, rrtype
))
530 scriptlet
.append("update add %s. %s %s %s" % \
531 (self
.hostname
, ttl
, rrtype
, address
))
533 # Send the actions to the server.
534 scriptlet
.append("send")
535 scriptlet
.append("quit")
537 logger
.debug(_("Scriptlet:"))
538 for line
in scriptlet
:
539 # Masquerade the line with the secret key.
540 if line
.startswith("key"):
541 line
= "key **** ****"
543 logger
.debug(" %s" % line
)
545 return "\n".join(scriptlet
)
548 class DDNSProviderChangeIP(DDNSProvider
):
549 handle
= "changeip.com"
550 name
= "ChangeIP.com"
551 website
= "https://changeip.com"
552 protocols
= ("ipv4",)
554 # Detailed information about the update api can be found here.
555 # http://www.changeip.com/accounts/knowledgebase.php?action=displayarticle&id=34
557 url
= "https://nic.changeip.com/nic/update"
558 can_remove_records
= False
560 def update_protocol(self
, proto
):
562 "hostname" : self
.hostname
,
563 "myip" : self
.get_address(proto
),
566 # Send update to the server.
568 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
571 # Handle error codes.
572 except urllib2
.HTTPError
, e
:
574 raise DDNSRequestError(_("Domain not found."))
578 # Handle success message.
579 if response
.code
== 200:
582 # If we got here, some other update error happened.
583 raise DDNSUpdateError(_("Server response: %s") % output
)
586 class DDNSProviderDDNSS(DDNSProvider
):
589 website
= "http://www.ddnss.de"
590 protocols
= ("ipv4",)
592 # Detailed information about how to send the update request and possible response
593 # codes can be obtained from here.
594 # http://www.ddnss.de/info.php
595 # http://www.megacomputing.de/2014/08/dyndns-service-response-time/#more-919
597 url
= "http://www.ddnss.de/upd.php"
598 can_remove_records
= False
600 def update_protocol(self
, proto
):
602 "ip" : self
.get_address(proto
),
603 "host" : self
.hostname
,
606 # Check if a token has been set.
608 data
["key"] = self
.token
610 # Check if username and hostname are given.
611 elif self
.username
and self
.password
:
613 "user" : self
.username
,
614 "pwd" : self
.password
,
617 # Raise an error if no auth details are given.
619 raise DDNSConfigurationError
621 # Send update to the server.
622 response
= self
.send_request(self
.url
, data
=data
)
624 # This provider sends the response code as part of the header.
625 header
= response
.info()
627 # Get status information from the header.
628 output
= header
.getheader('ddnss-response')
630 # Handle success messages.
631 if output
== "good" or output
== "nochg":
634 # Handle error codes.
635 if output
== "badauth":
636 raise DDNSAuthenticationError
637 elif output
== "notfqdn":
638 raise DDNSRequestError(_("No valid FQDN was given."))
639 elif output
== "nohost":
640 raise DDNSRequestError(_("Specified host does not exist."))
641 elif output
== "911":
642 raise DDNSInternalServerError
643 elif output
== "dnserr":
644 raise DDNSInternalServerError(_("DNS error encountered."))
645 elif output
== "disabled":
646 raise DDNSRequestError(_("Account disabled or locked."))
648 # If we got here, some other update error happened.
649 raise DDNSUpdateError
652 class DDNSProviderDHS(DDNSProvider
):
654 name
= "DHS International"
655 website
= "http://dhs.org/"
656 protocols
= ("ipv4",)
658 # No information about the used update api provided on webpage,
659 # grabed from source code of ez-ipudate.
661 url
= "http://members.dhs.org/nic/hosts"
662 can_remove_records
= False
664 def update_protocol(self
, proto
):
666 "domain" : self
.hostname
,
667 "ip" : self
.get_address(proto
),
669 "hostcmdstage" : "2",
673 # Send update to the server.
674 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
677 # Handle success messages.
678 if response
.code
== 200:
681 # If we got here, some other update error happened.
682 raise DDNSUpdateError
685 class DDNSProviderDNSpark(DDNSProvider
):
686 handle
= "dnspark.com"
688 website
= "http://dnspark.com/"
689 protocols
= ("ipv4",)
691 # Informations to the used api can be found here:
692 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
694 url
= "https://control.dnspark.com/api/dynamic/update.php"
695 can_remove_records
= False
697 def update_protocol(self
, proto
):
699 "domain" : self
.hostname
,
700 "ip" : self
.get_address(proto
),
703 # Send update to the server.
704 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
707 # Get the full response message.
708 output
= response
.read()
710 # Handle success messages.
711 if output
.startswith("ok") or output
.startswith("nochange"):
714 # Handle error codes.
715 if output
== "unauth":
716 raise DDNSAuthenticationError
717 elif output
== "abuse":
719 elif output
== "blocked":
720 raise DDNSBlockedError
721 elif output
== "nofqdn":
722 raise DDNSRequestError(_("No valid FQDN was given."))
723 elif output
== "nohost":
724 raise DDNSRequestError(_("Invalid hostname specified."))
725 elif output
== "notdyn":
726 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
727 elif output
== "invalid":
728 raise DDNSRequestError(_("Invalid IP address has been sent."))
730 # If we got here, some other update error happened.
731 raise DDNSUpdateError
734 class DDNSProviderDtDNS(DDNSProvider
):
737 website
= "http://dtdns.com/"
738 protocols
= ("ipv4",)
740 # Information about the format of the HTTPS request is to be found
741 # http://www.dtdns.com/dtsite/updatespec
743 url
= "https://www.dtdns.com/api/autodns.cfm"
744 can_remove_records
= False
746 def update_protocol(self
, proto
):
748 "ip" : self
.get_address(proto
),
749 "id" : self
.hostname
,
753 # Send update to the server.
754 response
= self
.send_request(self
.url
, data
=data
)
756 # Get the full response message.
757 output
= response
.read()
759 # Remove all leading and trailing whitespace.
760 output
= output
.strip()
762 # Handle success messages.
763 if "now points to" in output
:
766 # Handle error codes.
767 if output
== "No hostname to update was supplied.":
768 raise DDNSRequestError(_("No hostname specified."))
770 elif output
== "The hostname you supplied is not valid.":
771 raise DDNSRequestError(_("Invalid hostname specified."))
773 elif output
== "The password you supplied is not valid.":
774 raise DDNSAuthenticationError
776 elif output
== "Administration has disabled this account.":
777 raise DDNSRequestError(_("Account has been disabled."))
779 elif output
== "Illegal character in IP.":
780 raise DDNSRequestError(_("Invalid IP address has been sent."))
782 elif output
== "Too many failed requests.":
783 raise DDNSRequestError(_("Too many failed requests."))
785 # If we got here, some other update error happened.
786 raise DDNSUpdateError
789 class DDNSProviderDynDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
790 handle
= "dyndns.org"
792 website
= "http://dyn.com/dns/"
793 protocols
= ("ipv4",)
795 # Information about the format of the request is to be found
796 # http://http://dyn.com/support/developers/api/perform-update/
797 # http://dyn.com/support/developers/api/return-codes/
799 url
= "https://members.dyndns.org/nic/update"
802 class DDNSProviderDynU(DDNSProtocolDynDNS2
, DDNSProvider
):
805 website
= "http://dynu.com/"
806 protocols
= ("ipv6", "ipv4",)
808 # Detailed information about the request and response codes
809 # are available on the providers webpage.
810 # http://dynu.com/Default.aspx?page=dnsapi
812 url
= "https://api.dynu.com/nic/update"
814 # DynU sends the IPv6 and IPv4 address in one request
817 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, "ipv4")
819 # This one supports IPv6
820 myipv6
= self
.get_address("ipv6")
822 # Add update information if we have an IPv6 address.
824 data
["myipv6"] = myipv6
826 self
.send_request(data
)
829 class DDNSProviderEasyDNS(DDNSProvider
):
830 handle
= "easydns.com"
832 website
= "http://www.easydns.com/"
833 protocols
= ("ipv4",)
835 # Detailed information about the request and response codes
836 # (API 1.3) are available on the providers webpage.
837 # https://fusion.easydns.com/index.php?/Knowledgebase/Article/View/102/7/dynamic-dns
839 url
= "http://api.cp.easydns.com/dyn/tomato.php"
841 def update_protocol(self
, proto
):
843 "myip" : self
.get_address(proto
, "-"),
844 "hostname" : self
.hostname
,
847 # Send update to the server.
848 response
= self
.send_request(self
.url
, data
=data
,
849 username
=self
.username
, password
=self
.password
)
851 # Get the full response message.
852 output
= response
.read()
854 # Remove all leading and trailing whitespace.
855 output
= output
.strip()
857 # Handle success messages.
858 if output
.startswith("NOERROR"):
861 # Handle error codes.
862 if output
.startswith("NOACCESS"):
863 raise DDNSAuthenticationError
865 elif output
.startswith("NOSERVICE"):
866 raise DDNSRequestError(_("Dynamic DNS is not turned on for this domain."))
868 elif output
.startswith("ILLEGAL INPUT"):
869 raise DDNSRequestError(_("Invalid data has been sent."))
871 elif output
.startswith("TOOSOON"):
872 raise DDNSRequestError(_("Too frequent update requests have been sent."))
874 # If we got here, some other update error happened.
875 raise DDNSUpdateError
878 class DDNSProviderDomopoli(DDNSProtocolDynDNS2
, DDNSProvider
):
879 handle
= "domopoli.de"
881 website
= "http://domopoli.de/"
882 protocols
= ("ipv4",)
884 # https://www.domopoli.de/?page=howto#DynDns_start
886 url
= "http://dyndns.domopoli.de/nic/update"
889 class DDNSProviderDynsNet(DDNSProvider
):
892 website
= "http://www.dyns.net/"
893 protocols
= ("ipv4",)
894 can_remove_records
= False
896 # There is very detailed informatio about how to send the update request and
897 # the possible response codes. (Currently we are using the v1.1 proto)
898 # http://www.dyns.net/documentation/technical/protocol/
900 url
= "http://www.dyns.net/postscript011.php"
902 def update_protocol(self
, proto
):
904 "ip" : self
.get_address(proto
),
905 "host" : self
.hostname
,
906 "username" : self
.username
,
907 "password" : self
.password
,
910 # Send update to the server.
911 response
= self
.send_request(self
.url
, data
=data
)
913 # Get the full response message.
914 output
= response
.read()
916 # Handle success messages.
917 if output
.startswith("200"):
920 # Handle error codes.
921 if output
.startswith("400"):
922 raise DDNSRequestError(_("Malformed request has been sent."))
923 elif output
.startswith("401"):
924 raise DDNSAuthenticationError
925 elif output
.startswith("402"):
926 raise DDNSRequestError(_("Too frequent update requests have been sent."))
927 elif output
.startswith("403"):
928 raise DDNSInternalServerError
930 # If we got here, some other update error happened.
931 raise DDNSUpdateError(_("Server response: %s") % output
)
934 class DDNSProviderEnomCom(DDNSResponseParserXML
, DDNSProvider
):
937 website
= "http://www.enom.com/"
938 protocols
= ("ipv4",)
940 # There are very detailed information about how to send an update request and
942 # http://www.enom.com/APICommandCatalog/
944 url
= "https://dynamic.name-services.com/interface.asp"
945 can_remove_records
= False
947 def update_protocol(self
, proto
):
949 "command" : "setdnshost",
950 "responsetype" : "xml",
951 "address" : self
.get_address(proto
),
952 "domainpassword" : self
.password
,
953 "zone" : self
.hostname
956 # Send update to the server.
957 response
= self
.send_request(self
.url
, data
=data
)
959 # Get the full response message.
960 output
= response
.read()
962 # Handle success messages.
963 if self
.get_xml_tag_value(output
, "ErrCount") == "0":
966 # Handle error codes.
967 errorcode
= self
.get_xml_tag_value(output
, "ResponseNumber")
969 if errorcode
== "304155":
970 raise DDNSAuthenticationError
971 elif errorcode
== "304153":
972 raise DDNSRequestError(_("Domain not found."))
974 # If we got here, some other update error happened.
975 raise DDNSUpdateError
978 class DDNSProviderEntryDNS(DDNSProvider
):
979 handle
= "entrydns.net"
981 website
= "http://entrydns.net/"
982 protocols
= ("ipv4",)
984 # Some very tiny details about their so called "Simple API" can be found
985 # here: https://entrydns.net/help
986 url
= "https://entrydns.net/records/modify"
987 can_remove_records
= False
989 def update_protocol(self
, proto
):
991 "ip" : self
.get_address(proto
),
994 # Add auth token to the update url.
995 url
= "%s/%s" % (self
.url
, self
.token
)
997 # Send update to the server.
999 response
= self
.send_request(url
, data
=data
)
1001 # Handle error codes
1002 except urllib2
.HTTPError
, e
:
1004 raise DDNSAuthenticationError
1007 raise DDNSRequestError(_("An invalid IP address was submitted"))
1011 # Handle success messages.
1012 if response
.code
== 200:
1015 # If we got here, some other update error happened.
1016 raise DDNSUpdateError
1019 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider
):
1020 handle
= "freedns.afraid.org"
1021 name
= "freedns.afraid.org"
1022 website
= "http://freedns.afraid.org/"
1024 # No information about the request or response could be found on the vendor
1025 # page. All used values have been collected by testing.
1026 url
= "https://freedns.afraid.org/dynamic/update.php"
1027 can_remove_records
= False
1029 def update_protocol(self
, proto
):
1031 "address" : self
.get_address(proto
),
1034 # Add auth token to the update url.
1035 url
= "%s?%s" % (self
.url
, self
.token
)
1037 # Send update to the server.
1038 response
= self
.send_request(url
, data
=data
)
1040 # Get the full response message.
1041 output
= response
.read()
1043 # Handle success messages.
1044 if output
.startswith("Updated") or "has not changed" in output
:
1047 # Handle error codes.
1048 if output
== "ERROR: Unable to locate this record":
1049 raise DDNSAuthenticationError
1050 elif "is an invalid IP address" in output
:
1051 raise DDNSRequestError(_("Invalid IP address has been sent."))
1053 # If we got here, some other update error happened.
1054 raise DDNSUpdateError
1057 class DDNSProviderJoker(DDNSProtocolDynDNS2
, DDNSProvider
):
1058 handle
= "joker.com"
1059 name
= "Joker.com Dynamic DNS"
1060 website
= "https://joker.com/"
1061 protocols
= ("ipv4",)
1063 # Information about the request can be found here:
1064 # https://joker.com/faq/content/11/427/en/what-is-dynamic-dns-dyndns.html
1065 # Using DynDNS V2 protocol over HTTPS here
1067 url
= "https://svc.joker.com/nic/update"
1070 class DDNSProviderGoogle(DDNSProtocolDynDNS2
, DDNSProvider
):
1071 handle
= "domains.google.com"
1072 name
= "Google Domains"
1073 website
= "https://domains.google.com/"
1074 protocols
= ("ipv4",)
1076 # Information about the format of the HTTP request is to be found
1077 # here: https://support.google.com/domains/answer/6147083?hl=en
1079 url
= "https://domains.google.com/nic/update"
1082 class DDNSProviderLightningWireLabs(DDNSProvider
):
1083 handle
= "dns.lightningwirelabs.com"
1084 name
= "Lightning Wire Labs DNS Service"
1085 website
= "http://dns.lightningwirelabs.com/"
1087 # Information about the format of the HTTPS request is to be found
1088 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
1090 url
= "https://dns.lightningwirelabs.com/update"
1094 "hostname" : self
.hostname
,
1095 "address6" : self
.get_address("ipv6", "-"),
1096 "address4" : self
.get_address("ipv4", "-"),
1099 # Check if a token has been set.
1101 data
["token"] = self
.token
1103 # Check for username and password.
1104 elif self
.username
and self
.password
:
1106 "username" : self
.username
,
1107 "password" : self
.password
,
1110 # Raise an error if no auth details are given.
1112 raise DDNSConfigurationError
1114 # Send update to the server.
1115 response
= self
.send_request(self
.url
, data
=data
)
1117 # Handle success messages.
1118 if response
.code
== 200:
1121 # If we got here, some other update error happened.
1122 raise DDNSUpdateError
1125 class DDNSProviderLoopia(DDNSProtocolDynDNS2
, DDNSProvider
):
1126 handle
= "loopia.se"
1128 website
= "https://www.loopia.com"
1129 protocols
= ("ipv4",)
1131 # Information about the format of the HTTP request is to be found
1132 # here: https://support.loopia.com/wiki/About_the_DynDNS_support
1134 url
= "https://dns.loopia.se/XDynDNSServer/XDynDNS.php"
1137 class DDNSProviderMyOnlinePortal(DDNSProtocolDynDNS2
, DDNSProvider
):
1138 handle
= "myonlineportal.net"
1139 name
= "myonlineportal.net"
1140 website
= "https:/myonlineportal.net/"
1142 # Information about the request and response can be obtained here:
1143 # https://myonlineportal.net/howto_dyndns
1145 url
= "https://myonlineportal.net/updateddns"
1147 def prepare_request_data(self
, proto
):
1149 "hostname" : self
.hostname
,
1150 "ip" : self
.get_address(proto
),
1156 class DDNSProviderNamecheap(DDNSResponseParserXML
, DDNSProvider
):
1157 handle
= "namecheap.com"
1159 website
= "http://namecheap.com"
1160 protocols
= ("ipv4",)
1162 # Information about the format of the HTTP request is to be found
1163 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
1164 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
1166 url
= "https://dynamicdns.park-your-domain.com/update"
1167 can_remove_records
= False
1169 def update_protocol(self
, proto
):
1170 # Namecheap requires the hostname splitted into a host and domain part.
1171 host
, domain
= self
.hostname
.split(".", 1)
1174 "ip" : self
.get_address(proto
),
1175 "password" : self
.password
,
1180 # Send update to the server.
1181 response
= self
.send_request(self
.url
, data
=data
)
1183 # Get the full response message.
1184 output
= response
.read()
1186 # Handle success messages.
1187 if self
.get_xml_tag_value(output
, "IP") == address
:
1190 # Handle error codes.
1191 errorcode
= self
.get_xml_tag_value(output
, "ResponseNumber")
1193 if errorcode
== "304156":
1194 raise DDNSAuthenticationError
1195 elif errorcode
== "316153":
1196 raise DDNSRequestError(_("Domain not found."))
1197 elif errorcode
== "316154":
1198 raise DDNSRequestError(_("Domain not active."))
1199 elif errorcode
in ("380098", "380099"):
1200 raise DDNSInternalServerError
1202 # If we got here, some other update error happened.
1203 raise DDNSUpdateError
1206 class DDNSProviderNOIP(DDNSProtocolDynDNS2
, DDNSProvider
):
1207 handle
= "no-ip.com"
1209 website
= "http://www.no-ip.com/"
1210 protocols
= ("ipv4",)
1212 # Information about the format of the HTTP request is to be found
1213 # here: http://www.no-ip.com/integrate/request and
1214 # here: http://www.no-ip.com/integrate/response
1216 url
= "http://dynupdate.no-ip.com/nic/update"
1218 def prepare_request_data(self
, proto
):
1219 assert proto
== "ipv4"
1222 "hostname" : self
.hostname
,
1223 "address" : self
.get_address(proto
),
1229 class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2
, DDNSProvider
):
1230 handle
= "nsupdate.info"
1231 name
= "nsupdate.info"
1232 website
= "http://nsupdate.info/"
1233 protocols
= ("ipv6", "ipv4",)
1235 # Information about the format of the HTTP request can be found
1236 # after login on the provider user interface and here:
1237 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
1239 url
= "https://nsupdate.info/nic/update"
1241 # TODO nsupdate.info can actually do this, but the functionality
1242 # has not been implemented here, yet.
1243 can_remove_records
= False
1245 # After a failed update, there will be no retries
1246 # https://bugzilla.ipfire.org/show_bug.cgi?id=10603
1247 holdoff_failure_days
= None
1249 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
1250 # and for the password a so called secret.
1253 return self
.get("hostname")
1257 return self
.token
or self
.get("secret")
1259 def prepare_request_data(self
, proto
):
1261 "myip" : self
.get_address(proto
),
1267 class DDNSProviderOpenDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1268 handle
= "opendns.com"
1270 website
= "http://www.opendns.com"
1272 # Detailed information about the update request and possible
1273 # response codes can be obtained from here:
1274 # https://support.opendns.com/entries/23891440
1276 url
= "https://updates.opendns.com/nic/update"
1278 def prepare_request_data(self
, proto
):
1280 "hostname" : self
.hostname
,
1281 "myip" : self
.get_address(proto
),
1287 class DDNSProviderOVH(DDNSProtocolDynDNS2
, DDNSProvider
):
1290 website
= "http://www.ovh.com/"
1291 protocols
= ("ipv4",)
1293 # OVH only provides very limited information about how to
1294 # update a DynDNS host. They only provide the update url
1295 # on the their german subpage.
1297 # http://hilfe.ovh.de/DomainDynHost
1299 url
= "https://www.ovh.com/nic/update"
1301 def prepare_request_data(self
, proto
):
1302 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1304 "system" : "dyndns",
1310 class DDNSProviderRegfish(DDNSProvider
):
1311 handle
= "regfish.com"
1312 name
= "Regfish GmbH"
1313 website
= "http://www.regfish.com/"
1315 # A full documentation to the providers api can be found here
1316 # but is only available in german.
1317 # https://www.regfish.de/domains/dyndns/dokumentation
1319 url
= "https://dyndns.regfish.de/"
1320 can_remove_records
= False
1324 "fqdn" : self
.hostname
,
1327 # Check if we update an IPv6 address.
1328 address6
= self
.get_address("ipv6")
1330 data
["ipv6"] = address6
1332 # Check if we update an IPv4 address.
1333 address4
= self
.get_address("ipv4")
1335 data
["ipv4"] = address4
1337 # Raise an error if none address is given.
1338 if not data
.has_key("ipv6") and not data
.has_key("ipv4"):
1339 raise DDNSConfigurationError
1341 # Check if a token has been set.
1343 data
["token"] = self
.token
1345 # Raise an error if no token and no useranem and password
1347 elif not self
.username
and not self
.password
:
1348 raise DDNSConfigurationError(_("No Auth details specified."))
1350 # HTTP Basic Auth is only allowed if no token is used.
1352 # Send update to the server.
1353 response
= self
.send_request(self
.url
, data
=data
)
1355 # Send update to the server.
1356 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
1359 # Get the full response message.
1360 output
= response
.read()
1362 # Handle success messages.
1363 if "100" in output
or "101" in output
:
1366 # Handle error codes.
1367 if "401" or "402" in output
:
1368 raise DDNSAuthenticationError
1369 elif "408" in output
:
1370 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
1371 elif "409" in output
:
1372 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
1373 elif "412" in output
:
1374 raise DDNSRequestError(_("No valid FQDN was given."))
1375 elif "414" in output
:
1376 raise DDNSInternalServerError
1378 # If we got here, some other update error happened.
1379 raise DDNSUpdateError
1382 class DDNSProviderSelfhost(DDNSProtocolDynDNS2
, DDNSProvider
):
1383 handle
= "selfhost.de"
1384 name
= "Selfhost.de"
1385 website
= "http://www.selfhost.de/"
1386 protocols
= ("ipv4",)
1388 url
= "https://carol.selfhost.de/nic/update"
1390 def prepare_request_data(self
, proto
):
1391 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1399 class DDNSProviderSPDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1400 handle
= "spdns.org"
1402 website
= "http://spdns.org/"
1404 # Detailed information about request and response codes are provided
1405 # by the vendor. They are using almost the same mechanism and status
1406 # codes as dyndns.org so we can inherit all those stuff.
1408 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
1409 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
1411 url
= "https://update.spdns.de/nic/update"
1415 return self
.get("username") or self
.hostname
1419 return self
.get("password") or self
.token
1422 class DDNSProviderStrato(DDNSProtocolDynDNS2
, DDNSProvider
):
1423 handle
= "strato.com"
1425 website
= "http:/www.strato.com/"
1426 protocols
= ("ipv4",)
1428 # Information about the request and response can be obtained here:
1429 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
1431 url
= "https://dyndns.strato.com/nic/update"
1433 def prepare_request_data(self
, proto
):
1434 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1437 "backupmx" : "NOCHG"
1443 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1444 handle
= "twodns.de"
1446 website
= "http://www.twodns.de"
1447 protocols
= ("ipv4",)
1449 # Detailed information about the request can be found here
1450 # http://twodns.de/en/faqs
1451 # http://twodns.de/en/api
1453 url
= "https://update.twodns.de/update"
1455 def prepare_request_data(self
, proto
):
1456 assert proto
== "ipv4"
1459 "ip" : self
.get_address(proto
),
1460 "hostname" : self
.hostname
1466 class DDNSProviderUdmedia(DDNSProtocolDynDNS2
, DDNSProvider
):
1467 handle
= "udmedia.de"
1468 name
= "Udmedia GmbH"
1469 website
= "http://www.udmedia.de"
1470 protocols
= ("ipv4",)
1472 # Information about the request can be found here
1473 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1475 url
= "https://www.udmedia.de/nic/update"
1478 class DDNSProviderVariomedia(DDNSProtocolDynDNS2
, DDNSProvider
):
1479 handle
= "variomedia.de"
1481 website
= "http://www.variomedia.de/"
1482 protocols
= ("ipv6", "ipv4",)
1484 # Detailed information about the request can be found here
1485 # https://dyndns.variomedia.de/
1487 url
= "https://dyndns.variomedia.de/nic/update"
1489 def prepare_request_data(self
, proto
):
1491 "hostname" : self
.hostname
,
1492 "myip" : self
.get_address(proto
),
1498 class DDNSProviderXLhost(DDNSProtocolDynDNS2
, DDNSProvider
):
1499 handle
= "xlhost.de"
1501 website
= "http://xlhost.de/"
1502 protocols
= ("ipv4",)
1504 # Information about the format of the HTTP request is to be found
1505 # here: https://xlhost.de/faq/index_html?topicId=CQA2ELIPO4SQ
1507 url
= "https://nsupdate.xlhost.de/"
1510 class DDNSProviderZoneedit(DDNSProvider
):
1511 handle
= "zoneedit.com"
1513 website
= "http://www.zoneedit.com"
1514 protocols
= ("ipv4",)
1516 # Detailed information about the request and the response codes can be
1518 # http://www.zoneedit.com/doc/api/other.html
1519 # http://www.zoneedit.com/faq.html
1521 url
= "https://dynamic.zoneedit.com/auth/dynamic.html"
1523 def update_protocol(self
, proto
):
1525 "dnsto" : self
.get_address(proto
),
1526 "host" : self
.hostname
1529 # Send update to the server.
1530 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
1533 # Get the full response message.
1534 output
= response
.read()
1536 # Handle success messages.
1537 if output
.startswith("<SUCCESS"):
1540 # Handle error codes.
1541 if output
.startswith("invalid login"):
1542 raise DDNSAuthenticationError
1543 elif output
.startswith("<ERROR CODE=\"704\""):
1544 raise DDNSRequestError(_("No valid FQDN was given."))
1545 elif output
.startswith("<ERROR CODE=\"702\""):
1546 raise DDNSInternalServerError
1548 # If we got here, some other update error happened.
1549 raise DDNSUpdateError
1552 class DDNSProviderZZZZ(DDNSProvider
):
1555 website
= "https://zzzz.io"
1556 protocols
= ("ipv6", "ipv4",)
1558 # Detailed information about the update request can be found here:
1559 # https://zzzz.io/faq/
1561 # Details about the possible response codes have been provided in the bugtracker:
1562 # https://bugzilla.ipfire.org/show_bug.cgi?id=10584#c2
1564 url
= "https://zzzz.io/api/v1/update"
1565 can_remove_records
= False
1567 def update_protocol(self
, proto
):
1569 "ip" : self
.get_address(proto
),
1570 "token" : self
.token
,
1574 data
["type"] = "aaaa"
1576 # zzzz uses the host from the full hostname as part
1577 # of the update url.
1578 host
, domain
= self
.hostname
.split(".", 1)
1580 # Add host value to the update url.
1581 url
= "%s/%s" % (self
.url
, host
)
1583 # Send update to the server.
1585 response
= self
.send_request(url
, data
=data
)
1587 # Handle error codes.
1588 except DDNSNotFound
:
1589 raise DDNSRequestError(_("Invalid hostname specified"))
1591 # Handle success messages.
1592 if response
.code
== 200:
1595 # If we got here, some other update error happened.
1596 raise DDNSUpdateError