]>
git.ipfire.org Git - 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 # In case of any errors, log the failed request and
165 # raise the exception.
166 except DDNSError
as e
:
167 self
.core
.db
.log_failure(self
.hostname
, e
)
170 logger
.info(_("Dynamic DNS update for %(hostname)s (%(provider)s) successful") % \
171 { "hostname" : self
.hostname
, "provider" : self
.name
})
172 self
.core
.db
.log_success(self
.hostname
)
175 for protocol
in self
.protocols
:
176 if self
.have_address(protocol
):
177 self
.update_protocol(protocol
)
178 elif self
.can_remove_records
:
179 self
.remove_protocol(protocol
)
181 def update_protocol(self
, proto
):
182 raise NotImplementedError
184 def remove_protocol(self
, proto
):
185 if not self
.can_remove_records
:
186 raise RuntimeError, "can_remove_records is enabled, but remove_protocol() not implemented"
188 raise NotImplementedError
191 def requires_update(self
):
192 # If the IP addresses have changed, an update is required
193 if self
.ip_address_changed(self
.protocols
):
194 logger
.debug(_("An update for %(hostname)s (%(provider)s)"
195 " is performed because of an IP address change") % \
196 { "hostname" : self
.hostname
, "provider" : self
.name
})
200 # If the holdoff time has expired, an update is required, too
201 if self
.holdoff_time_expired():
202 logger
.debug(_("An update for %(hostname)s (%(provider)s)"
203 " is performed because the holdoff time has expired") % \
204 { "hostname" : self
.hostname
, "provider" : self
.name
})
208 # Otherwise, we don't need to perform an update
209 logger
.debug(_("No update required for %(hostname)s (%(provider)s)") % \
210 { "hostname" : self
.hostname
, "provider" : self
.name
})
215 def has_failure(self
):
217 Returns True when the last update has failed and no retry
218 should be performed, yet.
220 last_status
= self
.db
.last_update_status(self
.hostname
)
222 # Return False if the last update has not failed.
223 if not last_status
== "failure":
226 # If there is no holdoff time, we won't update ever again.
227 if self
.holdoff_failure_days
is None:
228 logger
.warning(_("An update has not been performed because earlier updates failed for %s") \
230 logger
.warning(_("There will be no retries"))
234 # Determine when the holdoff time ends
235 last_update
= self
.db
.last_update(self
.hostname
, status
=last_status
)
236 holdoff_end
= last_update
+ datetime
.timedelta(days
=self
.holdoff_failure_days
)
238 now
= datetime
.datetime
.utcnow()
239 if now
< holdoff_end
:
240 failure_message
= self
.db
.last_update_failure_message(self
.hostname
)
242 logger
.warning(_("An update has not been performed because earlier updates failed for %s") \
246 logger
.warning(_("Last failure message:"))
248 for line
in failure_message
.splitlines():
249 logger
.warning(" %s" % line
)
251 logger
.warning(_("Further updates will be withheld until %s") % holdoff_end
)
257 def ip_address_changed(self
, protos
):
259 Returns True if this host is already up to date
260 and does not need to change the IP address on the
264 addresses
= self
.core
.system
.resolve(self
.hostname
, proto
)
265 current_address
= self
.get_address(proto
)
267 # Handle if the system has not got any IP address from a protocol
268 # (i.e. had full dual-stack connectivity which it has not any more)
269 if current_address
is None:
270 # If addresses still exists in the DNS system and if this provider
271 # is able to remove records, we will do that.
272 if addresses
and self
.can_remove_records
:
275 # Otherwise, we cannot go on...
278 if not current_address
in addresses
:
283 def holdoff_time_expired(self
):
285 Returns true if the holdoff time has expired
286 and the host requires an update
288 # If no holdoff days is defined, we cannot go on
289 if not self
.holdoff_days
:
292 # Get the timestamp of the last successfull update
293 last_update
= self
.db
.last_update(self
.hostname
, status
="success")
295 # If no timestamp has been recorded, no update has been
296 # performed. An update should be performed now.
300 # Determine when the holdoff time ends
301 holdoff_end
= last_update
+ datetime
.timedelta(days
=self
.holdoff_days
)
303 now
= datetime
.datetime
.utcnow()
305 if now
>= holdoff_end
:
306 logger
.debug("The holdoff time has expired for %s" % self
.hostname
)
309 logger
.debug("Updates for %s are held off until %s" % \
310 (self
.hostname
, holdoff_end
))
313 def send_request(self
, *args
, **kwargs
):
315 Proxy connection to the send request
318 return self
.core
.system
.send_request(*args
, **kwargs
)
320 def get_address(self
, proto
, default
=None):
322 Proxy method to get the current IP address.
324 return self
.core
.system
.get_address(proto
) or default
326 def have_address(self
, proto
):
328 Returns True if an IP address for the given protocol
331 address
= self
.get_address(proto
)
339 class DDNSProtocolDynDNS2(object):
341 This is an abstract class that implements the DynDNS updater
342 protocol version 2. As this is a popular way to update dynamic
343 DNS records, this class is supposed make the provider classes
347 # Information about the format of the request is to be found
348 # http://dyn.com/support/developers/api/perform-update/
349 # http://dyn.com/support/developers/api/return-codes/
351 # The DynDNS protocol version 2 does not allow to remove records
352 can_remove_records
= False
354 def prepare_request_data(self
, proto
):
356 "hostname" : self
.hostname
,
357 "myip" : self
.get_address(proto
),
362 def update_protocol(self
, proto
):
363 data
= self
.prepare_request_data(proto
)
365 return self
.send_request(data
)
367 def send_request(self
, data
):
368 # Send update to the server.
369 response
= DDNSProvider
.send_request(self
, self
.url
, data
=data
,
370 username
=self
.username
, password
=self
.password
)
372 # Get the full response message.
373 output
= response
.read()
375 # Handle success messages.
376 if output
.startswith("good") or output
.startswith("nochg"):
379 # Handle error codes.
380 if output
== "badauth":
381 raise DDNSAuthenticationError
382 elif output
== "abuse":
384 elif output
== "notfqdn":
385 raise DDNSRequestError(_("No valid FQDN was given."))
386 elif output
== "nohost":
387 raise DDNSRequestError(_("Specified host does not exist."))
388 elif output
== "911":
389 raise DDNSInternalServerError
390 elif output
== "dnserr":
391 raise DDNSInternalServerError(_("DNS error encountered."))
392 elif output
== "badagent":
393 raise DDNSBlockedError
395 # If we got here, some other update error happened.
396 raise DDNSUpdateError(_("Server response: %s") % output
)
399 class DDNSResponseParserXML(object):
401 This class provides a parser for XML responses which
402 will be sent by various providers. This class uses the python
403 shipped XML minidom module to walk through the XML tree and return
407 def get_xml_tag_value(self
, document
, content
):
408 # Send input to the parser.
409 xmldoc
= xml
.dom
.minidom
.parseString(document
)
411 # Get XML elements by the given content.
412 element
= xmldoc
.getElementsByTagName(content
)
414 # If no element has been found, we directly can return None.
418 # Only get the first child from an element, even there are more than one.
419 firstchild
= element
[0].firstChild
421 # Get the value of the child.
422 value
= firstchild
.nodeValue
428 class DDNSProviderAllInkl(DDNSProvider
):
429 handle
= "all-inkl.com"
430 name
= "All-inkl.com"
431 website
= "http://all-inkl.com/"
432 protocols
= ("ipv4",)
434 # There are only information provided by the vendor how to
435 # perform an update on a FRITZ Box. Grab requried informations
437 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
439 url
= "http://dyndns.kasserver.com"
440 can_remove_records
= False
443 # There is no additional data required so we directly can
445 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
)
447 # Get the full response message.
448 output
= response
.read()
450 # Handle success messages.
451 if output
.startswith("good") or output
.startswith("nochg"):
454 # If we got here, some other update error happened.
455 raise DDNSUpdateError
458 class DDNSProviderBindNsupdate(DDNSProvider
):
460 name
= "BIND nsupdate utility"
461 website
= "http://en.wikipedia.org/wiki/Nsupdate"
467 # Search if the nsupdate utility is available
468 paths
= os
.environ
.get("PATH")
470 for path
in paths
.split(":"):
471 executable
= os
.path
.join(path
, "nsupdate")
473 if os
.path
.exists(executable
):
479 scriptlet
= self
.__make
_scriptlet
()
481 # -v enables TCP hence we transfer keys and other data that may
482 # exceed the size of one packet.
483 # -t sets the timeout
484 command
= ["nsupdate", "-v", "-t", "60"]
486 p
= subprocess
.Popen(command
, shell
=True,
487 stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
489 stdout
, stderr
= p
.communicate(scriptlet
)
491 if p
.returncode
== 0:
494 raise DDNSError("nsupdate terminated with error code: %s\n %s" % (p
.returncode
, stderr
))
496 def __make_scriptlet(self
):
499 # Set a different server the update is sent to.
500 server
= self
.get("server", None)
502 scriptlet
.append("server %s" % server
)
504 # Set the DNS zone the host should be added to.
505 zone
= self
.get("zone", None)
507 scriptlet
.append("zone %s" % zone
)
509 key
= self
.get("key", None)
511 secret
= self
.get("secret")
513 scriptlet
.append("key %s %s" % (key
, secret
))
515 ttl
= self
.get("ttl", self
.DEFAULT_TTL
)
517 # Perform an update for each supported protocol.
518 for rrtype
, proto
in (("AAAA", "ipv6"), ("A", "ipv4")):
519 address
= self
.get_address(proto
)
523 scriptlet
.append("update delete %s. %s" % (self
.hostname
, rrtype
))
524 scriptlet
.append("update add %s. %s %s %s" % \
525 (self
.hostname
, ttl
, rrtype
, address
))
527 # Send the actions to the server.
528 scriptlet
.append("send")
529 scriptlet
.append("quit")
531 logger
.debug(_("Scriptlet:"))
532 for line
in scriptlet
:
533 # Masquerade the line with the secret key.
534 if line
.startswith("key"):
535 line
= "key **** ****"
537 logger
.debug(" %s" % line
)
539 return "\n".join(scriptlet
)
542 class DDNSProviderChangeIP(DDNSProvider
):
543 handle
= "changeip.com"
544 name
= "ChangeIP.com"
545 website
= "https://changeip.com"
546 protocols
= ("ipv4",)
548 # Detailed information about the update api can be found here.
549 # http://www.changeip.com/accounts/knowledgebase.php?action=displayarticle&id=34
551 url
= "https://nic.changeip.com/nic/update"
552 can_remove_records
= False
554 def update_protocol(self
, proto
):
556 "hostname" : self
.hostname
,
557 "myip" : self
.get_address(proto
),
560 # Send update to the server.
562 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
565 # Handle error codes.
566 except urllib2
.HTTPError
, e
:
568 raise DDNSRequestError(_("Domain not found."))
572 # Handle success message.
573 if response
.code
== 200:
576 # If we got here, some other update error happened.
577 raise DDNSUpdateError(_("Server response: %s") % output
)
580 class DDNSProviderDHS(DDNSProvider
):
582 name
= "DHS International"
583 website
= "http://dhs.org/"
584 protocols
= ("ipv4",)
586 # No information about the used update api provided on webpage,
587 # grabed from source code of ez-ipudate.
589 url
= "http://members.dhs.org/nic/hosts"
590 can_remove_records
= False
592 def update_protocol(self
, proto
):
594 "domain" : self
.hostname
,
595 "ip" : self
.get_address(proto
),
597 "hostcmdstage" : "2",
601 # Send update to the server.
602 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
605 # Handle success messages.
606 if response
.code
== 200:
609 # If we got here, some other update error happened.
610 raise DDNSUpdateError
613 class DDNSProviderDNSpark(DDNSProvider
):
614 handle
= "dnspark.com"
616 website
= "http://dnspark.com/"
617 protocols
= ("ipv4",)
619 # Informations to the used api can be found here:
620 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
622 url
= "https://control.dnspark.com/api/dynamic/update.php"
623 can_remove_records
= False
625 def update_protocol(self
, proto
):
627 "domain" : self
.hostname
,
628 "ip" : self
.get_address(proto
),
631 # Send update to the server.
632 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
635 # Get the full response message.
636 output
= response
.read()
638 # Handle success messages.
639 if output
.startswith("ok") or output
.startswith("nochange"):
642 # Handle error codes.
643 if output
== "unauth":
644 raise DDNSAuthenticationError
645 elif output
== "abuse":
647 elif output
== "blocked":
648 raise DDNSBlockedError
649 elif output
== "nofqdn":
650 raise DDNSRequestError(_("No valid FQDN was given."))
651 elif output
== "nohost":
652 raise DDNSRequestError(_("Invalid hostname specified."))
653 elif output
== "notdyn":
654 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
655 elif output
== "invalid":
656 raise DDNSRequestError(_("Invalid IP address has been sent."))
658 # If we got here, some other update error happened.
659 raise DDNSUpdateError
662 class DDNSProviderDtDNS(DDNSProvider
):
665 website
= "http://dtdns.com/"
666 protocols
= ("ipv4",)
668 # Information about the format of the HTTPS request is to be found
669 # http://www.dtdns.com/dtsite/updatespec
671 url
= "https://www.dtdns.com/api/autodns.cfm"
672 can_remove_records
= False
674 def update_protocol(self
, proto
):
676 "ip" : self
.get_address(proto
),
677 "id" : self
.hostname
,
681 # Send update to the server.
682 response
= self
.send_request(self
.url
, data
=data
)
684 # Get the full response message.
685 output
= response
.read()
687 # Remove all leading and trailing whitespace.
688 output
= output
.strip()
690 # Handle success messages.
691 if "now points to" in output
:
694 # Handle error codes.
695 if output
== "No hostname to update was supplied.":
696 raise DDNSRequestError(_("No hostname specified."))
698 elif output
== "The hostname you supplied is not valid.":
699 raise DDNSRequestError(_("Invalid hostname specified."))
701 elif output
== "The password you supplied is not valid.":
702 raise DDNSAuthenticationError
704 elif output
== "Administration has disabled this account.":
705 raise DDNSRequestError(_("Account has been disabled."))
707 elif output
== "Illegal character in IP.":
708 raise DDNSRequestError(_("Invalid IP address has been sent."))
710 elif output
== "Too many failed requests.":
711 raise DDNSRequestError(_("Too many failed requests."))
713 # If we got here, some other update error happened.
714 raise DDNSUpdateError
717 class DDNSProviderDynDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
718 handle
= "dyndns.org"
720 website
= "http://dyn.com/dns/"
721 protocols
= ("ipv4",)
723 # Information about the format of the request is to be found
724 # http://http://dyn.com/support/developers/api/perform-update/
725 # http://dyn.com/support/developers/api/return-codes/
727 url
= "https://members.dyndns.org/nic/update"
730 class DDNSProviderDynU(DDNSProtocolDynDNS2
, DDNSProvider
):
733 website
= "http://dynu.com/"
734 protocols
= ("ipv6", "ipv4",)
736 # Detailed information about the request and response codes
737 # are available on the providers webpage.
738 # http://dynu.com/Default.aspx?page=dnsapi
740 url
= "https://api.dynu.com/nic/update"
742 # DynU sends the IPv6 and IPv4 address in one request
745 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, "ipv4")
747 # This one supports IPv6
748 myipv6
= self
.get_address("ipv6")
750 # Add update information if we have an IPv6 address.
752 data
["myipv6"] = myipv6
754 self
.send_request(data
)
757 class DDNSProviderEasyDNS(DDNSProvider
):
758 handle
= "easydns.com"
760 website
= "http://www.easydns.com/"
761 protocols
= ("ipv4",)
763 # Detailed information about the request and response codes
764 # (API 1.3) are available on the providers webpage.
765 # https://fusion.easydns.com/index.php?/Knowledgebase/Article/View/102/7/dynamic-dns
767 url
= "http://api.cp.easydns.com/dyn/tomato.php"
769 def update_protocol(self
, proto
):
771 "myip" : self
.get_address(proto
, "-"),
772 "hostname" : self
.hostname
,
775 # Send update to the server.
776 response
= self
.send_request(self
.url
, data
=data
,
777 username
=self
.username
, password
=self
.password
)
779 # Get the full response message.
780 output
= response
.read()
782 # Remove all leading and trailing whitespace.
783 output
= output
.strip()
785 # Handle success messages.
786 if output
.startswith("NOERROR"):
789 # Handle error codes.
790 if output
.startswith("NOACCESS"):
791 raise DDNSAuthenticationError
793 elif output
.startswith("NOSERVICE"):
794 raise DDNSRequestError(_("Dynamic DNS is not turned on for this domain."))
796 elif output
.startswith("ILLEGAL INPUT"):
797 raise DDNSRequestError(_("Invalid data has been sent."))
799 elif output
.startswith("TOOSOON"):
800 raise DDNSRequestError(_("Too frequent update requests have been sent."))
802 # If we got here, some other update error happened.
803 raise DDNSUpdateError
806 class DDNSProviderDomopoli(DDNSProtocolDynDNS2
, DDNSProvider
):
807 handle
= "domopoli.de"
809 website
= "http://domopoli.de/"
810 protocols
= ("ipv4",)
812 # https://www.domopoli.de/?page=howto#DynDns_start
814 url
= "http://dyndns.domopoli.de/nic/update"
817 class DDNSProviderDynsNet(DDNSProvider
):
820 website
= "http://www.dyns.net/"
821 protocols
= ("ipv4",)
822 can_remove_records
= False
824 # There is very detailed informatio about how to send the update request and
825 # the possible response codes. (Currently we are using the v1.1 proto)
826 # http://www.dyns.net/documentation/technical/protocol/
828 url
= "http://www.dyns.net/postscript011.php"
830 def update_protocol(self
, proto
):
832 "ip" : self
.get_address(proto
),
833 "host" : self
.hostname
,
834 "username" : self
.username
,
835 "password" : self
.password
,
838 # Send update to the server.
839 response
= self
.send_request(self
.url
, data
=data
)
841 # Get the full response message.
842 output
= response
.read()
844 # Handle success messages.
845 if output
.startswith("200"):
848 # Handle error codes.
849 if output
.startswith("400"):
850 raise DDNSRequestError(_("Malformed request has been sent."))
851 elif output
.startswith("401"):
852 raise DDNSAuthenticationError
853 elif output
.startswith("402"):
854 raise DDNSRequestError(_("Too frequent update requests have been sent."))
855 elif output
.startswith("403"):
856 raise DDNSInternalServerError
858 # If we got here, some other update error happened.
859 raise DDNSUpdateError(_("Server response: %s") % output
)
862 class DDNSProviderEnomCom(DDNSResponseParserXML
, DDNSProvider
):
865 website
= "http://www.enom.com/"
866 protocols
= ("ipv4",)
868 # There are very detailed information about how to send an update request and
870 # http://www.enom.com/APICommandCatalog/
872 url
= "https://dynamic.name-services.com/interface.asp"
873 can_remove_records
= False
875 def update_protocol(self
, proto
):
877 "command" : "setdnshost",
878 "responsetype" : "xml",
879 "address" : self
.get_address(proto
),
880 "domainpassword" : self
.password
,
881 "zone" : self
.hostname
884 # Send update to the server.
885 response
= self
.send_request(self
.url
, data
=data
)
887 # Get the full response message.
888 output
= response
.read()
890 # Handle success messages.
891 if self
.get_xml_tag_value(output
, "ErrCount") == "0":
894 # Handle error codes.
895 errorcode
= self
.get_xml_tag_value(output
, "ResponseNumber")
897 if errorcode
== "304155":
898 raise DDNSAuthenticationError
899 elif errorcode
== "304153":
900 raise DDNSRequestError(_("Domain not found."))
902 # If we got here, some other update error happened.
903 raise DDNSUpdateError
906 class DDNSProviderEntryDNS(DDNSProvider
):
907 handle
= "entrydns.net"
909 website
= "http://entrydns.net/"
910 protocols
= ("ipv4",)
912 # Some very tiny details about their so called "Simple API" can be found
913 # here: https://entrydns.net/help
914 url
= "https://entrydns.net/records/modify"
915 can_remove_records
= False
917 def update_protocol(self
, proto
):
919 "ip" : self
.get_address(proto
),
922 # Add auth token to the update url.
923 url
= "%s/%s" % (self
.url
, self
.token
)
925 # Send update to the server.
927 response
= self
.send_request(url
, data
=data
)
930 except urllib2
.HTTPError
, e
:
932 raise DDNSAuthenticationError
935 raise DDNSRequestError(_("An invalid IP address was submitted"))
939 # Handle success messages.
940 if response
.code
== 200:
943 # If we got here, some other update error happened.
944 raise DDNSUpdateError
947 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider
):
948 handle
= "freedns.afraid.org"
949 name
= "freedns.afraid.org"
950 website
= "http://freedns.afraid.org/"
952 # No information about the request or response could be found on the vendor
953 # page. All used values have been collected by testing.
954 url
= "https://freedns.afraid.org/dynamic/update.php"
955 can_remove_records
= False
957 def update_protocol(self
, proto
):
959 "address" : self
.get_address(proto
),
962 # Add auth token to the update url.
963 url
= "%s?%s" % (self
.url
, self
.token
)
965 # Send update to the server.
966 response
= self
.send_request(url
, data
=data
)
968 # Get the full response message.
969 output
= response
.read()
971 # Handle success messages.
972 if output
.startswith("Updated") or "has not changed" in output
:
975 # Handle error codes.
976 if output
== "ERROR: Unable to locate this record":
977 raise DDNSAuthenticationError
978 elif "is an invalid IP address" in output
:
979 raise DDNSRequestError(_("Invalid IP address has been sent."))
981 # If we got here, some other update error happened.
982 raise DDNSUpdateError
985 class DDNSProviderLightningWireLabs(DDNSProvider
):
986 handle
= "dns.lightningwirelabs.com"
987 name
= "Lightning Wire Labs DNS Service"
988 website
= "http://dns.lightningwirelabs.com/"
990 # Information about the format of the HTTPS request is to be found
991 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
993 url
= "https://dns.lightningwirelabs.com/update"
997 "hostname" : self
.hostname
,
998 "address6" : self
.get_address("ipv6", "-"),
999 "address4" : self
.get_address("ipv4", "-"),
1002 # Check if a token has been set.
1004 data
["token"] = self
.token
1006 # Check for username and password.
1007 elif self
.username
and self
.password
:
1009 "username" : self
.username
,
1010 "password" : self
.password
,
1013 # Raise an error if no auth details are given.
1015 raise DDNSConfigurationError
1017 # Send update to the server.
1018 response
= self
.send_request(self
.url
, data
=data
)
1020 # Handle success messages.
1021 if response
.code
== 200:
1024 # If we got here, some other update error happened.
1025 raise DDNSUpdateError
1028 class DDNSProviderMyOnlinePortal(DDNSProtocolDynDNS2
, DDNSProvider
):
1029 handle
= "myonlineportal.net"
1030 name
= "myonlineportal.net"
1031 website
= "https:/myonlineportal.net/"
1033 # Information about the request and response can be obtained here:
1034 # https://myonlineportal.net/howto_dyndns
1036 url
= "https://myonlineportal.net/updateddns"
1038 def prepare_request_data(self
, proto
):
1040 "hostname" : self
.hostname
,
1041 "ip" : self
.get_address(proto
),
1047 class DDNSProviderNamecheap(DDNSResponseParserXML
, DDNSProvider
):
1048 handle
= "namecheap.com"
1050 website
= "http://namecheap.com"
1051 protocols
= ("ipv4",)
1053 # Information about the format of the HTTP request is to be found
1054 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
1055 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
1057 url
= "https://dynamicdns.park-your-domain.com/update"
1058 can_remove_records
= False
1060 def update_protocol(self
, proto
):
1061 # Namecheap requires the hostname splitted into a host and domain part.
1062 host
, domain
= self
.hostname
.split(".", 1)
1065 "ip" : self
.get_address(proto
),
1066 "password" : self
.password
,
1071 # Send update to the server.
1072 response
= self
.send_request(self
.url
, data
=data
)
1074 # Get the full response message.
1075 output
= response
.read()
1077 # Handle success messages.
1078 if self
.get_xml_tag_value(output
, "IP") == address
:
1081 # Handle error codes.
1082 errorcode
= self
.get_xml_tag_value(output
, "ResponseNumber")
1084 if errorcode
== "304156":
1085 raise DDNSAuthenticationError
1086 elif errorcode
== "316153":
1087 raise DDNSRequestError(_("Domain not found."))
1088 elif errorcode
== "316154":
1089 raise DDNSRequestError(_("Domain not active."))
1090 elif errorcode
in ("380098", "380099"):
1091 raise DDNSInternalServerError
1093 # If we got here, some other update error happened.
1094 raise DDNSUpdateError
1097 class DDNSProviderNOIP(DDNSProtocolDynDNS2
, DDNSProvider
):
1098 handle
= "no-ip.com"
1100 website
= "http://www.no-ip.com/"
1101 protocols
= ("ipv4",)
1103 # Information about the format of the HTTP request is to be found
1104 # here: http://www.no-ip.com/integrate/request and
1105 # here: http://www.no-ip.com/integrate/response
1107 url
= "http://dynupdate.no-ip.com/nic/update"
1109 def prepare_request_data(self
, proto
):
1110 assert proto
== "ipv4"
1113 "hostname" : self
.hostname
,
1114 "address" : self
.get_address(proto
),
1120 class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2
, DDNSProvider
):
1121 handle
= "nsupdate.info"
1122 name
= "nsupdate.info"
1123 website
= "http://nsupdate.info/"
1124 protocols
= ("ipv6", "ipv4",)
1126 # Information about the format of the HTTP request can be found
1127 # after login on the provider user interface and here:
1128 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
1130 url
= "https://nsupdate.info/nic/update"
1132 # TODO nsupdate.info can actually do this, but the functionality
1133 # has not been implemented here, yet.
1134 can_remove_records
= False
1136 # After a failed update, there will be no retries
1137 # https://bugzilla.ipfire.org/show_bug.cgi?id=10603
1138 holdoff_failure_days
= None
1140 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
1141 # and for the password a so called secret.
1144 return self
.get("hostname")
1148 return self
.token
or self
.get("secret")
1150 def prepare_request_data(self
, proto
):
1152 "myip" : self
.get_address(proto
),
1158 class DDNSProviderOpenDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1159 handle
= "opendns.com"
1161 website
= "http://www.opendns.com"
1163 # Detailed information about the update request and possible
1164 # response codes can be obtained from here:
1165 # https://support.opendns.com/entries/23891440
1167 url
= "https://updates.opendns.com/nic/update"
1169 def prepare_request_data(self
, proto
):
1171 "hostname" : self
.hostname
,
1172 "myip" : self
.get_address(proto
),
1178 class DDNSProviderOVH(DDNSProtocolDynDNS2
, DDNSProvider
):
1181 website
= "http://www.ovh.com/"
1182 protocols
= ("ipv4",)
1184 # OVH only provides very limited information about how to
1185 # update a DynDNS host. They only provide the update url
1186 # on the their german subpage.
1188 # http://hilfe.ovh.de/DomainDynHost
1190 url
= "https://www.ovh.com/nic/update"
1192 def prepare_request_data(self
, proto
):
1193 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1195 "system" : "dyndns",
1201 class DDNSProviderRegfish(DDNSProvider
):
1202 handle
= "regfish.com"
1203 name
= "Regfish GmbH"
1204 website
= "http://www.regfish.com/"
1206 # A full documentation to the providers api can be found here
1207 # but is only available in german.
1208 # https://www.regfish.de/domains/dyndns/dokumentation
1210 url
= "https://dyndns.regfish.de/"
1211 can_remove_records
= False
1215 "fqdn" : self
.hostname
,
1218 # Check if we update an IPv6 address.
1219 address6
= self
.get_address("ipv6")
1221 data
["ipv6"] = address6
1223 # Check if we update an IPv4 address.
1224 address4
= self
.get_address("ipv4")
1226 data
["ipv4"] = address4
1228 # Raise an error if none address is given.
1229 if not data
.has_key("ipv6") and not data
.has_key("ipv4"):
1230 raise DDNSConfigurationError
1232 # Check if a token has been set.
1234 data
["token"] = self
.token
1236 # Raise an error if no token and no useranem and password
1238 elif not self
.username
and not self
.password
:
1239 raise DDNSConfigurationError(_("No Auth details specified."))
1241 # HTTP Basic Auth is only allowed if no token is used.
1243 # Send update to the server.
1244 response
= self
.send_request(self
.url
, data
=data
)
1246 # Send update to the server.
1247 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
1250 # Get the full response message.
1251 output
= response
.read()
1253 # Handle success messages.
1254 if "100" in output
or "101" in output
:
1257 # Handle error codes.
1258 if "401" or "402" in output
:
1259 raise DDNSAuthenticationError
1260 elif "408" in output
:
1261 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
1262 elif "409" in output
:
1263 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
1264 elif "412" in output
:
1265 raise DDNSRequestError(_("No valid FQDN was given."))
1266 elif "414" in output
:
1267 raise DDNSInternalServerError
1269 # If we got here, some other update error happened.
1270 raise DDNSUpdateError
1273 class DDNSProviderSelfhost(DDNSProtocolDynDNS2
, DDNSProvider
):
1274 handle
= "selfhost.de"
1275 name
= "Selfhost.de"
1276 website
= "http://www.selfhost.de/"
1277 protocols
= ("ipv4",)
1279 url
= "https://carol.selfhost.de/nic/update"
1281 def prepare_request_data(self
, proto
):
1282 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1290 class DDNSProviderSPDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1291 handle
= "spdns.org"
1293 website
= "http://spdns.org/"
1295 # Detailed information about request and response codes are provided
1296 # by the vendor. They are using almost the same mechanism and status
1297 # codes as dyndns.org so we can inherit all those stuff.
1299 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
1300 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
1302 url
= "https://update.spdns.de/nic/update"
1306 return self
.get("username") or self
.hostname
1310 return self
.get("password") or self
.token
1313 class DDNSProviderStrato(DDNSProtocolDynDNS2
, DDNSProvider
):
1314 handle
= "strato.com"
1316 website
= "http:/www.strato.com/"
1317 protocols
= ("ipv4",)
1319 # Information about the request and response can be obtained here:
1320 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
1322 url
= "https://dyndns.strato.com/nic/update"
1325 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1326 handle
= "twodns.de"
1328 website
= "http://www.twodns.de"
1329 protocols
= ("ipv4",)
1331 # Detailed information about the request can be found here
1332 # http://twodns.de/en/faqs
1333 # http://twodns.de/en/api
1335 url
= "https://update.twodns.de/update"
1337 def prepare_request_data(self
, proto
):
1338 assert proto
== "ipv4"
1341 "ip" : self
.get_address(proto
),
1342 "hostname" : self
.hostname
1348 class DDNSProviderUdmedia(DDNSProtocolDynDNS2
, DDNSProvider
):
1349 handle
= "udmedia.de"
1350 name
= "Udmedia GmbH"
1351 website
= "http://www.udmedia.de"
1352 protocols
= ("ipv4",)
1354 # Information about the request can be found here
1355 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1357 url
= "https://www.udmedia.de/nic/update"
1360 class DDNSProviderVariomedia(DDNSProtocolDynDNS2
, DDNSProvider
):
1361 handle
= "variomedia.de"
1363 website
= "http://www.variomedia.de/"
1364 protocols
= ("ipv6", "ipv4",)
1366 # Detailed information about the request can be found here
1367 # https://dyndns.variomedia.de/
1369 url
= "https://dyndns.variomedia.de/nic/update"
1371 def prepare_request_data(self
, proto
):
1373 "hostname" : self
.hostname
,
1374 "myip" : self
.get_address(proto
),
1380 class DDNSProviderZoneedit(DDNSProvider
):
1381 handle
= "zoneedit.com"
1383 website
= "http://www.zoneedit.com"
1384 protocols
= ("ipv4",)
1386 # Detailed information about the request and the response codes can be
1388 # http://www.zoneedit.com/doc/api/other.html
1389 # http://www.zoneedit.com/faq.html
1391 url
= "https://dynamic.zoneedit.com/auth/dynamic.html"
1393 def update_protocol(self
, proto
):
1395 "dnsto" : self
.get_address(proto
),
1396 "host" : self
.hostname
1399 # Send update to the server.
1400 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
1403 # Get the full response message.
1404 output
= response
.read()
1406 # Handle success messages.
1407 if output
.startswith("<SUCCESS"):
1410 # Handle error codes.
1411 if output
.startswith("invalid login"):
1412 raise DDNSAuthenticationError
1413 elif output
.startswith("<ERROR CODE=\"704\""):
1414 raise DDNSRequestError(_("No valid FQDN was given."))
1415 elif output
.startswith("<ERROR CODE=\"702\""):
1416 raise DDNSInternalServerError
1418 # If we got here, some other update error happened.
1419 raise DDNSUpdateError
1422 class DDNSProviderZZZZ(DDNSProvider
):
1425 website
= "https://zzzz.io"
1426 protocols
= ("ipv6", "ipv4",)
1428 # Detailed information about the update request can be found here:
1429 # https://zzzz.io/faq/
1431 # Details about the possible response codes have been provided in the bugtracker:
1432 # https://bugzilla.ipfire.org/show_bug.cgi?id=10584#c2
1434 url
= "https://zzzz.io/api/v1/update"
1435 can_remove_records
= False
1437 def update_protocol(self
, proto
):
1439 "ip" : self
.get_address(proto
),
1440 "token" : self
.token
,
1444 data
["type"] = "aaaa"
1446 # zzzz uses the host from the full hostname as part
1447 # of the update url.
1448 host
, domain
= self
.hostname
.split(".", 1)
1450 # Add host value to the update url.
1451 url
= "%s/%s" % (self
.url
, host
)
1453 # Send update to the server.
1455 response
= self
.send_request(url
, data
=data
)
1457 # Handle error codes.
1458 except DDNSNotFound
:
1459 raise DDNSRequestError(_("Invalid hostname specified"))
1461 # Handle success messages.
1462 if response
.code
== 200:
1465 # If we got here, some other update error happened.
1466 raise DDNSUpdateError