]>
git.ipfire.org Git - people/ms/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 # True if the provider is able to remove records, too.
67 # Required to remove AAAA records if IPv6 is absent again.
68 can_remove_records
= True
70 # Automatically register all providers.
71 class __metaclass__(type):
72 def __init__(provider
, name
, bases
, dict):
73 type.__init
__(provider
, name
, bases
, dict)
75 # The main class from which is inherited is not registered
77 if name
== "DDNSProvider":
80 if not all((provider
.handle
, provider
.name
, provider
.website
)):
81 raise DDNSError(_("Provider is not properly configured"))
83 assert not _providers
.has_key(provider
.handle
), \
84 "Provider '%s' has already been registered" % provider
.handle
86 _providers
[provider
.handle
] = provider
91 Should be overwritten to check if the system the code is running
92 on has all the required tools to support this provider.
96 def __init__(self
, core
, **settings
):
99 # Copy a set of default settings and
100 # update them by those from the configuration file.
101 self
.settings
= self
.DEFAULT_SETTINGS
.copy()
102 self
.settings
.update(settings
)
105 return "<DDNS Provider %s (%s)>" % (self
.name
, self
.handle
)
107 def __cmp__(self
, other
):
108 return cmp(self
.hostname
, other
.hostname
)
114 def get(self
, key
, default
=None):
116 Get a setting from the settings dictionary.
118 return self
.settings
.get(key
, default
)
123 Fast access to the hostname.
125 return self
.get("hostname")
130 Fast access to the username.
132 return self
.get("username")
137 Fast access to the password.
139 return self
.get("password")
144 Fast access to the token.
146 return self
.get("token")
148 def __call__(self
, force
=False):
150 logger
.debug(_("Updating %s forced") % self
.hostname
)
152 # Do nothing if no update is required
153 elif not self
.requires_update
:
156 # Execute the update.
160 # In case of any errors, log the failed request and
161 # raise the exception.
162 except DDNSError
as e
:
163 self
.core
.db
.log_failure(self
.hostname
, e
)
166 logger
.info(_("Dynamic DNS update for %(hostname)s (%(provider)s) successful") % \
167 { "hostname" : self
.hostname
, "provider" : self
.name
})
168 self
.core
.db
.log_success(self
.hostname
)
171 for protocol
in self
.protocols
:
172 if self
.have_address(protocol
):
173 self
.update_protocol(protocol
)
174 elif self
.can_remove_records
:
175 self
.remove_protocol(protocol
)
177 def update_protocol(self
, proto
):
178 raise NotImplementedError
180 def remove_protocol(self
, proto
):
181 if not self
.can_remove_records
:
182 raise RuntimeError, "can_remove_records is enabled, but remove_protocol() not implemented"
184 raise NotImplementedError
187 def requires_update(self
):
188 # If the IP addresses have changed, an update is required
189 if self
.ip_address_changed(self
.protocols
):
190 logger
.debug(_("An update for %(hostname)s (%(provider)s)"
191 " is performed because of an IP address change") % \
192 { "hostname" : self
.hostname
, "provider" : self
.name
})
196 # If the holdoff time has expired, an update is required, too
197 if self
.holdoff_time_expired():
198 logger
.debug(_("An update for %(hostname)s (%(provider)s)"
199 " is performed because the holdoff time has expired") % \
200 { "hostname" : self
.hostname
, "provider" : self
.name
})
204 # Otherwise, we don't need to perform an update
205 logger
.debug(_("No update required for %(hostname)s (%(provider)s)") % \
206 { "hostname" : self
.hostname
, "provider" : self
.name
})
210 def ip_address_changed(self
, protos
):
212 Returns True if this host is already up to date
213 and does not need to change the IP address on the
217 addresses
= self
.core
.system
.resolve(self
.hostname
, proto
)
218 current_address
= self
.get_address(proto
)
220 # Handle if the system has not got any IP address from a protocol
221 # (i.e. had full dual-stack connectivity which it has not any more)
222 if current_address
is None:
223 # If addresses still exists in the DNS system and if this provider
224 # is able to remove records, we will do that.
225 if addresses
and self
.can_remove_records
:
228 # Otherwise, we cannot go on...
231 if not current_address
in addresses
:
236 def holdoff_time_expired(self
):
238 Returns true if the holdoff time has expired
239 and the host requires an update
241 # If no holdoff days is defined, we cannot go on
242 if not self
.holdoff_days
:
245 # Get the timestamp of the last successfull update
246 last_update
= self
.db
.last_update(self
.hostname
)
248 # If no timestamp has been recorded, no update has been
249 # performed. An update should be performed now.
253 # Determine when the holdoff time ends
254 holdoff_end
= last_update
+ datetime
.timedelta(days
=self
.holdoff_days
)
256 now
= datetime
.datetime
.utcnow()
258 if now
>= holdoff_end
:
259 logger
.debug("The holdoff time has expired for %s" % self
.hostname
)
262 logger
.debug("Updates for %s are held off until %s" % \
263 (self
.hostname
, holdoff_end
))
266 def send_request(self
, *args
, **kwargs
):
268 Proxy connection to the send request
271 return self
.core
.system
.send_request(*args
, **kwargs
)
273 def get_address(self
, proto
, default
=None):
275 Proxy method to get the current IP address.
277 return self
.core
.system
.get_address(proto
) or default
279 def have_address(self
, proto
):
281 Returns True if an IP address for the given protocol
284 address
= self
.get_address(proto
)
292 class DDNSProtocolDynDNS2(object):
294 This is an abstract class that implements the DynDNS updater
295 protocol version 2. As this is a popular way to update dynamic
296 DNS records, this class is supposed make the provider classes
300 # Information about the format of the request is to be found
301 # http://dyn.com/support/developers/api/perform-update/
302 # http://dyn.com/support/developers/api/return-codes/
304 # The DynDNS protocol version 2 does not allow to remove records
305 can_remove_records
= False
307 def prepare_request_data(self
, proto
):
309 "hostname" : self
.hostname
,
310 "myip" : self
.get_address(proto
),
315 def update_protocol(self
, proto
):
316 data
= self
.prepare_request_data(proto
)
318 return self
.send_request(data
)
320 def send_request(self
, data
):
321 # Send update to the server.
322 response
= DDNSProvider
.send_request(self
, self
.url
, data
=data
,
323 username
=self
.username
, password
=self
.password
)
325 # Get the full response message.
326 output
= response
.read()
328 # Handle success messages.
329 if output
.startswith("good") or output
.startswith("nochg"):
332 # Handle error codes.
333 if output
== "badauth":
334 raise DDNSAuthenticationError
335 elif output
== "abuse":
337 elif output
== "notfqdn":
338 raise DDNSRequestError(_("No valid FQDN was given."))
339 elif output
== "nohost":
340 raise DDNSRequestError(_("Specified host does not exist."))
341 elif output
== "911":
342 raise DDNSInternalServerError
343 elif output
== "dnserr":
344 raise DDNSInternalServerError(_("DNS error encountered."))
345 elif output
== "badagent":
346 raise DDNSBlockedError
348 # If we got here, some other update error happened.
349 raise DDNSUpdateError(_("Server response: %s") % output
)
352 class DDNSResponseParserXML(object):
354 This class provides a parser for XML responses which
355 will be sent by various providers. This class uses the python
356 shipped XML minidom module to walk through the XML tree and return
360 def get_xml_tag_value(self
, document
, content
):
361 # Send input to the parser.
362 xmldoc
= xml
.dom
.minidom
.parseString(document
)
364 # Get XML elements by the given content.
365 element
= xmldoc
.getElementsByTagName(content
)
367 # If no element has been found, we directly can return None.
371 # Only get the first child from an element, even there are more than one.
372 firstchild
= element
[0].firstChild
374 # Get the value of the child.
375 value
= firstchild
.nodeValue
381 class DDNSProviderAllInkl(DDNSProvider
):
382 handle
= "all-inkl.com"
383 name
= "All-inkl.com"
384 website
= "http://all-inkl.com/"
385 protocols
= ("ipv4",)
387 # There are only information provided by the vendor how to
388 # perform an update on a FRITZ Box. Grab requried informations
390 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
392 url
= "http://dyndns.kasserver.com"
393 can_remove_records
= False
396 # There is no additional data required so we directly can
398 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
)
400 # Get the full response message.
401 output
= response
.read()
403 # Handle success messages.
404 if output
.startswith("good") or output
.startswith("nochg"):
407 # If we got here, some other update error happened.
408 raise DDNSUpdateError
411 class DDNSProviderBindNsupdate(DDNSProvider
):
413 name
= "BIND nsupdate utility"
414 website
= "http://en.wikipedia.org/wiki/Nsupdate"
420 # Search if the nsupdate utility is available
421 paths
= os
.environ
.get("PATH")
423 for path
in paths
.split(":"):
424 executable
= os
.path
.join(path
, "nsupdate")
426 if os
.path
.exists(executable
):
432 scriptlet
= self
.__make
_scriptlet
()
434 # -v enables TCP hence we transfer keys and other data that may
435 # exceed the size of one packet.
436 # -t sets the timeout
437 command
= ["nsupdate", "-v", "-t", "60"]
439 p
= subprocess
.Popen(command
, shell
=True,
440 stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
442 stdout
, stderr
= p
.communicate(scriptlet
)
444 if p
.returncode
== 0:
447 raise DDNSError("nsupdate terminated with error code: %s\n %s" % (p
.returncode
, stderr
))
449 def __make_scriptlet(self
):
452 # Set a different server the update is sent to.
453 server
= self
.get("server", None)
455 scriptlet
.append("server %s" % server
)
457 # Set the DNS zone the host should be added to.
458 zone
= self
.get("zone", None)
460 scriptlet
.append("zone %s" % zone
)
462 key
= self
.get("key", None)
464 secret
= self
.get("secret")
466 scriptlet
.append("key %s %s" % (key
, secret
))
468 ttl
= self
.get("ttl", self
.DEFAULT_TTL
)
470 # Perform an update for each supported protocol.
471 for rrtype
, proto
in (("AAAA", "ipv6"), ("A", "ipv4")):
472 address
= self
.get_address(proto
)
476 scriptlet
.append("update delete %s. %s" % (self
.hostname
, rrtype
))
477 scriptlet
.append("update add %s. %s %s %s" % \
478 (self
.hostname
, ttl
, rrtype
, address
))
480 # Send the actions to the server.
481 scriptlet
.append("send")
482 scriptlet
.append("quit")
484 logger
.debug(_("Scriptlet:"))
485 for line
in scriptlet
:
486 # Masquerade the line with the secret key.
487 if line
.startswith("key"):
488 line
= "key **** ****"
490 logger
.debug(" %s" % line
)
492 return "\n".join(scriptlet
)
495 class DDNSProviderDHS(DDNSProvider
):
497 name
= "DHS International"
498 website
= "http://dhs.org/"
499 protocols
= ("ipv4",)
501 # No information about the used update api provided on webpage,
502 # grabed from source code of ez-ipudate.
504 url
= "http://members.dhs.org/nic/hosts"
505 can_remove_records
= False
507 def update_protocol(self
, proto
):
509 "domain" : self
.hostname
,
510 "ip" : self
.get_address(proto
),
512 "hostcmdstage" : "2",
516 # Send update to the server.
517 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
520 # Handle success messages.
521 if response
.code
== 200:
524 # If we got here, some other update error happened.
525 raise DDNSUpdateError
528 class DDNSProviderDNSpark(DDNSProvider
):
529 handle
= "dnspark.com"
531 website
= "http://dnspark.com/"
532 protocols
= ("ipv4",)
534 # Informations to the used api can be found here:
535 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
537 url
= "https://control.dnspark.com/api/dynamic/update.php"
538 can_remove_records
= False
540 def update_protocol(self
, proto
):
542 "domain" : self
.hostname
,
543 "ip" : self
.get_address(proto
),
546 # Send update to the server.
547 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
550 # Get the full response message.
551 output
= response
.read()
553 # Handle success messages.
554 if output
.startswith("ok") or output
.startswith("nochange"):
557 # Handle error codes.
558 if output
== "unauth":
559 raise DDNSAuthenticationError
560 elif output
== "abuse":
562 elif output
== "blocked":
563 raise DDNSBlockedError
564 elif output
== "nofqdn":
565 raise DDNSRequestError(_("No valid FQDN was given."))
566 elif output
== "nohost":
567 raise DDNSRequestError(_("Invalid hostname specified."))
568 elif output
== "notdyn":
569 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
570 elif output
== "invalid":
571 raise DDNSRequestError(_("Invalid IP address has been sent."))
573 # If we got here, some other update error happened.
574 raise DDNSUpdateError
577 class DDNSProviderDtDNS(DDNSProvider
):
580 website
= "http://dtdns.com/"
581 protocols
= ("ipv4",)
583 # Information about the format of the HTTPS request is to be found
584 # http://www.dtdns.com/dtsite/updatespec
586 url
= "https://www.dtdns.com/api/autodns.cfm"
587 can_remove_records
= False
589 def update_protocol(self
, proto
):
591 "ip" : self
.get_address(proto
),
592 "id" : self
.hostname
,
596 # Send update to the server.
597 response
= self
.send_request(self
.url
, data
=data
)
599 # Get the full response message.
600 output
= response
.read()
602 # Remove all leading and trailing whitespace.
603 output
= output
.strip()
605 # Handle success messages.
606 if "now points to" in output
:
609 # Handle error codes.
610 if output
== "No hostname to update was supplied.":
611 raise DDNSRequestError(_("No hostname specified."))
613 elif output
== "The hostname you supplied is not valid.":
614 raise DDNSRequestError(_("Invalid hostname specified."))
616 elif output
== "The password you supplied is not valid.":
617 raise DDNSAuthenticationError
619 elif output
== "Administration has disabled this account.":
620 raise DDNSRequestError(_("Account has been disabled."))
622 elif output
== "Illegal character in IP.":
623 raise DDNSRequestError(_("Invalid IP address has been sent."))
625 elif output
== "Too many failed requests.":
626 raise DDNSRequestError(_("Too many failed requests."))
628 # If we got here, some other update error happened.
629 raise DDNSUpdateError
632 class DDNSProviderDynDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
633 handle
= "dyndns.org"
635 website
= "http://dyn.com/dns/"
636 protocols
= ("ipv4",)
638 # Information about the format of the request is to be found
639 # http://http://dyn.com/support/developers/api/perform-update/
640 # http://dyn.com/support/developers/api/return-codes/
642 url
= "https://members.dyndns.org/nic/update"
645 class DDNSProviderDynU(DDNSProtocolDynDNS2
, DDNSProvider
):
648 website
= "http://dynu.com/"
649 protocols
= ("ipv6", "ipv4",)
651 # Detailed information about the request and response codes
652 # are available on the providers webpage.
653 # http://dynu.com/Default.aspx?page=dnsapi
655 url
= "https://api.dynu.com/nic/update"
657 # DynU sends the IPv6 and IPv4 address in one request
660 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, "ipv4")
662 # This one supports IPv6
663 myipv6
= self
.get_address("ipv6")
665 # Add update information if we have an IPv6 address.
667 data
["myipv6"] = myipv6
669 self
.send_request(data
)
672 class DDNSProviderEasyDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
673 handle
= "easydns.com"
675 website
= "http://www.easydns.com/"
676 protocols
= ("ipv4",)
678 # There is only some basic documentation provided by the vendor,
679 # also searching the web gain very poor results.
680 # http://mediawiki.easydns.com/index.php/Dynamic_DNS
682 url
= "http://api.cp.easydns.com/dyn/tomato.php"
685 class DDNSProviderDomopoli(DDNSProtocolDynDNS2
, DDNSProvider
):
686 handle
= "domopoli.de"
688 website
= "http://domopoli.de/"
689 protocols
= ("ipv4",)
691 # https://www.domopoli.de/?page=howto#DynDns_start
693 url
= "http://dyndns.domopoli.de/nic/update"
696 class DDNSProviderDynsNet(DDNSProvider
):
699 website
= "http://www.dyns.net/"
700 protocols
= ("ipv4",)
701 can_remove_records
= False
703 # There is very detailed informatio about how to send the update request and
704 # the possible response codes. (Currently we are using the v1.1 proto)
705 # http://www.dyns.net/documentation/technical/protocol/
707 url
= "http://www.dyns.net/postscript011.php"
709 def update_protocol(self
, proto
):
711 "ip" : self
.get_address(proto
),
712 "host" : self
.hostname
,
713 "username" : self
.username
,
714 "password" : self
.password
,
717 # Send update to the server.
718 response
= self
.send_request(self
.url
, data
=data
)
720 # Get the full response message.
721 output
= response
.read()
723 # Handle success messages.
724 if output
.startswith("200"):
727 # Handle error codes.
728 if output
.startswith("400"):
729 raise DDNSRequestError(_("Malformed request has been sent."))
730 elif output
.startswith("401"):
731 raise DDNSAuthenticationError
732 elif output
.startswith("402"):
733 raise DDNSRequestError(_("Too frequent update requests have been sent."))
734 elif output
.startswith("403"):
735 raise DDNSInternalServerError
737 # If we got here, some other update error happened.
738 raise DDNSUpdateError(_("Server response: %s") % output
)
741 class DDNSProviderEnomCom(DDNSResponseParserXML
, DDNSProvider
):
744 website
= "http://www.enom.com/"
745 protocols
= ("ipv4",)
747 # There are very detailed information about how to send an update request and
749 # http://www.enom.com/APICommandCatalog/
751 url
= "https://dynamic.name-services.com/interface.asp"
752 can_remove_records
= False
754 def update_protocol(self
, proto
):
756 "command" : "setdnshost",
757 "responsetype" : "xml",
758 "address" : self
.get_address(proto
),
759 "domainpassword" : self
.password
,
760 "zone" : self
.hostname
763 # Send update to the server.
764 response
= self
.send_request(self
.url
, data
=data
)
766 # Get the full response message.
767 output
= response
.read()
769 # Handle success messages.
770 if self
.get_xml_tag_value(output
, "ErrCount") == "0":
773 # Handle error codes.
774 errorcode
= self
.get_xml_tag_value(output
, "ResponseNumber")
776 if errorcode
== "304155":
777 raise DDNSAuthenticationError
778 elif errorcode
== "304153":
779 raise DDNSRequestError(_("Domain not found."))
781 # If we got here, some other update error happened.
782 raise DDNSUpdateError
785 class DDNSProviderEntryDNS(DDNSProvider
):
786 handle
= "entrydns.net"
788 website
= "http://entrydns.net/"
789 protocols
= ("ipv4",)
791 # Some very tiny details about their so called "Simple API" can be found
792 # here: https://entrydns.net/help
793 url
= "https://entrydns.net/records/modify"
794 can_remove_records
= False
796 def update_protocol(self
, proto
):
798 "ip" : self
.get_address(proto
),
801 # Add auth token to the update url.
802 url
= "%s/%s" % (self
.url
, self
.token
)
804 # Send update to the server.
806 response
= self
.send_request(url
, data
=data
)
809 except urllib2
.HTTPError
, e
:
811 raise DDNSAuthenticationError
814 raise DDNSRequestError(_("An invalid IP address was submitted"))
818 # Handle success messages.
819 if response
.code
== 200:
822 # If we got here, some other update error happened.
823 raise DDNSUpdateError
826 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider
):
827 handle
= "freedns.afraid.org"
828 name
= "freedns.afraid.org"
829 website
= "http://freedns.afraid.org/"
831 # No information about the request or response could be found on the vendor
832 # page. All used values have been collected by testing.
833 url
= "https://freedns.afraid.org/dynamic/update.php"
834 can_remove_records
= False
836 def update_protocol(self
, proto
):
838 "address" : self
.get_address(proto
),
841 # Add auth token to the update url.
842 url
= "%s?%s" % (self
.url
, self
.token
)
844 # Send update to the server.
845 response
= self
.send_request(url
, data
=data
)
847 # Get the full response message.
848 output
= response
.read()
850 # Handle success messages.
851 if output
.startswith("Updated") or "has not changed" in output
:
854 # Handle error codes.
855 if output
== "ERROR: Unable to locate this record":
856 raise DDNSAuthenticationError
857 elif "is an invalid IP address" in output
:
858 raise DDNSRequestError(_("Invalid IP address has been sent."))
860 # If we got here, some other update error happened.
861 raise DDNSUpdateError
864 class DDNSProviderLightningWireLabs(DDNSProvider
):
865 handle
= "dns.lightningwirelabs.com"
866 name
= "Lightning Wire Labs DNS Service"
867 website
= "http://dns.lightningwirelabs.com/"
869 # Information about the format of the HTTPS request is to be found
870 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
872 url
= "https://dns.lightningwirelabs.com/update"
876 "hostname" : self
.hostname
,
877 "address6" : self
.get_address("ipv6", "-"),
878 "address4" : self
.get_address("ipv4", "-"),
881 # Check if a token has been set.
883 data
["token"] = self
.token
885 # Check for username and password.
886 elif self
.username
and self
.password
:
888 "username" : self
.username
,
889 "password" : self
.password
,
892 # Raise an error if no auth details are given.
894 raise DDNSConfigurationError
896 # Send update to the server.
897 response
= self
.send_request(self
.url
, data
=data
)
899 # Handle success messages.
900 if response
.code
== 200:
903 # If we got here, some other update error happened.
904 raise DDNSUpdateError
907 class DDNSProviderMyOnlinePortal(DDNSProtocolDynDNS2
, DDNSProvider
):
908 handle
= "myonlineportal.net"
909 name
= "myonlineportal.net"
910 website
= "https:/myonlineportal.net/"
912 # Information about the request and response can be obtained here:
913 # https://myonlineportal.net/howto_dyndns
915 url
= "https://myonlineportal.net/updateddns"
917 def prepare_request_data(self
, proto
):
919 "hostname" : self
.hostname
,
920 "ip" : self
.get_address(proto
),
926 class DDNSProviderNamecheap(DDNSResponseParserXML
, DDNSProvider
):
927 handle
= "namecheap.com"
929 website
= "http://namecheap.com"
930 protocols
= ("ipv4",)
932 # Information about the format of the HTTP request is to be found
933 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
934 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
936 url
= "https://dynamicdns.park-your-domain.com/update"
937 can_remove_records
= False
939 def update_protocol(self
, proto
):
940 # Namecheap requires the hostname splitted into a host and domain part.
941 host
, domain
= self
.hostname
.split(".", 1)
944 "ip" : self
.get_address(proto
),
945 "password" : self
.password
,
950 # Send update to the server.
951 response
= self
.send_request(self
.url
, data
=data
)
953 # Get the full response message.
954 output
= response
.read()
956 # Handle success messages.
957 if self
.get_xml_tag_value(output
, "IP") == address
:
960 # Handle error codes.
961 errorcode
= self
.get_xml_tag_value(output
, "ResponseNumber")
963 if errorcode
== "304156":
964 raise DDNSAuthenticationError
965 elif errorcode
== "316153":
966 raise DDNSRequestError(_("Domain not found."))
967 elif errorcode
== "316154":
968 raise DDNSRequestError(_("Domain not active."))
969 elif errorcode
in ("380098", "380099"):
970 raise DDNSInternalServerError
972 # If we got here, some other update error happened.
973 raise DDNSUpdateError
976 class DDNSProviderNOIP(DDNSProtocolDynDNS2
, DDNSProvider
):
979 website
= "http://www.no-ip.com/"
980 protocols
= ("ipv4",)
982 # Information about the format of the HTTP request is to be found
983 # here: http://www.no-ip.com/integrate/request and
984 # here: http://www.no-ip.com/integrate/response
986 url
= "http://dynupdate.no-ip.com/nic/update"
988 def prepare_request_data(self
, proto
):
989 assert proto
== "ipv4"
992 "hostname" : self
.hostname
,
993 "address" : self
.get_address(proto
),
999 class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2
, DDNSProvider
):
1000 handle
= "nsupdate.info"
1001 name
= "nsupdate.info"
1002 website
= "http://nsupdate.info/"
1003 protocols
= ("ipv6", "ipv4",)
1005 # Information about the format of the HTTP request can be found
1006 # after login on the provider user interface and here:
1007 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
1009 url
= "https://nsupdate.info/nic/update"
1011 # TODO nsupdate.info can actually do this, but the functionality
1012 # has not been implemented here, yet.
1013 can_remove_records
= False
1015 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
1016 # and for the password a so called secret.
1019 return self
.get("hostname")
1023 return self
.token
or self
.get("secret")
1025 def prepare_request_data(self
, proto
):
1027 "myip" : self
.get_address(proto
),
1033 class DDNSProviderOpenDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1034 handle
= "opendns.com"
1036 website
= "http://www.opendns.com"
1038 # Detailed information about the update request and possible
1039 # response codes can be obtained from here:
1040 # https://support.opendns.com/entries/23891440
1042 url
= "https://updates.opendns.com/nic/update"
1044 def prepare_request_data(self
, proto
):
1046 "hostname" : self
.hostname
,
1047 "myip" : self
.get_address(proto
),
1053 class DDNSProviderOVH(DDNSProtocolDynDNS2
, DDNSProvider
):
1056 website
= "http://www.ovh.com/"
1057 protocols
= ("ipv4",)
1059 # OVH only provides very limited information about how to
1060 # update a DynDNS host. They only provide the update url
1061 # on the their german subpage.
1063 # http://hilfe.ovh.de/DomainDynHost
1065 url
= "https://www.ovh.com/nic/update"
1067 def prepare_request_data(self
, proto
):
1068 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1070 "system" : "dyndns",
1076 class DDNSProviderRegfish(DDNSProvider
):
1077 handle
= "regfish.com"
1078 name
= "Regfish GmbH"
1079 website
= "http://www.regfish.com/"
1081 # A full documentation to the providers api can be found here
1082 # but is only available in german.
1083 # https://www.regfish.de/domains/dyndns/dokumentation
1085 url
= "https://dyndns.regfish.de/"
1086 can_remove_records
= False
1090 "fqdn" : self
.hostname
,
1093 # Check if we update an IPv6 address.
1094 address6
= self
.get_address("ipv6")
1096 data
["ipv6"] = address6
1098 # Check if we update an IPv4 address.
1099 address4
= self
.get_address("ipv4")
1101 data
["ipv4"] = address4
1103 # Raise an error if none address is given.
1104 if not data
.has_key("ipv6") and not data
.has_key("ipv4"):
1105 raise DDNSConfigurationError
1107 # Check if a token has been set.
1109 data
["token"] = self
.token
1111 # Raise an error if no token and no useranem and password
1113 elif not self
.username
and not self
.password
:
1114 raise DDNSConfigurationError(_("No Auth details specified."))
1116 # HTTP Basic Auth is only allowed if no token is used.
1118 # Send update to the server.
1119 response
= self
.send_request(self
.url
, data
=data
)
1121 # Send update to the server.
1122 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
1125 # Get the full response message.
1126 output
= response
.read()
1128 # Handle success messages.
1129 if "100" in output
or "101" in output
:
1132 # Handle error codes.
1133 if "401" or "402" in output
:
1134 raise DDNSAuthenticationError
1135 elif "408" in output
:
1136 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
1137 elif "409" in output
:
1138 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
1139 elif "412" in output
:
1140 raise DDNSRequestError(_("No valid FQDN was given."))
1141 elif "414" in output
:
1142 raise DDNSInternalServerError
1144 # If we got here, some other update error happened.
1145 raise DDNSUpdateError
1148 class DDNSProviderSelfhost(DDNSProtocolDynDNS2
, DDNSProvider
):
1149 handle
= "selfhost.de"
1150 name
= "Selfhost.de"
1151 website
= "http://www.selfhost.de/"
1152 protocols
= ("ipv4",)
1154 url
= "https://carol.selfhost.de/nic/update"
1156 def prepare_request_data(self
, proto
):
1157 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1165 class DDNSProviderSPDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1166 handle
= "spdns.org"
1168 website
= "http://spdns.org/"
1170 # Detailed information about request and response codes are provided
1171 # by the vendor. They are using almost the same mechanism and status
1172 # codes as dyndns.org so we can inherit all those stuff.
1174 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
1175 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
1177 url
= "https://update.spdns.de/nic/update"
1181 return self
.get("username") or self
.hostname
1185 return self
.get("username") or self
.token
1188 class DDNSProviderStrato(DDNSProtocolDynDNS2
, DDNSProvider
):
1189 handle
= "strato.com"
1191 website
= "http:/www.strato.com/"
1192 protocols
= ("ipv4",)
1194 # Information about the request and response can be obtained here:
1195 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
1197 url
= "https://dyndns.strato.com/nic/update"
1200 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1201 handle
= "twodns.de"
1203 website
= "http://www.twodns.de"
1204 protocols
= ("ipv4",)
1206 # Detailed information about the request can be found here
1207 # http://twodns.de/en/faqs
1208 # http://twodns.de/en/api
1210 url
= "https://update.twodns.de/update"
1212 def prepare_request_data(self
, proto
):
1213 assert proto
== "ipv4"
1216 "ip" : self
.get_address(proto
),
1217 "hostname" : self
.hostname
1223 class DDNSProviderUdmedia(DDNSProtocolDynDNS2
, DDNSProvider
):
1224 handle
= "udmedia.de"
1225 name
= "Udmedia GmbH"
1226 website
= "http://www.udmedia.de"
1227 protocols
= ("ipv4",)
1229 # Information about the request can be found here
1230 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1232 url
= "https://www.udmedia.de/nic/update"
1235 class DDNSProviderVariomedia(DDNSProtocolDynDNS2
, DDNSProvider
):
1236 handle
= "variomedia.de"
1238 website
= "http://www.variomedia.de/"
1239 protocols
= ("ipv6", "ipv4",)
1241 # Detailed information about the request can be found here
1242 # https://dyndns.variomedia.de/
1244 url
= "https://dyndns.variomedia.de/nic/update"
1246 def prepare_request_data(self
, proto
):
1248 "hostname" : self
.hostname
,
1249 "myip" : self
.get_address(proto
),
1255 class DDNSProviderZoneedit(DDNSProtocolDynDNS2
, DDNSProvider
):
1256 handle
= "zoneedit.com"
1258 website
= "http://www.zoneedit.com"
1259 protocols
= ("ipv4",)
1261 # Detailed information about the request and the response codes can be
1263 # http://www.zoneedit.com/doc/api/other.html
1264 # http://www.zoneedit.com/faq.html
1266 url
= "https://dynamic.zoneedit.com/auth/dynamic.html"
1268 def update_protocol(self
, proto
):
1270 "dnsto" : self
.get_address(proto
),
1271 "host" : self
.hostname
1274 # Send update to the server.
1275 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
1278 # Get the full response message.
1279 output
= response
.read()
1281 # Handle success messages.
1282 if output
.startswith("<SUCCESS"):
1285 # Handle error codes.
1286 if output
.startswith("invalid login"):
1287 raise DDNSAuthenticationError
1288 elif output
.startswith("<ERROR CODE=\"704\""):
1289 raise DDNSRequestError(_("No valid FQDN was given."))
1290 elif output
.startswith("<ERROR CODE=\"702\""):
1291 raise DDNSInternalServerError
1293 # If we got here, some other update error happened.
1294 raise DDNSUpdateError
1297 class DDNSProviderZZZZ(DDNSProvider
):
1300 website
= "https://zzzz.io"
1301 protocols
= ("ipv6", "ipv4",)
1303 # Detailed information about the update request can be found here:
1304 # https://zzzz.io/faq/
1306 # Details about the possible response codes have been provided in the bugtracker:
1307 # https://bugzilla.ipfire.org/show_bug.cgi?id=10584#c2
1309 url
= "https://zzzz.io/api/v1/update"
1310 can_remove_records
= False
1312 def update_protocol(self
, proto
):
1314 "ip" : self
.get_address(proto
),
1315 "token" : self
.token
,
1319 data
["type"] = "aaaa"
1321 # zzzz uses the host from the full hostname as part
1322 # of the update url.
1323 host
, domain
= self
.hostname
.split(".", 1)
1325 # Add host value to the update url.
1326 url
= "%s/%s" % (self
.url
, host
)
1328 # Send update to the server.
1330 response
= self
.send_request(url
, data
=data
)
1332 # Handle error codes.
1333 except DDNSNotFound
:
1334 raise DDNSRequestError(_("Invalid hostname specified"))
1336 # Handle success messages.
1337 if response
.code
== 200:
1340 # If we got here, some other update error happened.
1341 raise DDNSUpdateError