]>
git.ipfire.org Git - people/ms/ddns.git/blob - src/ddns/providers.py
16a6e7b84a12e61626daae02114dfa88fa168690
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 # Determine when the holdoff time ends
227 last_update
= self
.db
.last_update(self
.hostname
, status
=last_status
)
228 holdoff_end
= last_update
+ datetime
.timedelta(days
=self
.holdoff_failure_days
)
230 now
= datetime
.datetime
.utcnow()
231 if now
< holdoff_end
:
232 failure_message
= self
.db
.last_update_failure_message(self
.hostname
)
234 logger
.warning(_("An update has not been performed because earlier updates failed for %s") \
238 logger
.warning(_("Last failure message:"))
240 for line
in failure_message
.splitlines():
241 logger
.warning(" %s" % line
)
243 logger
.warning(_("Further updates will be withheld until %s") % holdoff_end
)
249 def ip_address_changed(self
, protos
):
251 Returns True if this host is already up to date
252 and does not need to change the IP address on the
256 addresses
= self
.core
.system
.resolve(self
.hostname
, proto
)
257 current_address
= self
.get_address(proto
)
259 # Handle if the system has not got any IP address from a protocol
260 # (i.e. had full dual-stack connectivity which it has not any more)
261 if current_address
is None:
262 # If addresses still exists in the DNS system and if this provider
263 # is able to remove records, we will do that.
264 if addresses
and self
.can_remove_records
:
267 # Otherwise, we cannot go on...
270 if not current_address
in addresses
:
275 def holdoff_time_expired(self
):
277 Returns true if the holdoff time has expired
278 and the host requires an update
280 # If no holdoff days is defined, we cannot go on
281 if not self
.holdoff_days
:
284 # Get the timestamp of the last successfull update
285 last_update
= self
.db
.last_update(self
.hostname
, status
="success")
287 # If no timestamp has been recorded, no update has been
288 # performed. An update should be performed now.
292 # Determine when the holdoff time ends
293 holdoff_end
= last_update
+ datetime
.timedelta(days
=self
.holdoff_days
)
295 now
= datetime
.datetime
.utcnow()
297 if now
>= holdoff_end
:
298 logger
.debug("The holdoff time has expired for %s" % self
.hostname
)
301 logger
.debug("Updates for %s are held off until %s" % \
302 (self
.hostname
, holdoff_end
))
305 def send_request(self
, *args
, **kwargs
):
307 Proxy connection to the send request
310 return self
.core
.system
.send_request(*args
, **kwargs
)
312 def get_address(self
, proto
, default
=None):
314 Proxy method to get the current IP address.
316 return self
.core
.system
.get_address(proto
) or default
318 def have_address(self
, proto
):
320 Returns True if an IP address for the given protocol
323 address
= self
.get_address(proto
)
331 class DDNSProtocolDynDNS2(object):
333 This is an abstract class that implements the DynDNS updater
334 protocol version 2. As this is a popular way to update dynamic
335 DNS records, this class is supposed make the provider classes
339 # Information about the format of the request is to be found
340 # http://dyn.com/support/developers/api/perform-update/
341 # http://dyn.com/support/developers/api/return-codes/
343 # The DynDNS protocol version 2 does not allow to remove records
344 can_remove_records
= False
346 def prepare_request_data(self
, proto
):
348 "hostname" : self
.hostname
,
349 "myip" : self
.get_address(proto
),
354 def update_protocol(self
, proto
):
355 data
= self
.prepare_request_data(proto
)
357 return self
.send_request(data
)
359 def send_request(self
, data
):
360 # Send update to the server.
361 response
= DDNSProvider
.send_request(self
, self
.url
, data
=data
,
362 username
=self
.username
, password
=self
.password
)
364 # Get the full response message.
365 output
= response
.read()
367 # Handle success messages.
368 if output
.startswith("good") or output
.startswith("nochg"):
371 # Handle error codes.
372 if output
== "badauth":
373 raise DDNSAuthenticationError
374 elif output
== "abuse":
376 elif output
== "notfqdn":
377 raise DDNSRequestError(_("No valid FQDN was given."))
378 elif output
== "nohost":
379 raise DDNSRequestError(_("Specified host does not exist."))
380 elif output
== "911":
381 raise DDNSInternalServerError
382 elif output
== "dnserr":
383 raise DDNSInternalServerError(_("DNS error encountered."))
384 elif output
== "badagent":
385 raise DDNSBlockedError
387 # If we got here, some other update error happened.
388 raise DDNSUpdateError(_("Server response: %s") % output
)
391 class DDNSResponseParserXML(object):
393 This class provides a parser for XML responses which
394 will be sent by various providers. This class uses the python
395 shipped XML minidom module to walk through the XML tree and return
399 def get_xml_tag_value(self
, document
, content
):
400 # Send input to the parser.
401 xmldoc
= xml
.dom
.minidom
.parseString(document
)
403 # Get XML elements by the given content.
404 element
= xmldoc
.getElementsByTagName(content
)
406 # If no element has been found, we directly can return None.
410 # Only get the first child from an element, even there are more than one.
411 firstchild
= element
[0].firstChild
413 # Get the value of the child.
414 value
= firstchild
.nodeValue
420 class DDNSProviderAllInkl(DDNSProvider
):
421 handle
= "all-inkl.com"
422 name
= "All-inkl.com"
423 website
= "http://all-inkl.com/"
424 protocols
= ("ipv4",)
426 # There are only information provided by the vendor how to
427 # perform an update on a FRITZ Box. Grab requried informations
429 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
431 url
= "http://dyndns.kasserver.com"
432 can_remove_records
= False
435 # There is no additional data required so we directly can
437 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
)
439 # Get the full response message.
440 output
= response
.read()
442 # Handle success messages.
443 if output
.startswith("good") or output
.startswith("nochg"):
446 # If we got here, some other update error happened.
447 raise DDNSUpdateError
450 class DDNSProviderBindNsupdate(DDNSProvider
):
452 name
= "BIND nsupdate utility"
453 website
= "http://en.wikipedia.org/wiki/Nsupdate"
459 # Search if the nsupdate utility is available
460 paths
= os
.environ
.get("PATH")
462 for path
in paths
.split(":"):
463 executable
= os
.path
.join(path
, "nsupdate")
465 if os
.path
.exists(executable
):
471 scriptlet
= self
.__make
_scriptlet
()
473 # -v enables TCP hence we transfer keys and other data that may
474 # exceed the size of one packet.
475 # -t sets the timeout
476 command
= ["nsupdate", "-v", "-t", "60"]
478 p
= subprocess
.Popen(command
, shell
=True,
479 stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
481 stdout
, stderr
= p
.communicate(scriptlet
)
483 if p
.returncode
== 0:
486 raise DDNSError("nsupdate terminated with error code: %s\n %s" % (p
.returncode
, stderr
))
488 def __make_scriptlet(self
):
491 # Set a different server the update is sent to.
492 server
= self
.get("server", None)
494 scriptlet
.append("server %s" % server
)
496 # Set the DNS zone the host should be added to.
497 zone
= self
.get("zone", None)
499 scriptlet
.append("zone %s" % zone
)
501 key
= self
.get("key", None)
503 secret
= self
.get("secret")
505 scriptlet
.append("key %s %s" % (key
, secret
))
507 ttl
= self
.get("ttl", self
.DEFAULT_TTL
)
509 # Perform an update for each supported protocol.
510 for rrtype
, proto
in (("AAAA", "ipv6"), ("A", "ipv4")):
511 address
= self
.get_address(proto
)
515 scriptlet
.append("update delete %s. %s" % (self
.hostname
, rrtype
))
516 scriptlet
.append("update add %s. %s %s %s" % \
517 (self
.hostname
, ttl
, rrtype
, address
))
519 # Send the actions to the server.
520 scriptlet
.append("send")
521 scriptlet
.append("quit")
523 logger
.debug(_("Scriptlet:"))
524 for line
in scriptlet
:
525 # Masquerade the line with the secret key.
526 if line
.startswith("key"):
527 line
= "key **** ****"
529 logger
.debug(" %s" % line
)
531 return "\n".join(scriptlet
)
534 class DDNSProviderDHS(DDNSProvider
):
536 name
= "DHS International"
537 website
= "http://dhs.org/"
538 protocols
= ("ipv4",)
540 # No information about the used update api provided on webpage,
541 # grabed from source code of ez-ipudate.
543 url
= "http://members.dhs.org/nic/hosts"
544 can_remove_records
= False
546 def update_protocol(self
, proto
):
548 "domain" : self
.hostname
,
549 "ip" : self
.get_address(proto
),
551 "hostcmdstage" : "2",
555 # Send update to the server.
556 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
559 # Handle success messages.
560 if response
.code
== 200:
563 # If we got here, some other update error happened.
564 raise DDNSUpdateError
567 class DDNSProviderDNSpark(DDNSProvider
):
568 handle
= "dnspark.com"
570 website
= "http://dnspark.com/"
571 protocols
= ("ipv4",)
573 # Informations to the used api can be found here:
574 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
576 url
= "https://control.dnspark.com/api/dynamic/update.php"
577 can_remove_records
= False
579 def update_protocol(self
, proto
):
581 "domain" : self
.hostname
,
582 "ip" : self
.get_address(proto
),
585 # Send update to the server.
586 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
589 # Get the full response message.
590 output
= response
.read()
592 # Handle success messages.
593 if output
.startswith("ok") or output
.startswith("nochange"):
596 # Handle error codes.
597 if output
== "unauth":
598 raise DDNSAuthenticationError
599 elif output
== "abuse":
601 elif output
== "blocked":
602 raise DDNSBlockedError
603 elif output
== "nofqdn":
604 raise DDNSRequestError(_("No valid FQDN was given."))
605 elif output
== "nohost":
606 raise DDNSRequestError(_("Invalid hostname specified."))
607 elif output
== "notdyn":
608 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
609 elif output
== "invalid":
610 raise DDNSRequestError(_("Invalid IP address has been sent."))
612 # If we got here, some other update error happened.
613 raise DDNSUpdateError
616 class DDNSProviderDtDNS(DDNSProvider
):
619 website
= "http://dtdns.com/"
620 protocols
= ("ipv4",)
622 # Information about the format of the HTTPS request is to be found
623 # http://www.dtdns.com/dtsite/updatespec
625 url
= "https://www.dtdns.com/api/autodns.cfm"
626 can_remove_records
= False
628 def update_protocol(self
, proto
):
630 "ip" : self
.get_address(proto
),
631 "id" : self
.hostname
,
635 # Send update to the server.
636 response
= self
.send_request(self
.url
, data
=data
)
638 # Get the full response message.
639 output
= response
.read()
641 # Remove all leading and trailing whitespace.
642 output
= output
.strip()
644 # Handle success messages.
645 if "now points to" in output
:
648 # Handle error codes.
649 if output
== "No hostname to update was supplied.":
650 raise DDNSRequestError(_("No hostname specified."))
652 elif output
== "The hostname you supplied is not valid.":
653 raise DDNSRequestError(_("Invalid hostname specified."))
655 elif output
== "The password you supplied is not valid.":
656 raise DDNSAuthenticationError
658 elif output
== "Administration has disabled this account.":
659 raise DDNSRequestError(_("Account has been disabled."))
661 elif output
== "Illegal character in IP.":
662 raise DDNSRequestError(_("Invalid IP address has been sent."))
664 elif output
== "Too many failed requests.":
665 raise DDNSRequestError(_("Too many failed requests."))
667 # If we got here, some other update error happened.
668 raise DDNSUpdateError
671 class DDNSProviderDynDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
672 handle
= "dyndns.org"
674 website
= "http://dyn.com/dns/"
675 protocols
= ("ipv4",)
677 # Information about the format of the request is to be found
678 # http://http://dyn.com/support/developers/api/perform-update/
679 # http://dyn.com/support/developers/api/return-codes/
681 url
= "https://members.dyndns.org/nic/update"
684 class DDNSProviderDynU(DDNSProtocolDynDNS2
, DDNSProvider
):
687 website
= "http://dynu.com/"
688 protocols
= ("ipv6", "ipv4",)
690 # Detailed information about the request and response codes
691 # are available on the providers webpage.
692 # http://dynu.com/Default.aspx?page=dnsapi
694 url
= "https://api.dynu.com/nic/update"
696 # DynU sends the IPv6 and IPv4 address in one request
699 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, "ipv4")
701 # This one supports IPv6
702 myipv6
= self
.get_address("ipv6")
704 # Add update information if we have an IPv6 address.
706 data
["myipv6"] = myipv6
708 self
.send_request(data
)
711 class DDNSProviderEasyDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
712 handle
= "easydns.com"
714 website
= "http://www.easydns.com/"
715 protocols
= ("ipv4",)
717 # There is only some basic documentation provided by the vendor,
718 # also searching the web gain very poor results.
719 # http://mediawiki.easydns.com/index.php/Dynamic_DNS
721 url
= "http://api.cp.easydns.com/dyn/tomato.php"
724 class DDNSProviderDomopoli(DDNSProtocolDynDNS2
, DDNSProvider
):
725 handle
= "domopoli.de"
727 website
= "http://domopoli.de/"
728 protocols
= ("ipv4",)
730 # https://www.domopoli.de/?page=howto#DynDns_start
732 url
= "http://dyndns.domopoli.de/nic/update"
735 class DDNSProviderDynsNet(DDNSProvider
):
738 website
= "http://www.dyns.net/"
739 protocols
= ("ipv4",)
740 can_remove_records
= False
742 # There is very detailed informatio about how to send the update request and
743 # the possible response codes. (Currently we are using the v1.1 proto)
744 # http://www.dyns.net/documentation/technical/protocol/
746 url
= "http://www.dyns.net/postscript011.php"
748 def update_protocol(self
, proto
):
750 "ip" : self
.get_address(proto
),
751 "host" : self
.hostname
,
752 "username" : self
.username
,
753 "password" : self
.password
,
756 # Send update to the server.
757 response
= self
.send_request(self
.url
, data
=data
)
759 # Get the full response message.
760 output
= response
.read()
762 # Handle success messages.
763 if output
.startswith("200"):
766 # Handle error codes.
767 if output
.startswith("400"):
768 raise DDNSRequestError(_("Malformed request has been sent."))
769 elif output
.startswith("401"):
770 raise DDNSAuthenticationError
771 elif output
.startswith("402"):
772 raise DDNSRequestError(_("Too frequent update requests have been sent."))
773 elif output
.startswith("403"):
774 raise DDNSInternalServerError
776 # If we got here, some other update error happened.
777 raise DDNSUpdateError(_("Server response: %s") % output
)
780 class DDNSProviderEnomCom(DDNSResponseParserXML
, DDNSProvider
):
783 website
= "http://www.enom.com/"
784 protocols
= ("ipv4",)
786 # There are very detailed information about how to send an update request and
788 # http://www.enom.com/APICommandCatalog/
790 url
= "https://dynamic.name-services.com/interface.asp"
791 can_remove_records
= False
793 def update_protocol(self
, proto
):
795 "command" : "setdnshost",
796 "responsetype" : "xml",
797 "address" : self
.get_address(proto
),
798 "domainpassword" : self
.password
,
799 "zone" : self
.hostname
802 # Send update to the server.
803 response
= self
.send_request(self
.url
, data
=data
)
805 # Get the full response message.
806 output
= response
.read()
808 # Handle success messages.
809 if self
.get_xml_tag_value(output
, "ErrCount") == "0":
812 # Handle error codes.
813 errorcode
= self
.get_xml_tag_value(output
, "ResponseNumber")
815 if errorcode
== "304155":
816 raise DDNSAuthenticationError
817 elif errorcode
== "304153":
818 raise DDNSRequestError(_("Domain not found."))
820 # If we got here, some other update error happened.
821 raise DDNSUpdateError
824 class DDNSProviderEntryDNS(DDNSProvider
):
825 handle
= "entrydns.net"
827 website
= "http://entrydns.net/"
828 protocols
= ("ipv4",)
830 # Some very tiny details about their so called "Simple API" can be found
831 # here: https://entrydns.net/help
832 url
= "https://entrydns.net/records/modify"
833 can_remove_records
= False
835 def update_protocol(self
, proto
):
837 "ip" : self
.get_address(proto
),
840 # Add auth token to the update url.
841 url
= "%s/%s" % (self
.url
, self
.token
)
843 # Send update to the server.
845 response
= self
.send_request(url
, data
=data
)
848 except urllib2
.HTTPError
, e
:
850 raise DDNSAuthenticationError
853 raise DDNSRequestError(_("An invalid IP address was submitted"))
857 # Handle success messages.
858 if response
.code
== 200:
861 # If we got here, some other update error happened.
862 raise DDNSUpdateError
865 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider
):
866 handle
= "freedns.afraid.org"
867 name
= "freedns.afraid.org"
868 website
= "http://freedns.afraid.org/"
870 # No information about the request or response could be found on the vendor
871 # page. All used values have been collected by testing.
872 url
= "https://freedns.afraid.org/dynamic/update.php"
873 can_remove_records
= False
875 def update_protocol(self
, proto
):
877 "address" : self
.get_address(proto
),
880 # Add auth token to the update url.
881 url
= "%s?%s" % (self
.url
, self
.token
)
883 # Send update to the server.
884 response
= self
.send_request(url
, data
=data
)
886 # Get the full response message.
887 output
= response
.read()
889 # Handle success messages.
890 if output
.startswith("Updated") or "has not changed" in output
:
893 # Handle error codes.
894 if output
== "ERROR: Unable to locate this record":
895 raise DDNSAuthenticationError
896 elif "is an invalid IP address" in output
:
897 raise DDNSRequestError(_("Invalid IP address has been sent."))
899 # If we got here, some other update error happened.
900 raise DDNSUpdateError
903 class DDNSProviderLightningWireLabs(DDNSProvider
):
904 handle
= "dns.lightningwirelabs.com"
905 name
= "Lightning Wire Labs DNS Service"
906 website
= "http://dns.lightningwirelabs.com/"
908 # Information about the format of the HTTPS request is to be found
909 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
911 url
= "https://dns.lightningwirelabs.com/update"
915 "hostname" : self
.hostname
,
916 "address6" : self
.get_address("ipv6", "-"),
917 "address4" : self
.get_address("ipv4", "-"),
920 # Check if a token has been set.
922 data
["token"] = self
.token
924 # Check for username and password.
925 elif self
.username
and self
.password
:
927 "username" : self
.username
,
928 "password" : self
.password
,
931 # Raise an error if no auth details are given.
933 raise DDNSConfigurationError
935 # Send update to the server.
936 response
= self
.send_request(self
.url
, data
=data
)
938 # Handle success messages.
939 if response
.code
== 200:
942 # If we got here, some other update error happened.
943 raise DDNSUpdateError
946 class DDNSProviderMyOnlinePortal(DDNSProtocolDynDNS2
, DDNSProvider
):
947 handle
= "myonlineportal.net"
948 name
= "myonlineportal.net"
949 website
= "https:/myonlineportal.net/"
951 # Information about the request and response can be obtained here:
952 # https://myonlineportal.net/howto_dyndns
954 url
= "https://myonlineportal.net/updateddns"
956 def prepare_request_data(self
, proto
):
958 "hostname" : self
.hostname
,
959 "ip" : self
.get_address(proto
),
965 class DDNSProviderNamecheap(DDNSResponseParserXML
, DDNSProvider
):
966 handle
= "namecheap.com"
968 website
= "http://namecheap.com"
969 protocols
= ("ipv4",)
971 # Information about the format of the HTTP request is to be found
972 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
973 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
975 url
= "https://dynamicdns.park-your-domain.com/update"
976 can_remove_records
= False
978 def update_protocol(self
, proto
):
979 # Namecheap requires the hostname splitted into a host and domain part.
980 host
, domain
= self
.hostname
.split(".", 1)
983 "ip" : self
.get_address(proto
),
984 "password" : self
.password
,
989 # Send update to the server.
990 response
= self
.send_request(self
.url
, data
=data
)
992 # Get the full response message.
993 output
= response
.read()
995 # Handle success messages.
996 if self
.get_xml_tag_value(output
, "IP") == address
:
999 # Handle error codes.
1000 errorcode
= self
.get_xml_tag_value(output
, "ResponseNumber")
1002 if errorcode
== "304156":
1003 raise DDNSAuthenticationError
1004 elif errorcode
== "316153":
1005 raise DDNSRequestError(_("Domain not found."))
1006 elif errorcode
== "316154":
1007 raise DDNSRequestError(_("Domain not active."))
1008 elif errorcode
in ("380098", "380099"):
1009 raise DDNSInternalServerError
1011 # If we got here, some other update error happened.
1012 raise DDNSUpdateError
1015 class DDNSProviderNOIP(DDNSProtocolDynDNS2
, DDNSProvider
):
1016 handle
= "no-ip.com"
1018 website
= "http://www.no-ip.com/"
1019 protocols
= ("ipv4",)
1021 # Information about the format of the HTTP request is to be found
1022 # here: http://www.no-ip.com/integrate/request and
1023 # here: http://www.no-ip.com/integrate/response
1025 url
= "http://dynupdate.no-ip.com/nic/update"
1027 def prepare_request_data(self
, proto
):
1028 assert proto
== "ipv4"
1031 "hostname" : self
.hostname
,
1032 "address" : self
.get_address(proto
),
1038 class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2
, DDNSProvider
):
1039 handle
= "nsupdate.info"
1040 name
= "nsupdate.info"
1041 website
= "http://nsupdate.info/"
1042 protocols
= ("ipv6", "ipv4",)
1044 # Information about the format of the HTTP request can be found
1045 # after login on the provider user interface and here:
1046 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
1048 url
= "https://nsupdate.info/nic/update"
1050 # TODO nsupdate.info can actually do this, but the functionality
1051 # has not been implemented here, yet.
1052 can_remove_records
= False
1054 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
1055 # and for the password a so called secret.
1058 return self
.get("hostname")
1062 return self
.token
or self
.get("secret")
1064 def prepare_request_data(self
, proto
):
1066 "myip" : self
.get_address(proto
),
1072 class DDNSProviderOpenDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1073 handle
= "opendns.com"
1075 website
= "http://www.opendns.com"
1077 # Detailed information about the update request and possible
1078 # response codes can be obtained from here:
1079 # https://support.opendns.com/entries/23891440
1081 url
= "https://updates.opendns.com/nic/update"
1083 def prepare_request_data(self
, proto
):
1085 "hostname" : self
.hostname
,
1086 "myip" : self
.get_address(proto
),
1092 class DDNSProviderOVH(DDNSProtocolDynDNS2
, DDNSProvider
):
1095 website
= "http://www.ovh.com/"
1096 protocols
= ("ipv4",)
1098 # OVH only provides very limited information about how to
1099 # update a DynDNS host. They only provide the update url
1100 # on the their german subpage.
1102 # http://hilfe.ovh.de/DomainDynHost
1104 url
= "https://www.ovh.com/nic/update"
1106 def prepare_request_data(self
, proto
):
1107 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1109 "system" : "dyndns",
1115 class DDNSProviderRegfish(DDNSProvider
):
1116 handle
= "regfish.com"
1117 name
= "Regfish GmbH"
1118 website
= "http://www.regfish.com/"
1120 # A full documentation to the providers api can be found here
1121 # but is only available in german.
1122 # https://www.regfish.de/domains/dyndns/dokumentation
1124 url
= "https://dyndns.regfish.de/"
1125 can_remove_records
= False
1129 "fqdn" : self
.hostname
,
1132 # Check if we update an IPv6 address.
1133 address6
= self
.get_address("ipv6")
1135 data
["ipv6"] = address6
1137 # Check if we update an IPv4 address.
1138 address4
= self
.get_address("ipv4")
1140 data
["ipv4"] = address4
1142 # Raise an error if none address is given.
1143 if not data
.has_key("ipv6") and not data
.has_key("ipv4"):
1144 raise DDNSConfigurationError
1146 # Check if a token has been set.
1148 data
["token"] = self
.token
1150 # Raise an error if no token and no useranem and password
1152 elif not self
.username
and not self
.password
:
1153 raise DDNSConfigurationError(_("No Auth details specified."))
1155 # HTTP Basic Auth is only allowed if no token is used.
1157 # Send update to the server.
1158 response
= self
.send_request(self
.url
, data
=data
)
1160 # Send update to the server.
1161 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
1164 # Get the full response message.
1165 output
= response
.read()
1167 # Handle success messages.
1168 if "100" in output
or "101" in output
:
1171 # Handle error codes.
1172 if "401" or "402" in output
:
1173 raise DDNSAuthenticationError
1174 elif "408" in output
:
1175 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
1176 elif "409" in output
:
1177 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
1178 elif "412" in output
:
1179 raise DDNSRequestError(_("No valid FQDN was given."))
1180 elif "414" in output
:
1181 raise DDNSInternalServerError
1183 # If we got here, some other update error happened.
1184 raise DDNSUpdateError
1187 class DDNSProviderSelfhost(DDNSProtocolDynDNS2
, DDNSProvider
):
1188 handle
= "selfhost.de"
1189 name
= "Selfhost.de"
1190 website
= "http://www.selfhost.de/"
1191 protocols
= ("ipv4",)
1193 url
= "https://carol.selfhost.de/nic/update"
1195 def prepare_request_data(self
, proto
):
1196 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1204 class DDNSProviderSPDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1205 handle
= "spdns.org"
1207 website
= "http://spdns.org/"
1209 # Detailed information about request and response codes are provided
1210 # by the vendor. They are using almost the same mechanism and status
1211 # codes as dyndns.org so we can inherit all those stuff.
1213 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
1214 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
1216 url
= "https://update.spdns.de/nic/update"
1220 return self
.get("username") or self
.hostname
1224 return self
.get("username") or self
.token
1227 class DDNSProviderStrato(DDNSProtocolDynDNS2
, DDNSProvider
):
1228 handle
= "strato.com"
1230 website
= "http:/www.strato.com/"
1231 protocols
= ("ipv4",)
1233 # Information about the request and response can be obtained here:
1234 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
1236 url
= "https://dyndns.strato.com/nic/update"
1239 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1240 handle
= "twodns.de"
1242 website
= "http://www.twodns.de"
1243 protocols
= ("ipv4",)
1245 # Detailed information about the request can be found here
1246 # http://twodns.de/en/faqs
1247 # http://twodns.de/en/api
1249 url
= "https://update.twodns.de/update"
1251 def prepare_request_data(self
, proto
):
1252 assert proto
== "ipv4"
1255 "ip" : self
.get_address(proto
),
1256 "hostname" : self
.hostname
1262 class DDNSProviderUdmedia(DDNSProtocolDynDNS2
, DDNSProvider
):
1263 handle
= "udmedia.de"
1264 name
= "Udmedia GmbH"
1265 website
= "http://www.udmedia.de"
1266 protocols
= ("ipv4",)
1268 # Information about the request can be found here
1269 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1271 url
= "https://www.udmedia.de/nic/update"
1274 class DDNSProviderVariomedia(DDNSProtocolDynDNS2
, DDNSProvider
):
1275 handle
= "variomedia.de"
1277 website
= "http://www.variomedia.de/"
1278 protocols
= ("ipv6", "ipv4",)
1280 # Detailed information about the request can be found here
1281 # https://dyndns.variomedia.de/
1283 url
= "https://dyndns.variomedia.de/nic/update"
1285 def prepare_request_data(self
, proto
):
1287 "hostname" : self
.hostname
,
1288 "myip" : self
.get_address(proto
),
1294 class DDNSProviderZoneedit(DDNSProtocolDynDNS2
, DDNSProvider
):
1295 handle
= "zoneedit.com"
1297 website
= "http://www.zoneedit.com"
1298 protocols
= ("ipv4",)
1300 # Detailed information about the request and the response codes can be
1302 # http://www.zoneedit.com/doc/api/other.html
1303 # http://www.zoneedit.com/faq.html
1305 url
= "https://dynamic.zoneedit.com/auth/dynamic.html"
1307 def update_protocol(self
, proto
):
1309 "dnsto" : self
.get_address(proto
),
1310 "host" : self
.hostname
1313 # Send update to the server.
1314 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
1317 # Get the full response message.
1318 output
= response
.read()
1320 # Handle success messages.
1321 if output
.startswith("<SUCCESS"):
1324 # Handle error codes.
1325 if output
.startswith("invalid login"):
1326 raise DDNSAuthenticationError
1327 elif output
.startswith("<ERROR CODE=\"704\""):
1328 raise DDNSRequestError(_("No valid FQDN was given."))
1329 elif output
.startswith("<ERROR CODE=\"702\""):
1330 raise DDNSInternalServerError
1332 # If we got here, some other update error happened.
1333 raise DDNSUpdateError
1336 class DDNSProviderZZZZ(DDNSProvider
):
1339 website
= "https://zzzz.io"
1340 protocols
= ("ipv6", "ipv4",)
1342 # Detailed information about the update request can be found here:
1343 # https://zzzz.io/faq/
1345 # Details about the possible response codes have been provided in the bugtracker:
1346 # https://bugzilla.ipfire.org/show_bug.cgi?id=10584#c2
1348 url
= "https://zzzz.io/api/v1/update"
1349 can_remove_records
= False
1351 def update_protocol(self
, proto
):
1353 "ip" : self
.get_address(proto
),
1354 "token" : self
.token
,
1358 data
["type"] = "aaaa"
1360 # zzzz uses the host from the full hostname as part
1361 # of the update url.
1362 host
, domain
= self
.hostname
.split(".", 1)
1364 # Add host value to the update url.
1365 url
= "%s/%s" % (self
.url
, host
)
1367 # Send update to the server.
1369 response
= self
.send_request(url
, data
=data
)
1371 # Handle error codes.
1372 except DDNSNotFound
:
1373 raise DDNSRequestError(_("Invalid hostname specified"))
1375 # Handle success messages.
1376 if response
.code
== 200:
1379 # If we got here, some other update error happened.
1380 raise DDNSUpdateError