]>
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 ###############################################################################
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"
194 # There is no additional data required so we directly can
197 # Send request to the server.
198 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
)
200 # Handle 401 HTTP Header (Authentication Error)
201 except urllib2
.HTTPError
, e
:
203 raise DDNSAuthenticationError
207 # Get the full response message.
208 output
= response
.read()
210 # Handle success messages.
211 if output
.startswith("good") or output
.startswith("nochg"):
214 # If we got here, some other update error happened.
215 raise DDNSUpdateError
218 class DDNSProviderDHS(DDNSProvider
):
220 "handle" : "dhs.org",
221 "name" : "DHS International",
222 "website" : "http://dhs.org/",
223 "protocols" : ["ipv4",]
226 # No information about the used update api provided on webpage,
227 # grabed from source code of ez-ipudate.
228 url
= "http://members.dhs.org/nic/hosts"
232 "domain" : self
.hostname
,
233 "ip" : self
.get_address("ipv4"),
235 "hostcmdstage" : "2",
239 # Send update to the server.
240 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
243 # Handle success messages.
244 if response
.code
== 200:
247 # Handle error codes.
248 elif response
.code
== 401:
249 raise DDNSAuthenticationError
251 # If we got here, some other update error happened.
252 raise DDNSUpdateError
255 class DDNSProviderDNSpark(DDNSProvider
):
257 "handle" : "dnspark.com",
259 "website" : "http://dnspark.com/",
260 "protocols" : ["ipv4",]
263 # Informations to the used api can be found here:
264 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
265 url
= "https://control.dnspark.com/api/dynamic/update.php"
269 "domain" : self
.hostname
,
270 "ip" : self
.get_address("ipv4"),
273 # Send update to the server.
274 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
277 # Get the full response message.
278 output
= response
.read()
280 # Handle success messages.
281 if output
.startswith("ok") or output
.startswith("nochange"):
284 # Handle error codes.
285 if output
== "unauth":
286 raise DDNSAuthenticationError
287 elif output
== "abuse":
289 elif output
== "blocked":
290 raise DDNSBlockedError
291 elif output
== "nofqdn":
292 raise DDNSRequestError(_("No valid FQDN was given."))
293 elif output
== "nohost":
294 raise DDNSRequestError(_("Invalid hostname specified."))
295 elif output
== "notdyn":
296 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
297 elif output
== "invalid":
298 raise DDNSRequestError(_("Invalid IP address has been sent."))
300 # If we got here, some other update error happened.
301 raise DDNSUpdateError
304 class DDNSProviderDtDNS(DDNSProvider
):
306 "handle" : "dtdns.com",
308 "website" : "http://dtdns.com/",
309 "protocols" : ["ipv4",]
312 # Information about the format of the HTTPS request is to be found
313 # http://www.dtdns.com/dtsite/updatespec
314 url
= "https://www.dtdns.com/api/autodns.cfm"
318 "ip" : self
.get_address("ipv4"),
319 "id" : self
.hostname
,
323 # Send update to the server.
324 response
= self
.send_request(self
.url
, data
=data
)
326 # Get the full response message.
327 output
= response
.read()
329 # Remove all leading and trailing whitespace.
330 output
= output
.strip()
332 # Handle success messages.
333 if "now points to" in output
:
336 # Handle error codes.
337 if output
== "No hostname to update was supplied.":
338 raise DDNSRequestError(_("No hostname specified."))
340 elif output
== "The hostname you supplied is not valid.":
341 raise DDNSRequestError(_("Invalid hostname specified."))
343 elif output
== "The password you supplied is not valid.":
344 raise DDNSAuthenticationError
346 elif output
== "Administration has disabled this account.":
347 raise DDNSRequestError(_("Account has been disabled."))
349 elif output
== "Illegal character in IP.":
350 raise DDNSRequestError(_("Invalid IP address has been sent."))
352 elif output
== "Too many failed requests.":
353 raise DDNSRequestError(_("Too many failed requests."))
355 # If we got here, some other update error happened.
356 raise DDNSUpdateError
359 class DDNSProviderDynDNS(DDNSProvider
):
361 "handle" : "dyndns.org",
363 "website" : "http://dyn.com/dns/",
364 "protocols" : ["ipv4",]
367 # Information about the format of the request is to be found
368 # http://http://dyn.com/support/developers/api/perform-update/
369 # http://dyn.com/support/developers/api/return-codes/
370 url
= "https://members.dyndns.org/nic/update"
372 def _prepare_request_data(self
):
374 "hostname" : self
.hostname
,
375 "myip" : self
.get_address("ipv4"),
381 data
= self
._prepare
_request
_data
()
383 # Send update to the server.
384 response
= self
.send_request(self
.url
, data
=data
,
385 username
=self
.username
, password
=self
.password
)
387 # Get the full response message.
388 output
= response
.read()
390 # Handle success messages.
391 if output
.startswith("good") or output
.startswith("nochg"):
394 # Handle error codes.
395 if output
== "badauth":
396 raise DDNSAuthenticationError
397 elif output
== "aduse":
399 elif output
== "notfqdn":
400 raise DDNSRequestError(_("No valid FQDN was given."))
401 elif output
== "nohost":
402 raise DDNSRequestError(_("Specified host does not exist."))
403 elif output
== "911":
404 raise DDNSInternalServerError
405 elif output
== "dnserr":
406 raise DDNSInternalServerError(_("DNS error encountered."))
408 # If we got here, some other update error happened.
409 raise DDNSUpdateError
412 class DDNSProviderDynU(DDNSProviderDynDNS
):
414 "handle" : "dynu.com",
416 "website" : "http://dynu.com/",
417 "protocols" : ["ipv6", "ipv4",]
421 # Detailed information about the request and response codes
422 # are available on the providers webpage.
423 # http://dynu.com/Default.aspx?page=dnsapi
425 url
= "https://api.dynu.com/nic/update"
427 def _prepare_request_data(self
):
428 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
430 # This one supports IPv6
432 "myipv6" : self
.get_address("ipv6"),
438 class DDNSProviderEasyDNS(DDNSProviderDynDNS
):
440 "handle" : "easydns.com",
442 "website" : "http://www.easydns.com/",
443 "protocols" : ["ipv4",]
446 # There is only some basic documentation provided by the vendor,
447 # also searching the web gain very poor results.
448 # http://mediawiki.easydns.com/index.php/Dynamic_DNS
450 url
= "http://api.cp.easydns.com/dyn/tomato.php"
453 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider
):
455 "handle" : "freedns.afraid.org",
456 "name" : "freedns.afraid.org",
457 "website" : "http://freedns.afraid.org/",
458 "protocols" : ["ipv6", "ipv4",]
461 # No information about the request or response could be found on the vendor
462 # page. All used values have been collected by testing.
463 url
= "https://freedns.afraid.org/dynamic/update.php"
467 return self
.get("proto")
470 address
= self
.get_address(self
.proto
)
476 # Add auth token to the update url.
477 url
= "%s?%s" % (self
.url
, self
.token
)
479 # Send update to the server.
480 response
= self
.send_request(url
, data
=data
)
482 if output
.startswith("Updated") or "has not changed" in output
:
485 # Handle error codes.
486 if output
== "ERROR: Unable to locate this record":
487 raise DDNSAuthenticationError
488 elif "is an invalid IP address" in output
:
489 raise DDNSRequestError(_("Invalid IP address has been sent."))
492 class DDNSProviderLightningWireLabs(DDNSProvider
):
494 "handle" : "dns.lightningwirelabs.com",
495 "name" : "Lightning Wire Labs",
496 "website" : "http://dns.lightningwirelabs.com/",
497 "protocols" : ["ipv6", "ipv4",]
500 # Information about the format of the HTTPS request is to be found
501 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
502 url
= "https://dns.lightningwirelabs.com/update"
506 "hostname" : self
.hostname
,
509 # Check if we update an IPv6 address.
510 address6
= self
.get_address("ipv6")
512 data
["address6"] = address6
514 # Check if we update an IPv4 address.
515 address4
= self
.get_address("ipv4")
517 data
["address4"] = address4
519 # Raise an error if none address is given.
520 if not data
.has_key("address6") and not data
.has_key("address4"):
521 raise DDNSConfigurationError
523 # Check if a token has been set.
525 data
["token"] = self
.token
527 # Check for username and password.
528 elif self
.username
and self
.password
:
530 "username" : self
.username
,
531 "password" : self
.password
,
534 # Raise an error if no auth details are given.
536 raise DDNSConfigurationError
538 # Send update to the server.
539 response
= self
.send_request(self
.url
, data
=data
)
541 # Handle success messages.
542 if response
.code
== 200:
545 # Handle error codes.
546 if response
.code
== 403:
547 raise DDNSAuthenticationError
548 elif response
.code
== 400:
549 raise DDNSRequestError
551 # If we got here, some other update error happened.
552 raise DDNSUpdateError
555 class DDNSProviderNamecheap(DDNSProvider
):
557 "handle" : "namecheap.com",
558 "name" : "Namecheap",
559 "website" : "http://namecheap.com",
560 "protocols" : ["ipv4",]
563 # Information about the format of the HTTP request is to be found
564 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
565 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
567 url
= "https://dynamicdns.park-your-domain.com/update"
569 def parse_xml(self
, document
, content
):
570 # Send input to the parser.
571 xmldoc
= xml
.dom
.minidom
.parseString(document
)
573 # Get XML elements by the given content.
574 element
= xmldoc
.getElementsByTagName(content
)
576 # If no element has been found, we directly can return None.
580 # Only get the first child from an element, even there are more than one.
581 firstchild
= element
[0].firstChild
583 # Get the value of the child.
584 value
= firstchild
.nodeValue
590 # Namecheap requires the hostname splitted into a host and domain part.
591 host
, domain
= self
.hostname
.split(".", 1)
594 "ip" : self
.get_address("ipv4"),
595 "password" : self
.password
,
600 # Send update to the server.
601 response
= self
.send_request(self
.url
, data
=data
)
603 # Get the full response message.
604 output
= response
.read()
606 # Handle success messages.
607 if self
.parse_xml(output
, "IP") == self
.get_address("ipv4"):
610 # Handle error codes.
611 errorcode
= self
.parse_xml(output
, "ResponseNumber")
613 if errorcode
== "304156":
614 raise DDNSAuthenticationError
615 elif errorcode
== "316153":
616 raise DDNSRequestError(_("Domain not found."))
617 elif errorcode
== "316154":
618 raise DDNSRequestError(_("Domain not active."))
619 elif errorcode
in ("380098", "380099"):
620 raise DDNSInternalServerError
622 # If we got here, some other update error happened.
623 raise DDNSUpdateError
626 class DDNSProviderNOIP(DDNSProviderDynDNS
):
628 "handle" : "no-ip.com",
630 "website" : "http://www.no-ip.com/",
631 "protocols" : ["ipv4",]
634 # Information about the format of the HTTP request is to be found
635 # here: http://www.no-ip.com/integrate/request and
636 # here: http://www.no-ip.com/integrate/response
638 url
= "http://dynupdate.no-ip.com/nic/update"
640 def _prepare_request_data(self
):
642 "hostname" : self
.hostname
,
643 "address" : self
.get_address("ipv4"),
649 class DDNSProviderOVH(DDNSProviderDynDNS
):
651 "handle" : "ovh.com",
653 "website" : "http://www.ovh.com/",
654 "protocols" : ["ipv4",]
657 # OVH only provides very limited information about how to
658 # update a DynDNS host. They only provide the update url
659 # on the their german subpage.
661 # http://hilfe.ovh.de/DomainDynHost
663 url
= "https://www.ovh.com/nic/update"
665 def _prepare_request_data(self
):
666 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
674 class DDNSProviderRegfish(DDNSProvider
):
676 "handle" : "regfish.com",
677 "name" : "Regfish GmbH",
678 "website" : "http://www.regfish.com/",
679 "protocols" : ["ipv6", "ipv4",]
682 # A full documentation to the providers api can be found here
683 # but is only available in german.
684 # https://www.regfish.de/domains/dyndns/dokumentation
686 url
= "https://dyndns.regfish.de/"
690 "fqdn" : self
.hostname
,
693 # Check if we update an IPv6 address.
694 address6
= self
.get_address("ipv6")
696 data
["ipv6"] = address6
698 # Check if we update an IPv4 address.
699 address4
= self
.get_address("ipv4")
701 data
["ipv4"] = address4
703 # Raise an error if none address is given.
704 if not data
.has_key("ipv6") and not data
.has_key("ipv4"):
705 raise DDNSConfigurationError
707 # Check if a token has been set.
709 data
["token"] = self
.token
711 # Raise an error if no token and no useranem and password
713 elif not self
.username
and not self
.password
:
714 raise DDNSConfigurationError(_("No Auth details specified."))
716 # HTTP Basic Auth is only allowed if no token is used.
718 # Send update to the server.
719 response
= self
.send_request(self
.url
, data
=data
)
721 # Send update to the server.
722 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
725 # Get the full response message.
726 output
= response
.read()
728 # Handle success messages.
729 if "100" in output
or "101" in output
:
732 # Handle error codes.
733 if "401" or "402" in output
:
734 raise DDNSAuthenticationError
735 elif "408" in output
:
736 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
737 elif "409" in output
:
738 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
739 elif "412" in output
:
740 raise DDNSRequestError(_("No valid FQDN was given."))
741 elif "414" in output
:
742 raise DDNSInternalServerError
744 # If we got here, some other update error happened.
745 raise DDNSUpdateError
748 class DDNSProviderSelfhost(DDNSProvider
):
750 "handle" : "selfhost.de",
751 "name" : "Selfhost.de",
752 "website" : "http://www.selfhost.de/",
753 "protocols" : ["ipv4",],
756 url
= "https://carol.selfhost.de/update"
760 "username" : self
.username
,
761 "password" : self
.password
,
765 response
= self
.send_request(self
.url
, data
=data
)
767 match
= re
.search("status=20(0|4)", response
.read())
769 raise DDNSUpdateError
772 class DDNSProviderSPDNS(DDNSProviderDynDNS
):
774 "handle" : "spdns.org",
776 "website" : "http://spdns.org/",
777 "protocols" : ["ipv4",]
780 # Detailed information about request and response codes are provided
781 # by the vendor. They are using almost the same mechanism and status
782 # codes as dyndns.org so we can inherit all those stuff.
784 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
785 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
787 url
= "https://update.spdns.de/nic/update"
790 class DDNSProviderStrato(DDNSProviderDynDNS
):
792 "handle" : "strato.com",
793 "name" : "Strato AG",
794 "website" : "http:/www.strato.com/",
795 "protocols" : ["ipv4",]
798 # Information about the request and response can be obtained here:
799 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
801 url
= "https://dyndns.strato.com/nic/update"
804 class DDNSProviderTwoDNS(DDNSProviderDynDNS
):
806 "handle" : "twodns.de",
808 "website" : "http://www.twodns.de",
809 "protocols" : ["ipv4",]
812 # Detailed information about the request can be found here
813 # http://twodns.de/en/faqs
814 # http://twodns.de/en/api
816 url
= "https://update.twodns.de/update"
818 def _prepare_request_data(self
):
820 "ip" : self
.get_address("ipv4"),
821 "hostname" : self
.hostname
827 class DDNSProviderUdmedia(DDNSProviderDynDNS
):
829 "handle" : "udmedia.de",
830 "name" : "Udmedia GmbH",
831 "website" : "http://www.udmedia.de",
832 "protocols" : ["ipv4",]
835 # Information about the request can be found here
836 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
838 url
= "https://www.udmedia.de/nic/update"
841 class DDNSProviderVariomedia(DDNSProviderDynDNS
):
843 "handle" : "variomedia.de",
844 "name" : "Variomedia",
845 "website" : "http://www.variomedia.de/",
846 "protocols" : ["ipv6", "ipv4",]
849 # Detailed information about the request can be found here
850 # https://dyndns.variomedia.de/
852 url
= "https://dyndns.variomedia.de/nic/update"
856 return self
.get("proto")
858 def _prepare_request_data(self
):
860 "hostname" : self
.hostname
,
861 "myip" : self
.get_address(self
.proto
)
867 class DDNSProviderZoneedit(DDNSProvider
):
869 "handle" : "zoneedit.com",
871 "website" : "http://www.zoneedit.com",
872 "protocols" : ["ipv6", "ipv4",]
875 # Detailed information about the request and the response codes can be
877 # http://www.zoneedit.com/doc/api/other.html
878 # http://www.zoneedit.com/faq.html
880 url
= "https://dynamic.zoneedit.com/auth/dynamic.html"
884 return self
.get("proto")
888 "dnsto" : self
.get_address(self
.proto
),
889 "host" : self
.hostname
892 # Send update to the server.
893 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
896 # Get the full response message.
897 output
= response
.read()
899 # Handle success messages.
900 if output
.startswith("<SUCCESS"):
903 # Handle error codes.
904 if output
.startswith("invalid login"):
905 raise DDNSAuthenticationError
906 elif output
.startswith("<ERROR CODE=\"704\""):
907 raise DDNSRequestError(_("No valid FQDN was given."))
908 elif output
.startswith("<ERROR CODE=\"702\""):
909 raise DDNSInternalServerError
911 # If we got here, some other update error happened.
912 raise DDNSUpdateError