]>
git.ipfire.org Git - ddns.git/blob - src/ddns/providers.py
2 ###############################################################################
4 # ddns - A dynamic DNS client for IPFire #
5 # Copyright (C) 2012 IPFire development team #
7 # This program is free software: you can redistribute it and/or modify #
8 # it under the terms of the GNU General Public License as published by #
9 # the Free Software Foundation, either version 3 of the License, or #
10 # (at your option) any later version. #
12 # This program is distributed in the hope that it will be useful, #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15 # GNU General Public License for more details. #
17 # You should have received a copy of the GNU General Public License #
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
20 ###############################################################################
24 import xml
.dom
.minidom
28 # Import all possible exception types.
31 logger
= logging
.getLogger("ddns.providers")
34 class DDNSProvider(object):
36 # A short string that uniquely identifies
40 # The full name of the provider.
43 # A weburl to the homepage of the provider.
44 # (Where to register a new account?)
47 # A list of supported protocols.
48 "protocols" : ["ipv6", "ipv4"],
53 def __init__(self
, core
, **settings
):
56 # Copy a set of default settings and
57 # update them by those from the configuration file.
58 self
.settings
= self
.DEFAULT_SETTINGS
.copy()
59 self
.settings
.update(settings
)
62 return "<DDNS Provider %s (%s)>" % (self
.name
, self
.handle
)
64 def __cmp__(self
, other
):
65 return cmp(self
.hostname
, other
.hostname
)
70 Returns the name of the provider.
72 return self
.INFO
.get("name")
77 Returns the website URL of the provider
78 or None if that is not available.
80 return self
.INFO
.get("website", None)
85 Returns the handle of this provider.
87 return self
.INFO
.get("handle")
89 def get(self
, key
, default
=None):
91 Get a setting from the settings dictionary.
93 return self
.settings
.get(key
, default
)
98 Fast access to the hostname.
100 return self
.get("hostname")
105 Fast access to the username.
107 return self
.get("username")
112 Fast access to the password.
114 return self
.get("password")
118 return self
.INFO
.get("protocols")
123 Fast access to the token.
125 return self
.get("token")
127 def __call__(self
, force
=False):
129 logger
.info(_("Updating %s forced") % self
.hostname
)
131 # Check if we actually need to update this host.
132 elif self
.is_uptodate(self
.protocols
):
133 logger
.info(_("%s is already up to date") % self
.hostname
)
136 # Execute the update.
140 raise NotImplementedError
142 def is_uptodate(self
, protos
):
144 Returns True if this host is already up to date
145 and does not need to change the IP address on the
149 addresses
= self
.core
.system
.resolve(self
.hostname
, proto
)
151 current_address
= self
.get_address(proto
)
153 # If no addresses for the given protocol exist, we
155 if current_address
is None and not addresses
:
158 if not current_address
in addresses
:
163 def send_request(self
, *args
, **kwargs
):
165 Proxy connection to the send request
168 return self
.core
.system
.send_request(*args
, **kwargs
)
170 def get_address(self
, proto
):
172 Proxy method to get the current IP address.
174 return self
.core
.system
.get_address(proto
)
177 class DDNSProviderAllInkl(DDNSProvider
):
179 "handle" : "all-inkl.com",
180 "name" : "All-inkl.com",
181 "website" : "http://all-inkl.com/",
182 "protocols" : ["ipv4",]
185 # There are only information provided by the vendor how to
186 # perform an update on a FRITZ Box. Grab requried informations
188 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
190 url
= "http://dyndns.kasserver.com"
193 # There is no additional data required so we directly can
195 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
)
197 # Get the full response message.
198 output
= response
.read()
200 # Handle success messages.
201 if output
.startswith("good") or output
.startswith("nochg"):
204 # If we got here, some other update error happened.
205 raise DDNSUpdateError
208 class DDNSProviderDHS(DDNSProvider
):
210 "handle" : "dhs.org",
211 "name" : "DHS International",
212 "website" : "http://dhs.org/",
213 "protocols" : ["ipv4",]
216 # No information about the used update api provided on webpage,
217 # grabed from source code of ez-ipudate.
218 url
= "http://members.dhs.org/nic/hosts"
222 "domain" : self
.hostname
,
223 "ip" : self
.get_address("ipv4"),
225 "hostcmdstage" : "2",
229 # Send update to the server.
230 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
233 # Handle success messages.
234 if response
.code
== 200:
237 # If we got here, some other update error happened.
238 raise DDNSUpdateError
241 class DDNSProviderDNSpark(DDNSProvider
):
243 "handle" : "dnspark.com",
245 "website" : "http://dnspark.com/",
246 "protocols" : ["ipv4",]
249 # Informations to the used api can be found here:
250 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
251 url
= "https://control.dnspark.com/api/dynamic/update.php"
255 "domain" : self
.hostname
,
256 "ip" : self
.get_address("ipv4"),
259 # Send update to the server.
260 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
263 # Get the full response message.
264 output
= response
.read()
266 # Handle success messages.
267 if output
.startswith("ok") or output
.startswith("nochange"):
270 # Handle error codes.
271 if output
== "unauth":
272 raise DDNSAuthenticationError
273 elif output
== "abuse":
275 elif output
== "blocked":
276 raise DDNSBlockedError
277 elif output
== "nofqdn":
278 raise DDNSRequestError(_("No valid FQDN was given."))
279 elif output
== "nohost":
280 raise DDNSRequestError(_("Invalid hostname specified."))
281 elif output
== "notdyn":
282 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
283 elif output
== "invalid":
284 raise DDNSRequestError(_("Invalid IP address has been sent."))
286 # If we got here, some other update error happened.
287 raise DDNSUpdateError
290 class DDNSProviderDtDNS(DDNSProvider
):
292 "handle" : "dtdns.com",
294 "website" : "http://dtdns.com/",
295 "protocols" : ["ipv4",]
298 # Information about the format of the HTTPS request is to be found
299 # http://www.dtdns.com/dtsite/updatespec
300 url
= "https://www.dtdns.com/api/autodns.cfm"
304 "ip" : self
.get_address("ipv4"),
305 "id" : self
.hostname
,
309 # Send update to the server.
310 response
= self
.send_request(self
.url
, data
=data
)
312 # Get the full response message.
313 output
= response
.read()
315 # Remove all leading and trailing whitespace.
316 output
= output
.strip()
318 # Handle success messages.
319 if "now points to" in output
:
322 # Handle error codes.
323 if output
== "No hostname to update was supplied.":
324 raise DDNSRequestError(_("No hostname specified."))
326 elif output
== "The hostname you supplied is not valid.":
327 raise DDNSRequestError(_("Invalid hostname specified."))
329 elif output
== "The password you supplied is not valid.":
330 raise DDNSAuthenticationError
332 elif output
== "Administration has disabled this account.":
333 raise DDNSRequestError(_("Account has been disabled."))
335 elif output
== "Illegal character in IP.":
336 raise DDNSRequestError(_("Invalid IP address has been sent."))
338 elif output
== "Too many failed requests.":
339 raise DDNSRequestError(_("Too many failed requests."))
341 # If we got here, some other update error happened.
342 raise DDNSUpdateError
345 class DDNSProviderDynDNS(DDNSProvider
):
347 "handle" : "dyndns.org",
349 "website" : "http://dyn.com/dns/",
350 "protocols" : ["ipv4",]
353 # Information about the format of the request is to be found
354 # http://http://dyn.com/support/developers/api/perform-update/
355 # http://dyn.com/support/developers/api/return-codes/
356 url
= "https://members.dyndns.org/nic/update"
358 def _prepare_request_data(self
):
360 "hostname" : self
.hostname
,
361 "myip" : self
.get_address("ipv4"),
367 data
= self
._prepare
_request
_data
()
369 # Send update to the server.
370 response
= self
.send_request(self
.url
, data
=data
,
371 username
=self
.username
, password
=self
.password
)
373 # Get the full response message.
374 output
= response
.read()
376 # Handle success messages.
377 if output
.startswith("good") or output
.startswith("nochg"):
380 # Handle error codes.
381 if output
== "badauth":
382 raise DDNSAuthenticationError
383 elif output
== "aduse":
385 elif output
== "notfqdn":
386 raise DDNSRequestError(_("No valid FQDN was given."))
387 elif output
== "nohost":
388 raise DDNSRequestError(_("Specified host does not exist."))
389 elif output
== "911":
390 raise DDNSInternalServerError
391 elif output
== "dnserr":
392 raise DDNSInternalServerError(_("DNS error encountered."))
394 # If we got here, some other update error happened.
395 raise DDNSUpdateError(_("Server response: %s") % output
)
398 class DDNSProviderDynU(DDNSProviderDynDNS
):
400 "handle" : "dynu.com",
402 "website" : "http://dynu.com/",
403 "protocols" : ["ipv6", "ipv4",]
407 # Detailed information about the request and response codes
408 # are available on the providers webpage.
409 # http://dynu.com/Default.aspx?page=dnsapi
411 url
= "https://api.dynu.com/nic/update"
413 def _prepare_request_data(self
):
414 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
416 # This one supports IPv6
418 "myipv6" : self
.get_address("ipv6"),
424 class DDNSProviderEasyDNS(DDNSProviderDynDNS
):
426 "handle" : "easydns.com",
428 "website" : "http://www.easydns.com/",
429 "protocols" : ["ipv4",]
432 # There is only some basic documentation provided by the vendor,
433 # also searching the web gain very poor results.
434 # http://mediawiki.easydns.com/index.php/Dynamic_DNS
436 url
= "http://api.cp.easydns.com/dyn/tomato.php"
439 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider
):
441 "handle" : "freedns.afraid.org",
442 "name" : "freedns.afraid.org",
443 "website" : "http://freedns.afraid.org/",
444 "protocols" : ["ipv6", "ipv4",]
447 # No information about the request or response could be found on the vendor
448 # page. All used values have been collected by testing.
449 url
= "https://freedns.afraid.org/dynamic/update.php"
453 return self
.get("proto")
456 address
= self
.get_address(self
.proto
)
462 # Add auth token to the update url.
463 url
= "%s?%s" % (self
.url
, self
.token
)
465 # Send update to the server.
466 response
= self
.send_request(url
, data
=data
)
468 if output
.startswith("Updated") or "has not changed" in output
:
471 # Handle error codes.
472 if output
== "ERROR: Unable to locate this record":
473 raise DDNSAuthenticationError
474 elif "is an invalid IP address" in output
:
475 raise DDNSRequestError(_("Invalid IP address has been sent."))
478 class DDNSProviderLightningWireLabs(DDNSProvider
):
480 "handle" : "dns.lightningwirelabs.com",
481 "name" : "Lightning Wire Labs",
482 "website" : "http://dns.lightningwirelabs.com/",
483 "protocols" : ["ipv6", "ipv4",]
486 # Information about the format of the HTTPS request is to be found
487 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
488 url
= "https://dns.lightningwirelabs.com/update"
492 "hostname" : self
.hostname
,
495 # Check if we update an IPv6 address.
496 address6
= self
.get_address("ipv6")
498 data
["address6"] = address6
500 # Check if we update an IPv4 address.
501 address4
= self
.get_address("ipv4")
503 data
["address4"] = address4
505 # Raise an error if none address is given.
506 if not data
.has_key("address6") and not data
.has_key("address4"):
507 raise DDNSConfigurationError
509 # Check if a token has been set.
511 data
["token"] = self
.token
513 # Check for username and password.
514 elif self
.username
and self
.password
:
516 "username" : self
.username
,
517 "password" : self
.password
,
520 # Raise an error if no auth details are given.
522 raise DDNSConfigurationError
524 # Send update to the server.
525 response
= self
.send_request(self
.url
, data
=data
)
527 # Handle success messages.
528 if response
.code
== 200:
531 # If we got here, some other update error happened.
532 raise DDNSUpdateError
535 class DDNSProviderNamecheap(DDNSProvider
):
537 "handle" : "namecheap.com",
538 "name" : "Namecheap",
539 "website" : "http://namecheap.com",
540 "protocols" : ["ipv4",]
543 # Information about the format of the HTTP request is to be found
544 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
545 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
547 url
= "https://dynamicdns.park-your-domain.com/update"
549 def parse_xml(self
, document
, content
):
550 # Send input to the parser.
551 xmldoc
= xml
.dom
.minidom
.parseString(document
)
553 # Get XML elements by the given content.
554 element
= xmldoc
.getElementsByTagName(content
)
556 # If no element has been found, we directly can return None.
560 # Only get the first child from an element, even there are more than one.
561 firstchild
= element
[0].firstChild
563 # Get the value of the child.
564 value
= firstchild
.nodeValue
570 # Namecheap requires the hostname splitted into a host and domain part.
571 host
, domain
= self
.hostname
.split(".", 1)
574 "ip" : self
.get_address("ipv4"),
575 "password" : self
.password
,
580 # Send update to the server.
581 response
= self
.send_request(self
.url
, data
=data
)
583 # Get the full response message.
584 output
= response
.read()
586 # Handle success messages.
587 if self
.parse_xml(output
, "IP") == self
.get_address("ipv4"):
590 # Handle error codes.
591 errorcode
= self
.parse_xml(output
, "ResponseNumber")
593 if errorcode
== "304156":
594 raise DDNSAuthenticationError
595 elif errorcode
== "316153":
596 raise DDNSRequestError(_("Domain not found."))
597 elif errorcode
== "316154":
598 raise DDNSRequestError(_("Domain not active."))
599 elif errorcode
in ("380098", "380099"):
600 raise DDNSInternalServerError
602 # If we got here, some other update error happened.
603 raise DDNSUpdateError
606 class DDNSProviderNOIP(DDNSProviderDynDNS
):
608 "handle" : "no-ip.com",
610 "website" : "http://www.no-ip.com/",
611 "protocols" : ["ipv4",]
614 # Information about the format of the HTTP request is to be found
615 # here: http://www.no-ip.com/integrate/request and
616 # here: http://www.no-ip.com/integrate/response
618 url
= "http://dynupdate.no-ip.com/nic/update"
620 def _prepare_request_data(self
):
622 "hostname" : self
.hostname
,
623 "address" : self
.get_address("ipv4"),
629 class DDNSProviderOVH(DDNSProviderDynDNS
):
631 "handle" : "ovh.com",
633 "website" : "http://www.ovh.com/",
634 "protocols" : ["ipv4",]
637 # OVH only provides very limited information about how to
638 # update a DynDNS host. They only provide the update url
639 # on the their german subpage.
641 # http://hilfe.ovh.de/DomainDynHost
643 url
= "https://www.ovh.com/nic/update"
645 def _prepare_request_data(self
):
646 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
654 class DDNSProviderRegfish(DDNSProvider
):
656 "handle" : "regfish.com",
657 "name" : "Regfish GmbH",
658 "website" : "http://www.regfish.com/",
659 "protocols" : ["ipv6", "ipv4",]
662 # A full documentation to the providers api can be found here
663 # but is only available in german.
664 # https://www.regfish.de/domains/dyndns/dokumentation
666 url
= "https://dyndns.regfish.de/"
670 "fqdn" : self
.hostname
,
673 # Check if we update an IPv6 address.
674 address6
= self
.get_address("ipv6")
676 data
["ipv6"] = address6
678 # Check if we update an IPv4 address.
679 address4
= self
.get_address("ipv4")
681 data
["ipv4"] = address4
683 # Raise an error if none address is given.
684 if not data
.has_key("ipv6") and not data
.has_key("ipv4"):
685 raise DDNSConfigurationError
687 # Check if a token has been set.
689 data
["token"] = self
.token
691 # Raise an error if no token and no useranem and password
693 elif not self
.username
and not self
.password
:
694 raise DDNSConfigurationError(_("No Auth details specified."))
696 # HTTP Basic Auth is only allowed if no token is used.
698 # Send update to the server.
699 response
= self
.send_request(self
.url
, data
=data
)
701 # Send update to the server.
702 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
705 # Get the full response message.
706 output
= response
.read()
708 # Handle success messages.
709 if "100" in output
or "101" in output
:
712 # Handle error codes.
713 if "401" or "402" in output
:
714 raise DDNSAuthenticationError
715 elif "408" in output
:
716 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
717 elif "409" in output
:
718 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
719 elif "412" in output
:
720 raise DDNSRequestError(_("No valid FQDN was given."))
721 elif "414" in output
:
722 raise DDNSInternalServerError
724 # If we got here, some other update error happened.
725 raise DDNSUpdateError
728 class DDNSProviderSelfhost(DDNSProviderDynDNS
):
730 "handle" : "selfhost.de",
731 "name" : "Selfhost.de",
732 "website" : "http://www.selfhost.de/",
733 "protocols" : ["ipv4",],
736 url
= "https://carol.selfhost.de/nic/update"
738 def _prepare_request_data(self
):
739 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
747 class DDNSProviderSPDNS(DDNSProviderDynDNS
):
749 "handle" : "spdns.org",
751 "website" : "http://spdns.org/",
752 "protocols" : ["ipv4",]
755 # Detailed information about request and response codes are provided
756 # by the vendor. They are using almost the same mechanism and status
757 # codes as dyndns.org so we can inherit all those stuff.
759 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
760 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
762 url
= "https://update.spdns.de/nic/update"
765 class DDNSProviderStrato(DDNSProviderDynDNS
):
767 "handle" : "strato.com",
768 "name" : "Strato AG",
769 "website" : "http:/www.strato.com/",
770 "protocols" : ["ipv4",]
773 # Information about the request and response can be obtained here:
774 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
776 url
= "https://dyndns.strato.com/nic/update"
779 class DDNSProviderTwoDNS(DDNSProviderDynDNS
):
781 "handle" : "twodns.de",
783 "website" : "http://www.twodns.de",
784 "protocols" : ["ipv4",]
787 # Detailed information about the request can be found here
788 # http://twodns.de/en/faqs
789 # http://twodns.de/en/api
791 url
= "https://update.twodns.de/update"
793 def _prepare_request_data(self
):
795 "ip" : self
.get_address("ipv4"),
796 "hostname" : self
.hostname
802 class DDNSProviderUdmedia(DDNSProviderDynDNS
):
804 "handle" : "udmedia.de",
805 "name" : "Udmedia GmbH",
806 "website" : "http://www.udmedia.de",
807 "protocols" : ["ipv4",]
810 # Information about the request can be found here
811 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
813 url
= "https://www.udmedia.de/nic/update"
816 class DDNSProviderVariomedia(DDNSProviderDynDNS
):
818 "handle" : "variomedia.de",
819 "name" : "Variomedia",
820 "website" : "http://www.variomedia.de/",
821 "protocols" : ["ipv6", "ipv4",]
824 # Detailed information about the request can be found here
825 # https://dyndns.variomedia.de/
827 url
= "https://dyndns.variomedia.de/nic/update"
831 return self
.get("proto")
833 def _prepare_request_data(self
):
835 "hostname" : self
.hostname
,
836 "myip" : self
.get_address(self
.proto
)
842 class DDNSProviderZoneedit(DDNSProvider
):
844 "handle" : "zoneedit.com",
846 "website" : "http://www.zoneedit.com",
847 "protocols" : ["ipv6", "ipv4",]
850 # Detailed information about the request and the response codes can be
852 # http://www.zoneedit.com/doc/api/other.html
853 # http://www.zoneedit.com/faq.html
855 url
= "https://dynamic.zoneedit.com/auth/dynamic.html"
859 return self
.get("proto")
863 "dnsto" : self
.get_address(self
.proto
),
864 "host" : self
.hostname
867 # Send update to the server.
868 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
871 # Get the full response message.
872 output
= response
.read()
874 # Handle success messages.
875 if output
.startswith("<SUCCESS"):
878 # Handle error codes.
879 if output
.startswith("invalid login"):
880 raise DDNSAuthenticationError
881 elif output
.startswith("<ERROR CODE=\"704\""):
882 raise DDNSRequestError(_("No valid FQDN was given."))
883 elif output
.startswith("<ERROR CODE=\"702\""):
884 raise DDNSInternalServerError
886 # If we got here, some other update error happened.
887 raise DDNSUpdateError