]>
git.ipfire.org Git - ddns.git/blob - src/ddns/providers.py
ed893a4d2e29ed67bb88237ed0101c2846b22d42
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")
38 Returns a dict with all automatically registered providers.
40 return _providers
.copy()
42 class DDNSProvider(object):
43 # A short string that uniquely identifies
47 # The full name of the provider.
50 # A weburl to the homepage of the provider.
51 # (Where to register a new account?)
54 # A list of supported protocols.
55 protocols
= ("ipv6", "ipv4")
59 # Automatically register all providers.
60 class __metaclass__(type):
61 def __init__(provider
, name
, bases
, dict):
62 type.__init
__(provider
, name
, bases
, dict)
64 # The main class from which is inherited is not registered
66 if name
== "DDNSProvider":
69 if not all((provider
.handle
, provider
.name
, provider
.website
)):
70 raise DDNSError(_("Provider is not properly configured"))
72 assert not _providers
.has_key(provider
.handle
), \
73 "Provider '%s' has already been registered" % provider
.handle
75 _providers
[provider
.handle
] = provider
77 def __init__(self
, core
, **settings
):
80 # Copy a set of default settings and
81 # update them by those from the configuration file.
82 self
.settings
= self
.DEFAULT_SETTINGS
.copy()
83 self
.settings
.update(settings
)
86 return "<DDNS Provider %s (%s)>" % (self
.name
, self
.handle
)
88 def __cmp__(self
, other
):
89 return cmp(self
.hostname
, other
.hostname
)
91 def get(self
, key
, default
=None):
93 Get a setting from the settings dictionary.
95 return self
.settings
.get(key
, default
)
100 Fast access to the hostname.
102 return self
.get("hostname")
107 Fast access to the username.
109 return self
.get("username")
114 Fast access to the password.
116 return self
.get("password")
121 Fast access to the token.
123 return self
.get("token")
125 def __call__(self
, force
=False):
127 logger
.debug(_("Updating %s forced") % self
.hostname
)
129 # Check if we actually need to update this host.
130 elif self
.is_uptodate(self
.protocols
):
131 logger
.debug(_("%s is already up to date") % self
.hostname
)
134 # Execute the update.
138 raise NotImplementedError
140 def is_uptodate(self
, protos
):
142 Returns True if this host is already up to date
143 and does not need to change the IP address on the
147 addresses
= self
.core
.system
.resolve(self
.hostname
, proto
)
149 current_address
= self
.get_address(proto
)
151 # If no addresses for the given protocol exist, we
153 if current_address
is None and not addresses
:
156 if not current_address
in addresses
:
161 def send_request(self
, *args
, **kwargs
):
163 Proxy connection to the send request
166 return self
.core
.system
.send_request(*args
, **kwargs
)
168 def get_address(self
, proto
, default
=None):
170 Proxy method to get the current IP address.
172 return self
.core
.system
.get_address(proto
) or default
175 class DDNSProtocolDynDNS2(object):
177 This is an abstract class that implements the DynDNS updater
178 protocol version 2. As this is a popular way to update dynamic
179 DNS records, this class is supposed make the provider classes
183 # Information about the format of the request is to be found
184 # http://dyn.com/support/developers/api/perform-update/
185 # http://dyn.com/support/developers/api/return-codes/
187 def _prepare_request_data(self
):
189 "hostname" : self
.hostname
,
190 "myip" : self
.get_address("ipv4"),
196 data
= self
._prepare
_request
_data
()
198 # Send update to the server.
199 response
= self
.send_request(self
.url
, data
=data
,
200 username
=self
.username
, password
=self
.password
)
202 # Get the full response message.
203 output
= response
.read()
205 # Handle success messages.
206 if output
.startswith("good") or output
.startswith("nochg"):
209 # Handle error codes.
210 if output
== "badauth":
211 raise DDNSAuthenticationError
212 elif output
== "aduse":
214 elif output
== "notfqdn":
215 raise DDNSRequestError(_("No valid FQDN was given."))
216 elif output
== "nohost":
217 raise DDNSRequestError(_("Specified host does not exist."))
218 elif output
== "911":
219 raise DDNSInternalServerError
220 elif output
== "dnserr":
221 raise DDNSInternalServerError(_("DNS error encountered."))
223 # If we got here, some other update error happened.
224 raise DDNSUpdateError(_("Server response: %s") % output
)
227 class DDNSProviderAllInkl(DDNSProvider
):
228 handle
= "all-inkl.com"
229 name
= "All-inkl.com"
230 website
= "http://all-inkl.com/"
231 protocols
= ("ipv4",)
233 # There are only information provided by the vendor how to
234 # perform an update on a FRITZ Box. Grab requried informations
236 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
238 url
= "http://dyndns.kasserver.com"
241 # There is no additional data required so we directly can
243 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
)
245 # Get the full response message.
246 output
= response
.read()
248 # Handle success messages.
249 if output
.startswith("good") or output
.startswith("nochg"):
252 # If we got here, some other update error happened.
253 raise DDNSUpdateError
256 class DDNSProviderDHS(DDNSProvider
):
258 name
= "DHS International"
259 website
= "http://dhs.org/"
260 protocols
= ("ipv4",)
262 # No information about the used update api provided on webpage,
263 # grabed from source code of ez-ipudate.
265 url
= "http://members.dhs.org/nic/hosts"
269 "domain" : self
.hostname
,
270 "ip" : self
.get_address("ipv4"),
272 "hostcmdstage" : "2",
276 # Send update to the server.
277 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
280 # Handle success messages.
281 if response
.code
== 200:
284 # If we got here, some other update error happened.
285 raise DDNSUpdateError
288 class DDNSProviderDNSpark(DDNSProvider
):
289 handle
= "dnspark.com"
291 website
= "http://dnspark.com/"
292 protocols
= ("ipv4",)
294 # Informations to the used api can be found here:
295 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
297 url
= "https://control.dnspark.com/api/dynamic/update.php"
301 "domain" : self
.hostname
,
302 "ip" : self
.get_address("ipv4"),
305 # Send update to the server.
306 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
309 # Get the full response message.
310 output
= response
.read()
312 # Handle success messages.
313 if output
.startswith("ok") or output
.startswith("nochange"):
316 # Handle error codes.
317 if output
== "unauth":
318 raise DDNSAuthenticationError
319 elif output
== "abuse":
321 elif output
== "blocked":
322 raise DDNSBlockedError
323 elif output
== "nofqdn":
324 raise DDNSRequestError(_("No valid FQDN was given."))
325 elif output
== "nohost":
326 raise DDNSRequestError(_("Invalid hostname specified."))
327 elif output
== "notdyn":
328 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
329 elif output
== "invalid":
330 raise DDNSRequestError(_("Invalid IP address has been sent."))
332 # If we got here, some other update error happened.
333 raise DDNSUpdateError
336 class DDNSProviderDtDNS(DDNSProvider
):
339 website
= "http://dtdns.com/"
340 protocols
= ("ipv4",)
342 # Information about the format of the HTTPS request is to be found
343 # http://www.dtdns.com/dtsite/updatespec
345 url
= "https://www.dtdns.com/api/autodns.cfm"
349 "ip" : self
.get_address("ipv4"),
350 "id" : self
.hostname
,
354 # Send update to the server.
355 response
= self
.send_request(self
.url
, data
=data
)
357 # Get the full response message.
358 output
= response
.read()
360 # Remove all leading and trailing whitespace.
361 output
= output
.strip()
363 # Handle success messages.
364 if "now points to" in output
:
367 # Handle error codes.
368 if output
== "No hostname to update was supplied.":
369 raise DDNSRequestError(_("No hostname specified."))
371 elif output
== "The hostname you supplied is not valid.":
372 raise DDNSRequestError(_("Invalid hostname specified."))
374 elif output
== "The password you supplied is not valid.":
375 raise DDNSAuthenticationError
377 elif output
== "Administration has disabled this account.":
378 raise DDNSRequestError(_("Account has been disabled."))
380 elif output
== "Illegal character in IP.":
381 raise DDNSRequestError(_("Invalid IP address has been sent."))
383 elif output
== "Too many failed requests.":
384 raise DDNSRequestError(_("Too many failed requests."))
386 # If we got here, some other update error happened.
387 raise DDNSUpdateError
390 class DDNSProviderDynDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
391 handle
= "dyndns.org"
393 website
= "http://dyn.com/dns/"
394 protocols
= ("ipv4",)
396 # Information about the format of the request is to be found
397 # http://http://dyn.com/support/developers/api/perform-update/
398 # http://dyn.com/support/developers/api/return-codes/
400 url
= "https://members.dyndns.org/nic/update"
403 class DDNSProviderDynU(DDNSProtocolDynDNS2
, DDNSProvider
):
406 website
= "http://dynu.com/"
407 protocols
= ("ipv6", "ipv4",)
409 # Detailed information about the request and response codes
410 # are available on the providers webpage.
411 # http://dynu.com/Default.aspx?page=dnsapi
413 url
= "https://api.dynu.com/nic/update"
415 def _prepare_request_data(self
):
416 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
418 # This one supports IPv6
420 "myipv6" : self
.get_address("ipv6"),
426 class DDNSProviderEasyDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
427 handle
= "easydns.com"
429 website
= "http://www.easydns.com/"
430 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
):
440 handle
= "freedns.afraid.org"
441 name
= "freedns.afraid.org"
442 website
= "http://freedns.afraid.org/"
444 # No information about the request or response could be found on the vendor
445 # page. All used values have been collected by testing.
446 url
= "https://freedns.afraid.org/dynamic/update.php"
450 return self
.get("proto")
453 address
= self
.get_address(self
.proto
)
459 # Add auth token to the update url.
460 url
= "%s?%s" % (self
.url
, self
.token
)
462 # Send update to the server.
463 response
= self
.send_request(url
, data
=data
)
465 if output
.startswith("Updated") or "has not changed" in output
:
468 # Handle error codes.
469 if output
== "ERROR: Unable to locate this record":
470 raise DDNSAuthenticationError
471 elif "is an invalid IP address" in output
:
472 raise DDNSRequestError(_("Invalid IP address has been sent."))
474 # If we got here, some other update error happened.
475 raise DDNSUpdateError
478 class DDNSProviderLightningWireLabs(DDNSProvider
):
479 handle
= "dns.lightningwirelabs.com"
480 name
= "Lightning Wire Labs DNS Service"
481 website
= "http://dns.lightningwirelabs.com/"
483 # Information about the format of the HTTPS request is to be found
484 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
486 url
= "https://dns.lightningwirelabs.com/update"
490 "hostname" : self
.hostname
,
491 "address6" : self
.get_address("ipv6", "-"),
492 "address4" : self
.get_address("ipv4", "-"),
495 # Check if a token has been set.
497 data
["token"] = self
.token
499 # Check for username and password.
500 elif self
.username
and self
.password
:
502 "username" : self
.username
,
503 "password" : self
.password
,
506 # Raise an error if no auth details are given.
508 raise DDNSConfigurationError
510 # Send update to the server.
511 response
= self
.send_request(self
.url
, data
=data
)
513 # Handle success messages.
514 if response
.code
== 200:
517 # If we got here, some other update error happened.
518 raise DDNSUpdateError
521 class DDNSProviderNamecheap(DDNSProvider
):
522 handle
= "namecheap.com"
524 website
= "http://namecheap.com"
525 protocols
= ("ipv4",)
527 # Information about the format of the HTTP request is to be found
528 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
529 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
531 url
= "https://dynamicdns.park-your-domain.com/update"
533 def parse_xml(self
, document
, content
):
534 # Send input to the parser.
535 xmldoc
= xml
.dom
.minidom
.parseString(document
)
537 # Get XML elements by the given content.
538 element
= xmldoc
.getElementsByTagName(content
)
540 # If no element has been found, we directly can return None.
544 # Only get the first child from an element, even there are more than one.
545 firstchild
= element
[0].firstChild
547 # Get the value of the child.
548 value
= firstchild
.nodeValue
554 # Namecheap requires the hostname splitted into a host and domain part.
555 host
, domain
= self
.hostname
.split(".", 1)
558 "ip" : self
.get_address("ipv4"),
559 "password" : self
.password
,
564 # Send update to the server.
565 response
= self
.send_request(self
.url
, data
=data
)
567 # Get the full response message.
568 output
= response
.read()
570 # Handle success messages.
571 if self
.parse_xml(output
, "IP") == self
.get_address("ipv4"):
574 # Handle error codes.
575 errorcode
= self
.parse_xml(output
, "ResponseNumber")
577 if errorcode
== "304156":
578 raise DDNSAuthenticationError
579 elif errorcode
== "316153":
580 raise DDNSRequestError(_("Domain not found."))
581 elif errorcode
== "316154":
582 raise DDNSRequestError(_("Domain not active."))
583 elif errorcode
in ("380098", "380099"):
584 raise DDNSInternalServerError
586 # If we got here, some other update error happened.
587 raise DDNSUpdateError
590 class DDNSProviderNOIP(DDNSProtocolDynDNS2
, DDNSProvider
):
593 website
= "http://www.no-ip.com/"
594 protocols
= ("ipv4",)
596 # Information about the format of the HTTP request is to be found
597 # here: http://www.no-ip.com/integrate/request and
598 # here: http://www.no-ip.com/integrate/response
600 url
= "http://dynupdate.no-ip.com/nic/update"
602 def _prepare_request_data(self
):
604 "hostname" : self
.hostname
,
605 "address" : self
.get_address("ipv4"),
611 class DDNSProviderOVH(DDNSProtocolDynDNS2
, DDNSProvider
):
614 website
= "http://www.ovh.com/"
615 protocols
= ("ipv4",)
617 # OVH only provides very limited information about how to
618 # update a DynDNS host. They only provide the update url
619 # on the their german subpage.
621 # http://hilfe.ovh.de/DomainDynHost
623 url
= "https://www.ovh.com/nic/update"
625 def _prepare_request_data(self
):
626 data
= DDNSProtocolDynDNS2
._prepare_request_data(self
)
634 class DDNSProviderRegfish(DDNSProvider
):
635 handle
= "regfish.com"
636 name
= "Regfish GmbH"
637 website
= "http://www.regfish.com/"
639 # A full documentation to the providers api can be found here
640 # but is only available in german.
641 # https://www.regfish.de/domains/dyndns/dokumentation
643 url
= "https://dyndns.regfish.de/"
647 "fqdn" : self
.hostname
,
650 # Check if we update an IPv6 address.
651 address6
= self
.get_address("ipv6")
653 data
["ipv6"] = address6
655 # Check if we update an IPv4 address.
656 address4
= self
.get_address("ipv4")
658 data
["ipv4"] = address4
660 # Raise an error if none address is given.
661 if not data
.has_key("ipv6") and not data
.has_key("ipv4"):
662 raise DDNSConfigurationError
664 # Check if a token has been set.
666 data
["token"] = self
.token
668 # Raise an error if no token and no useranem and password
670 elif not self
.username
and not self
.password
:
671 raise DDNSConfigurationError(_("No Auth details specified."))
673 # HTTP Basic Auth is only allowed if no token is used.
675 # Send update to the server.
676 response
= self
.send_request(self
.url
, data
=data
)
678 # Send update to the server.
679 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
682 # Get the full response message.
683 output
= response
.read()
685 # Handle success messages.
686 if "100" in output
or "101" in output
:
689 # Handle error codes.
690 if "401" or "402" in output
:
691 raise DDNSAuthenticationError
692 elif "408" in output
:
693 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
694 elif "409" in output
:
695 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
696 elif "412" in output
:
697 raise DDNSRequestError(_("No valid FQDN was given."))
698 elif "414" in output
:
699 raise DDNSInternalServerError
701 # If we got here, some other update error happened.
702 raise DDNSUpdateError
705 class DDNSProviderSelfhost(DDNSProtocolDynDNS2
, DDNSProvider
):
706 handle
= "selfhost.de"
708 website
= "http://www.selfhost.de/"
709 protocols
= ("ipv4",)
711 url
= "https://carol.selfhost.de/nic/update"
713 def _prepare_request_data(self
):
714 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
722 class DDNSProviderSPDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
725 website
= "http://spdns.org/"
726 protocols
= ("ipv4",)
728 # Detailed information about request and response codes are provided
729 # by the vendor. They are using almost the same mechanism and status
730 # codes as dyndns.org so we can inherit all those stuff.
732 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
733 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
735 url
= "https://update.spdns.de/nic/update"
738 class DDNSProviderStrato(DDNSProtocolDynDNS2
, DDNSProvider
):
739 handle
= "strato.com"
741 website
= "http:/www.strato.com/"
742 protocols
= ("ipv4",)
744 # Information about the request and response can be obtained here:
745 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
747 url
= "https://dyndns.strato.com/nic/update"
750 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
753 website
= "http://www.twodns.de"
754 protocols
= ("ipv4",)
756 # Detailed information about the request can be found here
757 # http://twodns.de/en/faqs
758 # http://twodns.de/en/api
760 url
= "https://update.twodns.de/update"
762 def _prepare_request_data(self
):
764 "ip" : self
.get_address("ipv4"),
765 "hostname" : self
.hostname
771 class DDNSProviderUdmedia(DDNSProtocolDynDNS2
, DDNSProvider
):
772 handle
= "udmedia.de"
773 name
= "Udmedia GmbH"
774 website
= "http://www.udmedia.de"
775 protocols
= ("ipv4",)
777 # Information about the request can be found here
778 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
780 url
= "https://www.udmedia.de/nic/update"
783 class DDNSProviderVariomedia(DDNSProtocolDynDNS2
, DDNSProvider
):
784 handle
= "variomedia.de"
786 website
= "http://www.variomedia.de/"
787 protocols
= ("ipv6", "ipv4",)
789 # Detailed information about the request can be found here
790 # https://dyndns.variomedia.de/
792 url
= "https://dyndns.variomedia.de/nic/update"
796 return self
.get("proto")
798 def _prepare_request_data(self
):
800 "hostname" : self
.hostname
,
801 "myip" : self
.get_address(self
.proto
)
807 class DDNSProviderZoneedit(DDNSProtocolDynDNS2
, DDNSProvider
):
808 handle
= "zoneedit.com"
810 website
= "http://www.zoneedit.com"
811 protocols
= ("ipv4",)
813 # Detailed information about the request and the response codes can be
815 # http://www.zoneedit.com/doc/api/other.html
816 # http://www.zoneedit.com/faq.html
818 url
= "https://dynamic.zoneedit.com/auth/dynamic.html"
822 return self
.get("proto")
826 "dnsto" : self
.get_address(self
.proto
),
827 "host" : self
.hostname
830 # Send update to the server.
831 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
834 # Get the full response message.
835 output
= response
.read()
837 # Handle success messages.
838 if output
.startswith("<SUCCESS"):
841 # Handle error codes.
842 if output
.startswith("invalid login"):
843 raise DDNSAuthenticationError
844 elif output
.startswith("<ERROR CODE=\"704\""):
845 raise DDNSRequestError(_("No valid FQDN was given."))
846 elif output
.startswith("<ERROR CODE=\"702\""):
847 raise DDNSInternalServerError
849 # If we got here, some other update error happened.
850 raise DDNSUpdateError