]>
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-2017 IPFire development team #
7 # This program is free software: you can redistribute it and/or modify #
8 # it under the terms of the GNU General Public License as published by #
9 # the Free Software Foundation, either version 3 of the License, or #
10 # (at your option) any later version. #
12 # This program is distributed in the hope that it will be useful, #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15 # GNU General Public License for more details. #
17 # You should have received a copy of the GNU General Public License #
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
20 ###############################################################################
30 import xml
.dom
.minidom
34 # Import all possible exception types.
37 logger
= logging
.getLogger("ddns.providers")
44 Returns a dict with all automatically registered providers.
46 return _providers
.copy()
48 class DDNSProvider(object):
49 # A short string that uniquely identifies
53 # The full name of the provider.
56 # A weburl to the homepage of the provider.
57 # (Where to register a new account?)
60 # A list of supported protocols.
61 protocols
= ("ipv6", "ipv4")
65 # holdoff time - Number of days no update is performed unless
66 # the IP address has changed.
69 # holdoff time for update failures - Number of days no update
70 # is tried after the last one has failed.
71 holdoff_failure_days
= 0.5
73 # True if the provider is able to remove records, too.
74 # Required to remove AAAA records if IPv6 is absent again.
75 can_remove_records
= True
77 # True if the provider supports authentication via a random
78 # generated token instead of username and password.
79 supports_token_auth
= True
84 Should be overwritten to check if the system the code is running
85 on has all the required tools to support this provider.
89 def __init__(self
, core
, **settings
):
92 # Copy a set of default settings and
93 # update them by those from the configuration file.
94 self
.settings
= self
.DEFAULT_SETTINGS
.copy()
95 self
.settings
.update(settings
)
97 def __init_subclass__(cls
, **kwargs
):
98 super().__init
_subclass
__(**kwargs
)
100 if not all((cls
.handle
, cls
.name
, cls
.website
)):
101 raise DDNSError(_("Provider is not properly configured"))
103 assert cls
.handle
not in _providers
, \
104 "Provider '%s' has already been registered" % cls
.handle
107 _providers
[cls
.handle
] = cls
110 return "<DDNS Provider %s (%s)>" % (self
.name
, self
.handle
)
112 def __cmp__(self
, other
):
113 return (lambda a
, b
: (a
> b
)-(a
< b
))(self
.hostname
, other
.hostname
)
119 def get(self
, key
, default
=None):
121 Get a setting from the settings dictionary.
123 return self
.settings
.get(key
, default
)
128 Fast access to the hostname.
130 return self
.get("hostname")
135 Fast access to the username.
137 return self
.get("username")
142 Fast access to the password.
144 return self
.get("password")
149 Fast access to the token.
151 return self
.get("token")
153 def __call__(self
, force
=False):
155 logger
.debug(_("Updating %s forced") % self
.hostname
)
157 # Do nothing if the last update has failed or no update is required
158 elif self
.has_failure
or not self
.requires_update
:
161 # Execute the update.
165 # 1) Catch network errors early, because we do not want to log
166 # them to the database. They are usually temporary and caused
167 # by the client side, so that we will retry quickly.
168 # 2) If there is an internet server error (HTTP code 500) on the
169 # provider's site, we will not log a failure and try again
171 except (DDNSNetworkError
, DDNSInternalServerError
):
174 # In case of any errors, log the failed request and
175 # raise the exception.
176 except DDNSError
as e
:
177 self
.core
.db
.log_failure(self
.hostname
, e
)
180 logger
.info(_("Dynamic DNS update for %(hostname)s (%(provider)s) successful") %
181 {"hostname": self
.hostname
, "provider": self
.name
})
182 self
.core
.db
.log_success(self
.hostname
)
185 for protocol
in self
.protocols
:
186 if self
.have_address(protocol
):
187 self
.update_protocol(protocol
)
188 elif self
.can_remove_records
:
189 self
.remove_protocol(protocol
)
191 def update_protocol(self
, proto
):
192 raise NotImplementedError
194 def remove_protocol(self
, proto
):
195 if not self
.can_remove_records
:
196 raise RuntimeError("can_remove_records is enabled, but remove_protocol() not implemented")
198 raise NotImplementedError
201 def requires_update(self
):
202 # If the IP addresses have changed, an update is required
203 if self
.ip_address_changed(self
.protocols
):
204 logger
.debug(_("An update for %(hostname)s (%(provider)s) 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) is performed because the holdoff time has expired") %
212 {"hostname": self
.hostname
, "provider": self
.name
})
216 # Otherwise, we don't need to perform an update
217 logger
.debug(_("No update required for %(hostname)s (%(provider)s)") %
218 {"hostname": self
.hostname
, "provider": self
.name
})
223 def has_failure(self
):
225 Returns True when the last update has failed and no retry
226 should be performed, yet.
228 last_status
= self
.db
.last_update_status(self
.hostname
)
230 # Return False if the last update has not failed.
231 if not last_status
== "failure":
234 # If there is no holdoff time, we won't update ever again.
235 if self
.holdoff_failure_days
is None:
236 logger
.warning(_("An update has not been performed because earlier updates failed for %s") % self
.hostname
)
237 logger
.warning(_("There will be no retries"))
241 # Determine when the holdoff time ends
242 last_update
= self
.db
.last_update(self
.hostname
, status
=last_status
)
243 holdoff_end
= last_update
+ datetime
.timedelta(days
=self
.holdoff_failure_days
)
245 now
= datetime
.datetime
.utcnow()
246 if now
< holdoff_end
:
247 failure_message
= self
.db
.last_update_failure_message(self
.hostname
)
249 logger
.warning(_("An update has not been performed because earlier updates failed for %s") % self
.hostname
)
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 # The DynDNS protocol version 2 only supports authentication via
361 # username and password.
362 supports_token_auth
= False
364 def prepare_request_data(self
, proto
):
366 "hostname" : self
.hostname
,
367 "myip" : self
.get_address(proto
),
372 def update_protocol(self
, proto
):
373 data
= self
.prepare_request_data(proto
)
375 return self
.send_request(data
)
377 def send_request(self
, data
):
378 # Send update to the server.
379 response
= DDNSProvider
.send_request(self
, self
.url
, data
=data
, username
=self
.username
, password
=self
.password
)
381 # Get the full response message.
382 output
= response
.read().decode()
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
403 elif output
== "badip":
404 raise DDNSBlockedError
406 # If we got here, some other update error happened.
407 raise DDNSUpdateError(_("Server response: %s") % output
)
410 class DDNSResponseParserXML(object):
412 This class provides a parser for XML responses which
413 will be sent by various providers. This class uses the python
414 shipped XML minidom module to walk through the XML tree and return
418 def get_xml_tag_value(self
, document
, content
):
419 # Send input to the parser.
420 xmldoc
= xml
.dom
.minidom
.parseString(document
)
422 # Get XML elements by the given content.
423 element
= xmldoc
.getElementsByTagName(content
)
425 # If no element has been found, we directly can return None.
429 # Only get the first child from an element, even there are more than one.
430 firstchild
= element
[0].firstChild
432 # Get the value of the child.
433 value
= firstchild
.nodeValue
439 class DDNSProviderAllInkl(DDNSProvider
):
440 handle
= "all-inkl.com"
441 name
= "All-inkl.com"
442 website
= "http://all-inkl.com/"
443 protocols
= ("ipv4",)
445 # There are only information provided by the vendor how to
446 # perform an update on a FRITZ Box. Grab required information
448 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
450 url
= "https://dyndns.kasserver.com"
451 can_remove_records
= False
452 supports_token_auth
= False
455 # There is no additional data required so we directly can
457 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
)
459 # Get the full response message.
460 output
= response
.read().decode()
462 # Handle success messages.
463 if output
.startswith("good") or output
.startswith("nochg"):
466 # If we got here, some other update error happened.
467 raise DDNSUpdateError
470 class DDNSProviderBindNsupdate(DDNSProvider
):
472 name
= "BIND nsupdate utility"
473 website
= "http://en.wikipedia.org/wiki/Nsupdate"
477 supports_token_auth
= False
481 # Search if the nsupdate utility is available
482 paths
= os
.environ
.get("PATH")
484 for path
in paths
.split(":"):
485 executable
= os
.path
.join(path
, "nsupdate")
487 if os
.path
.exists(executable
):
493 scriptlet
= self
.__make
_scriptlet
()
495 # -v enables TCP hence we transfer keys and other data that may
496 # exceed the size of one packet.
497 # -t sets the timeout
498 command
= ["nsupdate", "-v", "-t", "60"]
500 p
= subprocess
.Popen(command
, shell
=True, stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
)
501 stdout
, stderr
= p
.communicate(scriptlet
)
503 if p
.returncode
== 0:
506 raise DDNSError("nsupdate terminated with error code: %s\n %s" % (p
.returncode
, stderr
))
508 def __make_scriptlet(self
):
511 # Set a different server the update is sent to.
512 server
= self
.get("server", None)
514 scriptlet
.append("server %s" % server
)
516 # Set the DNS zone the host should be added to.
517 zone
= self
.get("zone", None)
519 scriptlet
.append("zone %s" % zone
)
521 key
= self
.get("key", None)
523 secret
= self
.get("secret")
525 scriptlet
.append("key %s %s" % (key
, secret
))
527 ttl
= self
.get("ttl", self
.DEFAULT_TTL
)
529 # Perform an update for each supported protocol.
530 for rrtype
, proto
in (("AAAA", "ipv6"), ("A", "ipv4")):
531 address
= self
.get_address(proto
)
535 scriptlet
.append("update delete %s. %s" % (self
.hostname
, rrtype
))
536 scriptlet
.append("update add %s. %s %s %s" % \
537 (self
.hostname
, ttl
, rrtype
, address
))
539 # Send the actions to the server.
540 scriptlet
.append("send")
541 scriptlet
.append("quit")
543 logger
.debug(_("Scriptlet:"))
544 for line
in scriptlet
:
545 # Masquerade the line with the secret key.
546 if line
.startswith("key"):
547 line
= "key **** ****"
549 logger
.debug(" %s" % line
)
551 return "\n".join(scriptlet
).encode()
554 class DDNSProviderChangeIP(DDNSProvider
):
555 handle
= "changeip.com"
556 name
= "ChangeIP.com"
557 website
= "https://changeip.com"
558 protocols
= ("ipv4",)
560 # Detailed information about the update api can be found here.
561 # http://www.changeip.com/accounts/knowledgebase.php?action=displayarticle&id=34
563 url
= "https://nic.changeip.com/nic/update"
564 can_remove_records
= False
565 supports_token_auth
= False
567 def update_protocol(self
, proto
):
569 "hostname" : self
.hostname
,
570 "myip" : self
.get_address(proto
),
573 # Send update to the server.
575 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
, data
=data
)
577 # Handle error codes.
578 except urllib
.error
.HTTPError
as e
:
580 raise DDNSRequestError(_("Domain not found."))
584 # Handle success message.
585 if response
.code
== 200:
588 # If we got here, some other update error happened.
589 raise DDNSUpdateError(_("Server response: %s") % output
)
592 class DDNSProviderDesecIO(DDNSProtocolDynDNS2
, DDNSProvider
):
595 website
= "https://www.desec.io"
596 protocols
= ("ipv6", "ipv4",)
598 # ipv4 / ipv6 records are automatically removed when the update
599 # request originates from the respectively other protocol and no
600 # address is explicitly provided for the unused protocol.
602 url
= "https://update.dedyn.io"
604 # desec.io sends the IPv6 and IPv4 address in one request
607 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, "ipv4")
609 # This one supports IPv6
610 myipv6
= self
.get_address("ipv6")
612 # Add update information if we have an IPv6 address.
614 data
["myipv6"] = myipv6
616 self
.send_request(data
)
619 class DDNSProviderDDNSS(DDNSProvider
):
622 website
= "http://www.ddnss.de"
623 protocols
= ("ipv4",)
625 # Detailed information about how to send the update request and possible response
626 # codes can be obtained from here.
627 # http://www.ddnss.de/info.php
628 # http://www.megacomputing.de/2014/08/dyndns-service-response-time/#more-919
630 url
= "https://www.ddnss.de/upd.php"
631 can_remove_records
= False
632 supports_token_auth
= False
634 def update_protocol(self
, proto
):
636 "ip" : self
.get_address(proto
),
637 "host" : self
.hostname
,
640 # Check if a token has been set.
642 data
["key"] = self
.token
644 # Check if username and hostname are given.
645 elif self
.username
and self
.password
:
647 "user" : self
.username
,
648 "pwd" : self
.password
,
651 # Raise an error if no auth details are given.
653 raise DDNSConfigurationError
655 # Send update to the server.
656 response
= self
.send_request(self
.url
, data
=data
)
658 # This provider sends the response code as part of the header.
659 # Get status information from the header.
660 output
= response
.getheader('ddnss-response')
662 # Handle success messages.
663 if output
== "good" or output
== "nochg":
666 # Handle error codes.
667 if output
== "badauth":
668 raise DDNSAuthenticationError
669 elif output
== "notfqdn":
670 raise DDNSRequestError(_("No valid FQDN was given"))
671 elif output
== "nohost":
672 raise DDNSRequestError(_("Specified host does not exist"))
673 elif output
== "911":
674 raise DDNSInternalServerError
675 elif output
== "dnserr":
676 raise DDNSInternalServerError(_("DNS error encountered"))
677 elif output
== "disabled":
678 raise DDNSRequestError(_("Account disabled or locked"))
680 # If we got here, some other update error happened.
681 raise DDNSUpdateError
684 class DDNSProviderDHS(DDNSProvider
):
686 name
= "DHS International"
687 website
= "http://dhs.org/"
688 protocols
= ("ipv4",)
690 # No information about the used update api provided on webpage,
691 # grabed from source code of ez-ipudate.
693 # Provider currently does not support TLS 1.2.
694 url
= "https://members.dhs.org/nic/hosts"
695 can_remove_records
= False
696 supports_token_auth
= False
698 def update_protocol(self
, proto
):
700 "domain" : self
.hostname
,
701 "ip" : self
.get_address(proto
),
703 "hostcmdstage" : "2",
707 # Send update to the server.
708 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
, data
=data
)
710 # Handle success messages.
711 if response
.code
== 200:
714 # If we got here, some other update error happened.
715 raise DDNSUpdateError
718 class DDNSProviderDNSpark(DDNSProvider
):
719 handle
= "dnspark.com"
721 website
= "http://dnspark.com/"
722 protocols
= ("ipv4",)
724 # Informations to the used api can be found here:
725 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
727 url
= "https://control.dnspark.com/api/dynamic/update.php"
728 can_remove_records
= False
729 supports_token_auth
= False
731 def update_protocol(self
, proto
):
733 "domain" : self
.hostname
,
734 "ip" : self
.get_address(proto
),
737 # Send update to the server.
738 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
, data
=data
)
740 # Get the full response message.
741 output
= response
.read().decode()
743 # Handle success messages.
744 if output
.startswith("ok") or output
.startswith("nochange"):
747 # Handle error codes.
748 if output
== "unauth":
749 raise DDNSAuthenticationError
750 elif output
== "abuse":
752 elif output
== "blocked":
753 raise DDNSBlockedError
754 elif output
== "nofqdn":
755 raise DDNSRequestError(_("No valid FQDN was given"))
756 elif output
== "nohost":
757 raise DDNSRequestError(_("Invalid hostname specified"))
758 elif output
== "notdyn":
759 raise DDNSRequestError(_("Hostname not marked as a dynamic host"))
760 elif output
== "invalid":
761 raise DDNSRequestError(_("Invalid IP address has been sent"))
763 # If we got here, some other update error happened.
764 raise DDNSUpdateError
767 class DDNSProviderDtDNS(DDNSProvider
):
770 website
= "http://dtdns.com/"
771 protocols
= ("ipv4",)
773 # Information about the format of the HTTPS request is to be found
774 # http://www.dtdns.com/dtsite/updatespec
776 url
= "https://www.dtdns.com/api/autodns.cfm"
777 can_remove_records
= False
778 supports_token_auth
= False
780 def update_protocol(self
, proto
):
782 "ip" : self
.get_address(proto
),
783 "id" : self
.hostname
,
787 # Send update to the server.
788 response
= self
.send_request(self
.url
, data
=data
)
790 # Get the full response message.
791 output
= response
.read().decode()
793 # Remove all leading and trailing whitespace.
794 output
= output
.strip()
796 # Handle success messages.
797 if "now points to" in output
:
800 # Handle error codes.
801 if output
== "No hostname to update was supplied.":
802 raise DDNSRequestError(_("No hostname specified"))
804 elif output
== "The hostname you supplied is not valid.":
805 raise DDNSRequestError(_("Invalid hostname specified"))
807 elif output
== "The password you supplied is not valid.":
808 raise DDNSAuthenticationError
810 elif output
== "Administration has disabled this account.":
811 raise DDNSRequestError(_("Account has been disabled"))
813 elif output
== "Illegal character in IP.":
814 raise DDNSRequestError(_("Invalid IP address has been sent"))
816 elif output
== "Too many failed requests.":
817 raise DDNSRequestError(_("Too many failed requests"))
819 # If we got here, some other update error happened.
820 raise DDNSUpdateError
823 class DDNSProviderDuckDNS(DDNSProvider
):
824 handle
= "duckdns.org"
826 website
= "http://www.duckdns.org/"
827 protocols
= ("ipv6", "ipv4",)
829 # Information about the format of the request is to be found
830 # https://www.duckdns.org/spec.jsp
832 url
= "https://www.duckdns.org/update"
833 can_remove_records
= False
834 supports_token_auth
= True
837 # Raise an error if no auth details are given.
839 raise DDNSConfigurationError
842 "domains" : self
.hostname
,
843 "token" : self
.token
,
846 # Check if we update an IPv4 address.
847 address4
= self
.get_address("ipv4")
849 data
["ip"] = address4
851 # Check if we update an IPv6 address.
852 address6
= self
.get_address("ipv6")
854 data
["ipv6"] = address6
856 # Raise an error if no address is given.
857 if "ip" not in data
and "ipv6" not in data
:
858 raise DDNSConfigurationError
860 # Send update to the server.
861 response
= self
.send_request(self
.url
, data
=data
)
863 # Get the full response message.
864 output
= response
.read().decode()
866 # Remove all leading and trailing whitespace.
867 output
= output
.strip()
869 # Handle success messages.
873 # The provider does not give detailed information
874 # if the update fails. Only a "KO" will be sent back.
876 raise DDNSUpdateError
878 # If we got here, some other update error happened.
879 raise DDNSUpdateError
882 class DDNSProviderDyFi(DDNSProtocolDynDNS2
, DDNSProvider
):
885 website
= "https://www.dy.fi/"
886 protocols
= ("ipv4",)
888 # Information about the format of the request is to be found
889 # https://www.dy.fi/page/clients?lang=en
890 # https://www.dy.fi/page/specification?lang=en
892 url
= "https://www.dy.fi/nic/update"
894 # Please only send automatic updates when your IP address changes,
895 # or once per 5 to 6 days to refresh the address mapping (they will
896 # expire if not refreshed within 7 days).
900 class DDNSProviderDynDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
901 handle
= "dyndns.org"
903 website
= "http://dyn.com/dns/"
904 protocols
= ("ipv4",)
906 # Information about the format of the request is to be found
907 # http://http://dyn.com/support/developers/api/perform-update/
908 # http://dyn.com/support/developers/api/return-codes/
910 url
= "https://members.dyndns.org/nic/update"
913 class DDNSProviderDomainOffensive(DDNSProtocolDynDNS2
, DDNSProvider
):
915 name
= "Domain-Offensive"
916 website
= "https://www.do.de/"
917 protocols
= ("ipv6", "ipv4")
919 # Detailed information about the request and response codes
920 # are available on the providers webpage.
921 # https://www.do.de/wiki/FlexDNS_-_Entwickler
923 url
= "https://ddns.do.de/"
925 class DDNSProviderDynUp(DDNSProvider
):
928 website
= "http://dynup.de/"
929 protocols
= ("ipv4",)
931 # Information about the format of the HTTPS request is to be found
932 # https://dyndnsfree.de/user/hilfe.php
934 url
= "https://dynup.de/dyn.php"
935 can_remove_records
= False
936 supports_token_auth
= False
938 def update_protocol(self
, proto
):
940 "username" : self
.username
,
941 "password" : self
.password
,
942 "hostname" : self
.hostname
,
946 # Send update to the server.
947 response
= self
.send_request(self
.url
, data
=data
)
949 # Get the full response message.
950 output
= response
.read().decode()
952 # Remove all leading and trailing whitespace.
953 output
= output
.strip()
955 # Handle success messages.
956 if output
.startswith("I:OK"):
959 # If we got here, some other update error happened.
960 raise DDNSUpdateError
963 class DDNSProviderDynU(DDNSProtocolDynDNS2
, DDNSProvider
):
966 website
= "http://dynu.com/"
967 protocols
= ("ipv6", "ipv4",)
969 # Detailed information about the request and response codes
970 # are available on the providers webpage.
971 # http://dynu.com/Default.aspx?page=dnsapi
973 url
= "https://api.dynu.com/nic/update"
975 # DynU sends the IPv6 and IPv4 address in one request
978 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, "ipv4")
980 # This one supports IPv6
981 myipv6
= self
.get_address("ipv6")
983 # Add update information if we have an IPv6 address.
985 data
["myipv6"] = myipv6
987 self
.send_request(data
)
990 class DDNSProviderEasyDNS(DDNSProvider
):
991 handle
= "easydns.com"
993 website
= "http://www.easydns.com/"
994 protocols
= ("ipv4",)
996 # Detailed information about the request and response codes
997 # (API 1.3) are available on the providers webpage.
998 # https://fusion.easydns.com/index.php?/Knowledgebase/Article/View/102/7/dynamic-dns
1000 url
= "https://api.cp.easydns.com/dyn/tomato.php"
1002 supports_token_auth
= False
1004 def update_protocol(self
, proto
):
1006 "myip" : self
.get_address(proto
, "-"),
1007 "hostname" : self
.hostname
,
1010 # Send update to the server.
1011 response
= self
.send_request(self
.url
, data
=data
, username
=self
.username
, password
=self
.password
)
1013 # Get the full response message.
1014 output
= response
.read().decode()
1016 # Remove all leading and trailing whitespace.
1017 output
= output
.strip()
1019 # Handle success messages.
1020 if output
.startswith("NOERROR"):
1023 # Handle error codes.
1024 if output
.startswith("NOACCESS"):
1025 raise DDNSAuthenticationError
1027 elif output
.startswith("NOSERVICE"):
1028 raise DDNSRequestError(_("Dynamic DNS is not turned on for this domain"))
1030 elif output
.startswith("ILLEGAL INPUT"):
1031 raise DDNSRequestError(_("Invalid data has been sent"))
1033 elif output
.startswith("TOOSOON"):
1034 raise DDNSRequestError(_("Too frequent update requests have been sent"))
1036 # If we got here, some other update error happened.
1037 raise DDNSUpdateError
1040 class DDNSProviderDomopoli(DDNSProtocolDynDNS2
, DDNSProvider
):
1041 handle
= "domopoli.de"
1042 name
= "domopoli.de"
1043 website
= "http://domopoli.de/"
1044 protocols
= ("ipv4",)
1046 # https://www.domopoli.de/?page=howto#DynDns_start
1048 # This provider does not support TLS 1.2.
1049 url
= "https://dyndns.domopoli.de/nic/update"
1052 class DDNSProviderDynsNet(DDNSProvider
):
1055 website
= "http://www.dyns.net/"
1056 protocols
= ("ipv4",)
1057 can_remove_records
= False
1058 supports_token_auth
= False
1060 # There is very detailed informatio about how to send the update request and
1061 # the possible response codes. (Currently we are using the v1.1 proto)
1062 # http://www.dyns.net/documentation/technical/protocol/
1064 url
= "https://www.dyns.net/postscript011.php"
1066 def update_protocol(self
, proto
):
1068 "ip" : self
.get_address(proto
),
1069 "host" : self
.hostname
,
1070 "username" : self
.username
,
1071 "password" : self
.password
,
1074 # Send update to the server.
1075 response
= self
.send_request(self
.url
, data
=data
)
1077 # Get the full response message.
1078 output
= response
.read().decode()
1080 # Handle success messages.
1081 if output
.startswith("200"):
1084 # Handle error codes.
1085 if output
.startswith("400"):
1086 raise DDNSRequestError(_("Malformed request has been sent"))
1087 elif output
.startswith("401"):
1088 raise DDNSAuthenticationError
1089 elif output
.startswith("402"):
1090 raise DDNSRequestError(_("Too frequent update requests have been sent"))
1091 elif output
.startswith("403"):
1092 raise DDNSInternalServerError
1094 # If we got here, some other update error happened.
1095 raise DDNSUpdateError(_("Server response: %s") % output
)
1098 class DDNSProviderEnomCom(DDNSResponseParserXML
, DDNSProvider
):
1101 website
= "http://www.enom.com/"
1102 protocols
= ("ipv4",)
1104 # There are very detailed information about how to send an update request and
1105 # the respone codes.
1106 # http://www.enom.com/APICommandCatalog/
1108 url
= "https://dynamic.name-services.com/interface.asp"
1109 can_remove_records
= False
1110 supports_token_auth
= False
1112 def update_protocol(self
, proto
):
1114 "command" : "setdnshost",
1115 "responsetype" : "xml",
1116 "address" : self
.get_address(proto
),
1117 "domainpassword" : self
.password
,
1118 "zone" : self
.hostname
1121 # Send update to the server.
1122 response
= self
.send_request(self
.url
, data
=data
)
1124 # Get the full response message.
1125 output
= response
.read().decode()
1127 # Handle success messages.
1128 if self
.get_xml_tag_value(output
, "ErrCount") == "0":
1131 # Handle error codes.
1132 errorcode
= self
.get_xml_tag_value(output
, "ResponseNumber")
1134 if errorcode
== "304155":
1135 raise DDNSAuthenticationError
1136 elif errorcode
== "304153":
1137 raise DDNSRequestError(_("Domain not found"))
1139 # If we got here, some other update error happened.
1140 raise DDNSUpdateError
1143 class DDNSProviderEntryDNS(DDNSProvider
):
1144 handle
= "entrydns.net"
1146 website
= "http://entrydns.net/"
1147 protocols
= ("ipv4",)
1149 # Some very tiny details about their so called "Simple API" can be found
1150 # here: https://entrydns.net/help
1151 url
= "https://entrydns.net/records/modify"
1152 can_remove_records
= False
1153 supports_token_auth
= True
1155 def update_protocol(self
, proto
):
1157 "ip" : self
.get_address(proto
),
1160 # Add auth token to the update url.
1161 url
= "%s/%s" % (self
.url
, self
.token
)
1163 # Send update to the server.
1165 response
= self
.send_request(url
, data
=data
)
1167 # Handle error codes
1168 except urllib
.error
.HTTPError
as e
:
1170 raise DDNSAuthenticationError
1173 raise DDNSRequestError(_("An invalid IP address was submitted"))
1177 # Handle success messages.
1178 if response
.code
== 200:
1181 # If we got here, some other update error happened.
1182 raise DDNSUpdateError
1185 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider
):
1186 handle
= "freedns.afraid.org"
1187 name
= "freedns.afraid.org"
1188 website
= "http://freedns.afraid.org/"
1190 # No information about the request or response could be found on the vendor
1191 # page. All used values have been collected by testing.
1192 url
= "https://sync.afraid.org/u/"
1193 can_remove_records
= False
1194 supports_token_auth
= True
1196 def update_protocol(self
, proto
):
1198 # Add auth token to the update url.
1199 url
= "%s%s/" % (self
.url
, self
.token
)
1201 # Send update to the server.
1202 response
= self
.send_request(url
)
1204 # Get the full response message.
1205 output
= response
.read().decode()
1207 # Handle success messages.
1208 if output
.startswith("Updated") or output
.startswith("No IP change detected"):
1211 # Handle error codes.
1212 if output
== "ERROR: Unable to locate this record":
1213 raise DDNSAuthenticationError
1214 elif "is an invalid IP address" in output
:
1215 raise DDNSRequestError(_("Invalid IP address has been sent"))
1217 # If we got here, some other update error happened.
1218 raise DDNSUpdateError
1221 class DDNSProviderGodaddy(DDNSProvider
):
1222 handle
= "godaddy.com"
1223 name
= "godaddy.com"
1224 website
= "https://godaddy.com/"
1225 protocols
= ("ipv4",)
1227 # Information about the format of the HTTP request is to be found
1228 # here: https://developer.godaddy.com/doc/endpoint/domains#/v1/recordReplaceTypeName
1229 url
= "https://api.godaddy.com/v1/domains/"
1230 can_remove_records
= False
1232 def update_protocol(self
, proto
):
1234 ip_address
= self
.get_address(proto
)
1237 url
= f
"{self.url}/{self.hostname}/records/A/@"
1240 data
= json
.dumps([{"data": ip_address
, "ttl": 600, "name": self
.hostname
, "type": "A"}]).encode("utf-8")
1242 # Method requires authentication by special headers.
1243 request
= urllib
.request
.Request(url
=url
,
1245 headers
={"Authorization": f
"sso-key {self.username}:{self.password}",
1246 "Content-Type": "application/json"},
1248 result
= urllib
.request
.urlopen(request
)
1251 if result
.code
== 200:
1255 if result
.code
== 400:
1256 raise DDNSRequestError(_("Malformed request received."))
1257 if result
.code
in (401, 403):
1258 raise DDNSAuthenticationError
1259 if result
.code
== 404:
1260 raise DDNSRequestError(_("Resource not found."))
1261 if result
.code
== 422:
1262 raise DDNSRequestError(_("Record does not fulfill the schema."))
1263 if result
.code
== 429:
1264 raise DDNSRequestError(_("API Rate limiting."))
1266 # If we got here, some other update error happened.
1267 raise DDNSUpdateError
1270 class DDNSProviderHENet(DDNSProtocolDynDNS2
, DDNSProvider
):
1273 website
= "https://he.net"
1274 protocols
= ("ipv6", "ipv4",)
1276 # Detailed information about the update api can be found here.
1277 # http://dns.he.net/docs.html
1279 url
= "https://dyn.dns.he.net/nic/update"
1282 return self
.get("hostname")
1286 class DDNSProviderItsdns(DDNSProtocolDynDNS2
, DDNSProvider
):
1289 website
= "https://www.inwx.com"
1290 protocols
= ("ipv6", "ipv4")
1292 # Information about the format of the HTTP request is to be found
1293 # here: https://www.inwx.com/en/nameserver2/dyndns (requires login)
1294 # Notice: The URL is the same for: inwx.com|de|at|ch|es
1296 url
= "https://dyndns.inwx.com/nic/update"
1299 class DDNSProviderItsdns(DDNSProtocolDynDNS2
, DDNSProvider
):
1300 handle
= "itsdns.de"
1302 website
= "http://www.itsdns.de/"
1303 protocols
= ("ipv6", "ipv4")
1305 # Information about the format of the HTTP request is to be found
1306 # here: https://www.itsdns.de/dynupdatehelp.htm
1308 url
= "https://www.itsdns.de/update.php"
1311 class DDNSProviderJoker(DDNSProtocolDynDNS2
, DDNSProvider
):
1312 handle
= "joker.com"
1313 name
= "Joker.com Dynamic DNS"
1314 website
= "https://joker.com/"
1315 protocols
= ("ipv4",)
1317 # Information about the request can be found here:
1318 # https://joker.com/faq/content/11/427/en/what-is-dynamic-dns-dyndns.html
1319 # Using DynDNS V2 protocol over HTTPS here
1321 url
= "https://svc.joker.com/nic/update"
1324 class DDNSProviderKEYSYSTEMS(DDNSProvider
):
1325 handle
= "key-systems.net"
1326 name
= "dynamicdns.key-systems.net"
1327 website
= "https://domaindiscount24.com/"
1328 protocols
= ("ipv4",)
1330 # There are only information provided by the domaindiscount24 how to
1331 # perform an update with HTTP APIs
1332 # https://www.domaindiscount24.com/faq/dynamic-dns
1333 # examples: https://dynamicdns.key-systems.net/update.php?hostname=hostname&password=password&ip=auto
1334 # https://dynamicdns.key-systems.net/update.php?hostname=hostname&password=password&ip=213.x.x.x&mx=213.x.x.x
1336 url
= "https://dynamicdns.key-systems.net/update.php"
1337 can_remove_records
= False
1338 supports_token_auth
= False
1340 def update_protocol(self
, proto
):
1341 address
= self
.get_address(proto
)
1343 "hostname" : self
.hostname
,
1344 "password" : self
.password
,
1348 # Send update to the server.
1349 response
= self
.send_request(self
.url
, data
=data
)
1351 # Get the full response message.
1352 output
= response
.read().decode()
1354 # Handle success messages.
1355 if "code = 200" in output
:
1358 # Handle error messages.
1359 if "abuse prevention triggered" in output
:
1360 raise DDNSAbuseError
1361 elif "invalid password" in output
:
1362 raise DDNSAuthenticationError
1363 elif "Authorization failed" in output
:
1364 raise DDNSRequestError(_("Invalid hostname specified"))
1366 # If we got here, some other update error happened.
1367 raise DDNSUpdateError
1370 class DDNSProviderGoogle(DDNSProtocolDynDNS2
, DDNSProvider
):
1371 handle
= "domains.google.com"
1372 name
= "Google Domains"
1373 website
= "https://domains.google.com/"
1374 protocols
= ("ipv4",)
1376 # Information about the format of the HTTP request is to be found
1377 # here: https://support.google.com/domains/answer/6147083?hl=en
1379 url
= "https://domains.google.com/nic/update"
1382 class DDNSProviderLightningWireLabs(DDNSProvider
):
1383 handle
= "dns.lightningwirelabs.com"
1384 name
= "Lightning Wire Labs DNS Service"
1385 website
= "https://dns.lightningwirelabs.com/"
1387 # Information about the format of the HTTPS request is to be found
1388 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
1390 supports_token_auth
= True
1392 url
= "https://dns.lightningwirelabs.com/update"
1395 # Raise an error if no auth details are given.
1397 raise DDNSConfigurationError
1400 "hostname" : self
.hostname
,
1401 "token" : self
.token
,
1402 "address6" : self
.get_address("ipv6", "-"),
1403 "address4" : self
.get_address("ipv4", "-"),
1406 # Send update to the server.
1407 response
= self
.send_request(self
.url
, data
=data
)
1409 # Handle success messages.
1410 if response
.code
== 200:
1413 # If we got here, some other update error happened.
1414 raise DDNSUpdateError
1417 class DDNSProviderLoopia(DDNSProtocolDynDNS2
, DDNSProvider
):
1418 handle
= "loopia.se"
1420 website
= "https://www.loopia.com"
1421 protocols
= ("ipv4",)
1423 # Information about the format of the HTTP request is to be found
1424 # here: https://support.loopia.com/wiki/About_the_DynDNS_support
1426 url
= "https://dns.loopia.se/XDynDNSServer/XDynDNS.php"
1429 class DDNSProviderMyOnlinePortal(DDNSProtocolDynDNS2
, DDNSProvider
):
1430 handle
= "myonlineportal.net"
1431 name
= "myonlineportal.net"
1432 website
= "https:/myonlineportal.net/"
1434 # Information about the request and response can be obtained here:
1435 # https://myonlineportal.net/howto_dyndns
1437 url
= "https://myonlineportal.net/updateddns"
1439 def prepare_request_data(self
, proto
):
1441 "hostname" : self
.hostname
,
1442 "ip" : self
.get_address(proto
),
1448 class DDNSProviderNamecheap(DDNSResponseParserXML
, DDNSProvider
):
1449 handle
= "namecheap.com"
1451 website
= "http://namecheap.com"
1452 protocols
= ("ipv4",)
1454 # Information about the format of the HTTP request is to be found
1455 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
1456 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
1458 url
= "https://dynamicdns.park-your-domain.com/update"
1459 can_remove_records
= False
1460 supports_token_auth
= False
1462 def update_protocol(self
, proto
):
1463 # Namecheap requires the hostname splitted into a host and domain part.
1464 host
, domain
= self
.hostname
.split(".", 1)
1466 # Get and store curent IP address.
1467 address
= self
.get_address(proto
)
1471 "password" : self
.password
,
1476 # Send update to the server.
1477 response
= self
.send_request(self
.url
, data
=data
)
1479 # Get the full response message.
1480 output
= response
.read().decode()
1482 # Handle success messages.
1483 if self
.get_xml_tag_value(output
, "IP") == address
:
1486 # Handle error codes.
1487 errorcode
= self
.get_xml_tag_value(output
, "ResponseNumber")
1489 if errorcode
== "304156":
1490 raise DDNSAuthenticationError
1491 elif errorcode
== "316153":
1492 raise DDNSRequestError(_("Domain not found"))
1493 elif errorcode
== "316154":
1494 raise DDNSRequestError(_("Domain not active"))
1495 elif errorcode
in ("380098", "380099"):
1496 raise DDNSInternalServerError
1498 # If we got here, some other update error happened.
1499 raise DDNSUpdateError
1502 class DDNSProviderNOIP(DDNSProtocolDynDNS2
, DDNSProvider
):
1503 handle
= "no-ip.com"
1505 website
= "http://www.noip.com/"
1506 protocols
= ("ipv4",)
1508 # Information about the format of the HTTP request is to be found
1509 # here: http://www.noip.com/integrate/request and
1510 # here: http://www.noip.com/integrate/response
1512 url
= "https://dynupdate.noip.com/nic/update"
1514 def prepare_request_data(self
, proto
):
1515 assert proto
== "ipv4"
1518 "hostname" : self
.hostname
,
1519 "address" : self
.get_address(proto
),
1525 class DDNSProviderNowDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1526 handle
= "now-dns.com"
1528 website
= "http://now-dns.com/"
1529 protocols
= ("ipv6", "ipv4")
1531 # Information about the format of the request is to be found
1532 # but only can be accessed by register an account and login
1533 # https://now-dns.com/?m=api
1535 url
= "https://now-dns.com/update"
1538 class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2
, DDNSProvider
):
1539 handle
= "nsupdate.info"
1540 name
= "nsupdate.info"
1541 website
= "http://nsupdate.info/"
1542 protocols
= ("ipv6", "ipv4",)
1544 # Information about the format of the HTTP request can be found
1545 # after login on the provider user interface and here:
1546 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
1548 url
= "https://nsupdate.info/nic/update"
1550 # TODO nsupdate.info can actually do this, but the functionality
1551 # has not been implemented here, yet.
1552 can_remove_records
= False
1554 supports_token_auth
= True
1556 # After a failed update, there will be no retries
1557 # https://bugzilla.ipfire.org/show_bug.cgi?id=10603
1558 holdoff_failure_days
= None
1560 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
1561 # and for the password a so called secret.
1564 return self
.get("hostname")
1568 return self
.token
or self
.get("secret")
1570 def prepare_request_data(self
, proto
):
1572 "myip" : self
.get_address(proto
),
1578 class DDNSProviderOpenDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1579 handle
= "opendns.com"
1581 website
= "http://www.opendns.com"
1583 # Detailed information about the update request and possible
1584 # response codes can be obtained from here:
1585 # https://support.opendns.com/entries/23891440
1587 url
= "https://updates.opendns.com/nic/update"
1589 def prepare_request_data(self
, proto
):
1591 "hostname" : self
.hostname
,
1592 "myip" : self
.get_address(proto
),
1598 class DDNSProviderOVH(DDNSProtocolDynDNS2
, DDNSProvider
):
1601 website
= "http://www.ovh.com/"
1602 protocols
= ("ipv4",)
1604 # OVH only provides very limited information about how to
1605 # update a DynDNS host. They only provide the update url
1606 # on the their german subpage.
1608 # http://hilfe.ovh.de/DomainDynHost
1610 url
= "https://www.ovh.com/nic/update"
1612 def prepare_request_data(self
, proto
):
1613 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1615 "system" : "dyndns",
1621 class DDNSProviderRegfish(DDNSProvider
):
1622 handle
= "regfish.com"
1623 name
= "Regfish GmbH"
1624 website
= "http://www.regfish.com/"
1626 # A full documentation to the providers api can be found here
1627 # but is only available in german.
1628 # https://www.regfish.de/domains/dyndns/dokumentation
1630 url
= "https://dyndns.regfish.de/"
1631 can_remove_records
= False
1632 supports_token_auth
= True
1636 "fqdn" : self
.hostname
,
1639 # Check if we update an IPv6 address.
1640 address6
= self
.get_address("ipv6")
1642 data
["ipv6"] = address6
1644 # Check if we update an IPv4 address.
1645 address4
= self
.get_address("ipv4")
1647 data
["ipv4"] = address4
1649 # Raise an error if none address is given.
1650 if "ipv6" not in data
and "ipv4" not in data
:
1651 raise DDNSConfigurationError
1653 # Check if a token has been set.
1655 data
["token"] = self
.token
1657 # Raise an error if no token and no useranem and password
1659 elif not self
.username
and not self
.password
:
1660 raise DDNSConfigurationError(_("No Auth details specified"))
1662 # HTTP Basic Auth is only allowed if no token is used.
1664 # Send update to the server.
1665 response
= self
.send_request(self
.url
, data
=data
)
1667 # Send update to the server.
1668 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
, data
=data
)
1670 # Get the full response message.
1671 output
= response
.read().decode()
1673 # Handle success messages.
1674 if "100" in output
or "101" in output
:
1677 # Handle error codes.
1678 if "401" or "402" in output
:
1679 raise DDNSAuthenticationError
1680 elif "408" in output
:
1681 raise DDNSRequestError(_("Invalid IPv4 address has been sent"))
1682 elif "409" in output
:
1683 raise DDNSRequestError(_("Invalid IPv6 address has been sent"))
1684 elif "412" in output
:
1685 raise DDNSRequestError(_("No valid FQDN was given"))
1686 elif "414" in output
:
1687 raise DDNSInternalServerError
1689 # If we got here, some other update error happened.
1690 raise DDNSUpdateError
1693 class DDNSProviderSchokokeksDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1694 handle
= "schokokeks.org"
1696 website
= "http://www.schokokeks.org/"
1697 protocols
= ("ipv4",)
1699 # Information about the format of the request is to be found
1700 # https://wiki.schokokeks.org/DynDNS
1701 url
= "https://dyndns.schokokeks.org/nic/update"
1704 class DDNSProviderSelfhost(DDNSProtocolDynDNS2
, DDNSProvider
):
1705 handle
= "selfhost.de"
1706 name
= "Selfhost.de"
1707 website
= "http://www.selfhost.de/"
1708 protocols
= ("ipv4",)
1710 url
= "https://carol.selfhost.de/nic/update"
1712 def prepare_request_data(self
, proto
):
1713 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1721 class DDNSProviderServercow(DDNSProvider
):
1722 handle
= "servercow.de"
1723 name
= "servercow.de"
1724 website
= "https://servercow.de/"
1725 protocols
= ("ipv4", "ipv6")
1727 url
= "https://www.servercow.de/dnsupdate/update.php"
1728 can_remove_records
= False
1729 supports_token_auth
= False
1731 def update_protocol(self
, proto
):
1733 "ipaddr" : self
.get_address(proto
),
1734 "hostname" : self
.hostname
,
1735 "username" : self
.username
,
1736 "pass" : self
.password
,
1739 # Send request to provider
1740 response
= self
.send_request(self
.url
, data
=data
)
1743 output
= response
.read().decode()
1745 # Server responds with OK if update was successful
1746 if output
.startswith("OK"):
1750 elif output
.startswith("FAILED - Authentication failed"):
1751 raise DDNSAuthenticationError
1753 # If we got here, some other update error happened
1754 raise DDNSUpdateError(output
)
1757 class DDNSProviderSPDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1758 handle
= "spdns.org"
1760 website
= "https://www.spdyn.de/"
1762 # Detailed information about request and response codes are provided
1763 # by the vendor. They are using almost the same mechanism and status
1764 # codes as dyndns.org so we can inherit all those stuff.
1766 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
1767 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
1769 url
= "https://update.spdyn.de/nic/update"
1771 supports_token_auth
= True
1775 return self
.get("username") or self
.hostname
1779 return self
.get("password") or self
.token
1782 class DDNSProviderStrato(DDNSProtocolDynDNS2
, DDNSProvider
):
1783 handle
= "strato.com"
1785 website
= "http:/www.strato.com/"
1786 protocols
= ("ipv4",)
1788 # Information about the request and response can be obtained here:
1789 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
1791 url
= "https://dyndns.strato.com/nic/update"
1793 def prepare_request_data(self
, proto
):
1794 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1797 "backupmx" : "NOCHG"
1803 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1804 handle
= "twodns.de"
1806 website
= "http://www.twodns.de"
1807 protocols
= ("ipv4",)
1809 # Detailed information about the request can be found here
1810 # http://twodns.de/en/faqs
1811 # http://twodns.de/en/api
1813 url
= "https://update.twodns.de/update"
1815 def prepare_request_data(self
, proto
):
1816 assert proto
== "ipv4"
1819 "ip" : self
.get_address(proto
),
1820 "hostname" : self
.hostname
1826 class DDNSProviderUdmedia(DDNSProtocolDynDNS2
, DDNSProvider
):
1827 handle
= "udmedia.de"
1828 name
= "Udmedia GmbH"
1829 website
= "http://www.udmedia.de"
1830 protocols
= ("ipv4",)
1832 # Information about the request can be found here
1833 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1835 url
= "https://www.udmedia.de/nic/update"
1838 class DDNSProviderVariomedia(DDNSProtocolDynDNS2
, DDNSProvider
):
1839 handle
= "variomedia.de"
1841 website
= "http://www.variomedia.de/"
1842 protocols
= ("ipv6", "ipv4",)
1844 # Detailed information about the request can be found here
1845 # https://dyndns.variomedia.de/
1847 url
= "https://dyndns.variomedia.de/nic/update"
1849 def prepare_request_data(self
, proto
):
1851 "hostname" : self
.hostname
,
1852 "myip" : self
.get_address(proto
),
1858 class DDNSProviderXLhost(DDNSProtocolDynDNS2
, DDNSProvider
):
1859 handle
= "xlhost.de"
1861 website
= "http://xlhost.de/"
1862 protocols
= ("ipv4",)
1864 # Information about the format of the HTTP request is to be found
1865 # here: https://xlhost.de/faq/index_html?topicId=CQA2ELIPO4SQ
1867 url
= "https://nsupdate.xlhost.de/"
1870 class DDNSProviderZoneedit(DDNSProvider
):
1871 handle
= "zoneedit.com"
1873 website
= "http://www.zoneedit.com"
1874 protocols
= ("ipv4",)
1876 supports_token_auth
= False
1878 # Detailed information about the request and the response codes can be
1880 # http://www.zoneedit.com/doc/api/other.html
1881 # http://www.zoneedit.com/faq.html
1883 url
= "https://dynamic.zoneedit.com/auth/dynamic.html"
1885 def update_protocol(self
, proto
):
1887 "dnsto" : self
.get_address(proto
),
1888 "host" : self
.hostname
1891 # Send update to the server.
1892 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
, data
=data
)
1894 # Get the full response message.
1895 output
= response
.read().decode()
1897 # Handle success messages.
1898 if output
.startswith("<SUCCESS"):
1901 # Handle error codes.
1902 if output
.startswith("invalid login"):
1903 raise DDNSAuthenticationError
1904 elif output
.startswith("<ERROR CODE=\"704\""):
1905 raise DDNSRequestError(_("No valid FQDN was given"))
1906 elif output
.startswith("<ERROR CODE=\"702\""):
1907 raise DDNSRequestError(_("Too frequent update requests have been sent"))
1909 # If we got here, some other update error happened.
1910 raise DDNSUpdateError
1913 class DDNSProviderDNSmadeEasy(DDNSProvider
):
1914 handle
= "dnsmadeeasy.com"
1915 name
= "DNSmadeEasy.com"
1916 website
= "http://www.dnsmadeeasy.com/"
1917 protocols
= ("ipv4",)
1919 # DNS Made Easy Nameserver Provider also offering Dynamic DNS
1920 # Documentation can be found here:
1921 # http://www.dnsmadeeasy.com/dynamic-dns/
1923 url
= "https://cp.dnsmadeeasy.com/servlet/updateip?"
1924 can_remove_records
= False
1925 supports_token_auth
= False
1927 def update_protocol(self
, proto
):
1929 "ip" : self
.get_address(proto
),
1930 "id" : self
.hostname
,
1931 "username" : self
.username
,
1932 "password" : self
.password
,
1935 # Send update to the server.
1936 response
= self
.send_request(self
.url
, data
=data
)
1938 # Get the full response message.
1939 output
= response
.read().decode()
1941 # Handle success messages.
1942 if output
.startswith("success") or output
.startswith("error-record-ip-same"):
1945 # Handle error codes.
1946 if output
.startswith("error-auth-suspend"):
1947 raise DDNSRequestError(_("Account has been suspended"))
1949 elif output
.startswith("error-auth-voided"):
1950 raise DDNSRequestError(_("Account has been revoked"))
1952 elif output
.startswith("error-record-invalid"):
1953 raise DDNSRequestError(_("Specified host does not exist"))
1955 elif output
.startswith("error-auth"):
1956 raise DDNSAuthenticationError
1958 # If we got here, some other update error happened.
1959 raise DDNSUpdateError(_("Server response: %s") % output
)
1962 class DDNSProviderZZZZ(DDNSProvider
):
1965 website
= "https://zzzz.io"
1966 protocols
= ("ipv6", "ipv4",)
1968 # Detailed information about the update request can be found here:
1969 # https://zzzz.io/faq/
1971 # Details about the possible response codes have been provided in the bugtracker:
1972 # https://bugzilla.ipfire.org/show_bug.cgi?id=10584#c2
1974 url
= "https://zzzz.io/api/v1/update"
1975 can_remove_records
= False
1976 supports_token_auth
= True
1978 def update_protocol(self
, proto
):
1980 "ip" : self
.get_address(proto
),
1981 "token" : self
.token
,
1985 data
["type"] = "aaaa"
1987 # zzzz uses the host from the full hostname as part
1988 # of the update url.
1989 host
, domain
= self
.hostname
.split(".", 1)
1991 # Add host value to the update url.
1992 url
= "%s/%s" % (self
.url
, host
)
1994 # Send update to the server.
1996 response
= self
.send_request(url
, data
=data
)
1998 # Handle error codes.
1999 except DDNSNotFound
:
2000 raise DDNSRequestError(_("Invalid hostname specified"))
2002 # Handle success messages.
2003 if response
.code
== 200:
2006 # If we got here, some other update error happened.
2007 raise DDNSUpdateError