]>
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):
35 # A short string that uniquely identifies
39 # The full name of the provider.
42 # A weburl to the homepage of the provider.
43 # (Where to register a new account?)
46 # A list of supported protocols.
47 protocols
= ("ipv6", "ipv4")
51 def __init__(self
, core
, **settings
):
54 # Copy a set of default settings and
55 # update them by those from the configuration file.
56 self
.settings
= self
.DEFAULT_SETTINGS
.copy()
57 self
.settings
.update(settings
)
60 return "<DDNS Provider %s (%s)>" % (self
.name
, self
.handle
)
62 def __cmp__(self
, other
):
63 return cmp(self
.hostname
, other
.hostname
)
65 def get(self
, key
, default
=None):
67 Get a setting from the settings dictionary.
69 return self
.settings
.get(key
, default
)
74 Fast access to the hostname.
76 return self
.get("hostname")
81 Fast access to the username.
83 return self
.get("username")
88 Fast access to the password.
90 return self
.get("password")
95 Fast access to the token.
97 return self
.get("token")
99 def __call__(self
, force
=False):
101 logger
.debug(_("Updating %s forced") % self
.hostname
)
103 # Check if we actually need to update this host.
104 elif self
.is_uptodate(self
.protocols
):
105 logger
.debug(_("%s is already up to date") % self
.hostname
)
108 # Execute the update.
112 raise NotImplementedError
114 def is_uptodate(self
, protos
):
116 Returns True if this host is already up to date
117 and does not need to change the IP address on the
121 addresses
= self
.core
.system
.resolve(self
.hostname
, proto
)
123 current_address
= self
.get_address(proto
)
125 # If no addresses for the given protocol exist, we
127 if current_address
is None and not addresses
:
130 if not current_address
in addresses
:
135 def send_request(self
, *args
, **kwargs
):
137 Proxy connection to the send request
140 return self
.core
.system
.send_request(*args
, **kwargs
)
142 def get_address(self
, proto
, default
=None):
144 Proxy method to get the current IP address.
146 return self
.core
.system
.get_address(proto
) or default
149 class DDNSProviderAllInkl(DDNSProvider
):
150 handle
= "all-inkl.com"
151 name
= "All-inkl.com"
152 website
= "http://all-inkl.com/"
153 protocols
= ("ipv4",)
155 # There are only information provided by the vendor how to
156 # perform an update on a FRITZ Box. Grab requried informations
158 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
160 url
= "http://dyndns.kasserver.com"
163 # There is no additional data required so we directly can
165 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
)
167 # Get the full response message.
168 output
= response
.read()
170 # Handle success messages.
171 if output
.startswith("good") or output
.startswith("nochg"):
174 # If we got here, some other update error happened.
175 raise DDNSUpdateError
178 class DDNSProviderDHS(DDNSProvider
):
180 name
= "DHS International"
181 website
= "http://dhs.org/"
182 protocols
= ("ipv4",)
184 # No information about the used update api provided on webpage,
185 # grabed from source code of ez-ipudate.
187 url
= "http://members.dhs.org/nic/hosts"
191 "domain" : self
.hostname
,
192 "ip" : self
.get_address("ipv4"),
194 "hostcmdstage" : "2",
198 # Send update to the server.
199 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
202 # Handle success messages.
203 if response
.code
== 200:
206 # If we got here, some other update error happened.
207 raise DDNSUpdateError
210 class DDNSProviderDNSpark(DDNSProvider
):
211 handle
= "dnspark.com"
213 website
= "http://dnspark.com/"
214 protocols
= ("ipv4",)
216 # Informations to the used api can be found here:
217 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
219 url
= "https://control.dnspark.com/api/dynamic/update.php"
223 "domain" : self
.hostname
,
224 "ip" : self
.get_address("ipv4"),
227 # Send update to the server.
228 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
231 # Get the full response message.
232 output
= response
.read()
234 # Handle success messages.
235 if output
.startswith("ok") or output
.startswith("nochange"):
238 # Handle error codes.
239 if output
== "unauth":
240 raise DDNSAuthenticationError
241 elif output
== "abuse":
243 elif output
== "blocked":
244 raise DDNSBlockedError
245 elif output
== "nofqdn":
246 raise DDNSRequestError(_("No valid FQDN was given."))
247 elif output
== "nohost":
248 raise DDNSRequestError(_("Invalid hostname specified."))
249 elif output
== "notdyn":
250 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
251 elif output
== "invalid":
252 raise DDNSRequestError(_("Invalid IP address has been sent."))
254 # If we got here, some other update error happened.
255 raise DDNSUpdateError
258 class DDNSProviderDtDNS(DDNSProvider
):
261 website
= "http://dtdns.com/"
262 protocols
= ("ipv4",)
264 # Information about the format of the HTTPS request is to be found
265 # http://www.dtdns.com/dtsite/updatespec
267 url
= "https://www.dtdns.com/api/autodns.cfm"
271 "ip" : self
.get_address("ipv4"),
272 "id" : self
.hostname
,
276 # Send update to the server.
277 response
= self
.send_request(self
.url
, data
=data
)
279 # Get the full response message.
280 output
= response
.read()
282 # Remove all leading and trailing whitespace.
283 output
= output
.strip()
285 # Handle success messages.
286 if "now points to" in output
:
289 # Handle error codes.
290 if output
== "No hostname to update was supplied.":
291 raise DDNSRequestError(_("No hostname specified."))
293 elif output
== "The hostname you supplied is not valid.":
294 raise DDNSRequestError(_("Invalid hostname specified."))
296 elif output
== "The password you supplied is not valid.":
297 raise DDNSAuthenticationError
299 elif output
== "Administration has disabled this account.":
300 raise DDNSRequestError(_("Account has been disabled."))
302 elif output
== "Illegal character in IP.":
303 raise DDNSRequestError(_("Invalid IP address has been sent."))
305 elif output
== "Too many failed requests.":
306 raise DDNSRequestError(_("Too many failed requests."))
308 # If we got here, some other update error happened.
309 raise DDNSUpdateError
312 class DDNSProviderDynDNS(DDNSProvider
):
313 handle
= "dyndns.org"
315 website
= "http://dyn.com/dns/"
316 protocols
= ("ipv4",)
318 # Information about the format of the request is to be found
319 # http://http://dyn.com/support/developers/api/perform-update/
320 # http://dyn.com/support/developers/api/return-codes/
322 url
= "https://members.dyndns.org/nic/update"
324 def _prepare_request_data(self
):
326 "hostname" : self
.hostname
,
327 "myip" : self
.get_address("ipv4"),
333 data
= self
._prepare
_request
_data
()
335 # Send update to the server.
336 response
= self
.send_request(self
.url
, data
=data
,
337 username
=self
.username
, password
=self
.password
)
339 # Get the full response message.
340 output
= response
.read()
342 # Handle success messages.
343 if output
.startswith("good") or output
.startswith("nochg"):
346 # Handle error codes.
347 if output
== "badauth":
348 raise DDNSAuthenticationError
349 elif output
== "aduse":
351 elif output
== "notfqdn":
352 raise DDNSRequestError(_("No valid FQDN was given."))
353 elif output
== "nohost":
354 raise DDNSRequestError(_("Specified host does not exist."))
355 elif output
== "911":
356 raise DDNSInternalServerError
357 elif output
== "dnserr":
358 raise DDNSInternalServerError(_("DNS error encountered."))
360 # If we got here, some other update error happened.
361 raise DDNSUpdateError(_("Server response: %s") % output
)
364 class DDNSProviderDynU(DDNSProviderDynDNS
):
367 website
= "http://dynu.com/"
368 protocols
= ("ipv6", "ipv4",)
370 # Detailed information about the request and response codes
371 # are available on the providers webpage.
372 # http://dynu.com/Default.aspx?page=dnsapi
374 url
= "https://api.dynu.com/nic/update"
376 def _prepare_request_data(self
):
377 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
379 # This one supports IPv6
381 "myipv6" : self
.get_address("ipv6"),
387 class DDNSProviderEasyDNS(DDNSProviderDynDNS
):
388 handle
= "easydns.com"
390 website
= "http://www.easydns.com/"
392 # There is only some basic documentation provided by the vendor,
393 # also searching the web gain very poor results.
394 # http://mediawiki.easydns.com/index.php/Dynamic_DNS
396 url
= "http://api.cp.easydns.com/dyn/tomato.php"
399 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider
):
400 handle
= "freedns.afraid.org"
401 name
= "freedns.afraid.org"
402 website
= "http://freedns.afraid.org/"
404 # No information about the request or response could be found on the vendor
405 # page. All used values have been collected by testing.
406 url
= "https://freedns.afraid.org/dynamic/update.php"
410 return self
.get("proto")
413 address
= self
.get_address(self
.proto
)
419 # Add auth token to the update url.
420 url
= "%s?%s" % (self
.url
, self
.token
)
422 # Send update to the server.
423 response
= self
.send_request(url
, data
=data
)
425 if output
.startswith("Updated") or "has not changed" in output
:
428 # Handle error codes.
429 if output
== "ERROR: Unable to locate this record":
430 raise DDNSAuthenticationError
431 elif "is an invalid IP address" in output
:
432 raise DDNSRequestError(_("Invalid IP address has been sent."))
434 # If we got here, some other update error happened.
435 raise DDNSUpdateError
438 class DDNSProviderLightningWireLabs(DDNSProvider
):
439 handle
= "dns.lightningwirelabs.com"
440 name
= "Lightning Wire Labs DNS Service"
441 website
= "http://dns.lightningwirelabs.com/"
443 # Information about the format of the HTTPS request is to be found
444 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
446 url
= "https://dns.lightningwirelabs.com/update"
450 "hostname" : self
.hostname
,
451 "address6" : self
.get_address("ipv6", "-"),
452 "address4" : self
.get_address("ipv4", "-"),
455 # Check if a token has been set.
457 data
["token"] = self
.token
459 # Check for username and password.
460 elif self
.username
and self
.password
:
462 "username" : self
.username
,
463 "password" : self
.password
,
466 # Raise an error if no auth details are given.
468 raise DDNSConfigurationError
470 # Send update to the server.
471 response
= self
.send_request(self
.url
, data
=data
)
473 # Handle success messages.
474 if response
.code
== 200:
477 # If we got here, some other update error happened.
478 raise DDNSUpdateError
481 class DDNSProviderNamecheap(DDNSProvider
):
482 handle
= "namecheap.com"
484 website
= "http://namecheap.com"
485 protocols
= ("ipv4",)
487 # Information about the format of the HTTP request is to be found
488 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
489 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
491 url
= "https://dynamicdns.park-your-domain.com/update"
493 def parse_xml(self
, document
, content
):
494 # Send input to the parser.
495 xmldoc
= xml
.dom
.minidom
.parseString(document
)
497 # Get XML elements by the given content.
498 element
= xmldoc
.getElementsByTagName(content
)
500 # If no element has been found, we directly can return None.
504 # Only get the first child from an element, even there are more than one.
505 firstchild
= element
[0].firstChild
507 # Get the value of the child.
508 value
= firstchild
.nodeValue
514 # Namecheap requires the hostname splitted into a host and domain part.
515 host
, domain
= self
.hostname
.split(".", 1)
518 "ip" : self
.get_address("ipv4"),
519 "password" : self
.password
,
524 # Send update to the server.
525 response
= self
.send_request(self
.url
, data
=data
)
527 # Get the full response message.
528 output
= response
.read()
530 # Handle success messages.
531 if self
.parse_xml(output
, "IP") == self
.get_address("ipv4"):
534 # Handle error codes.
535 errorcode
= self
.parse_xml(output
, "ResponseNumber")
537 if errorcode
== "304156":
538 raise DDNSAuthenticationError
539 elif errorcode
== "316153":
540 raise DDNSRequestError(_("Domain not found."))
541 elif errorcode
== "316154":
542 raise DDNSRequestError(_("Domain not active."))
543 elif errorcode
in ("380098", "380099"):
544 raise DDNSInternalServerError
546 # If we got here, some other update error happened.
547 raise DDNSUpdateError
550 class DDNSProviderNOIP(DDNSProviderDynDNS
):
553 website
= "http://www.no-ip.com/"
555 # Information about the format of the HTTP request is to be found
556 # here: http://www.no-ip.com/integrate/request and
557 # here: http://www.no-ip.com/integrate/response
559 url
= "http://dynupdate.no-ip.com/nic/update"
561 def _prepare_request_data(self
):
563 "hostname" : self
.hostname
,
564 "address" : self
.get_address("ipv4"),
570 class DDNSProviderOVH(DDNSProviderDynDNS
):
573 website
= "http://www.ovh.com/"
575 # OVH only provides very limited information about how to
576 # update a DynDNS host. They only provide the update url
577 # on the their german subpage.
579 # http://hilfe.ovh.de/DomainDynHost
581 url
= "https://www.ovh.com/nic/update"
583 def _prepare_request_data(self
):
584 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
592 class DDNSProviderRegfish(DDNSProvider
):
593 handle
= "regfish.com"
594 name
= "Regfish GmbH"
595 website
= "http://www.regfish.com/"
597 # A full documentation to the providers api can be found here
598 # but is only available in german.
599 # https://www.regfish.de/domains/dyndns/dokumentation
601 url
= "https://dyndns.regfish.de/"
605 "fqdn" : self
.hostname
,
608 # Check if we update an IPv6 address.
609 address6
= self
.get_address("ipv6")
611 data
["ipv6"] = address6
613 # Check if we update an IPv4 address.
614 address4
= self
.get_address("ipv4")
616 data
["ipv4"] = address4
618 # Raise an error if none address is given.
619 if not data
.has_key("ipv6") and not data
.has_key("ipv4"):
620 raise DDNSConfigurationError
622 # Check if a token has been set.
624 data
["token"] = self
.token
626 # Raise an error if no token and no useranem and password
628 elif not self
.username
and not self
.password
:
629 raise DDNSConfigurationError(_("No Auth details specified."))
631 # HTTP Basic Auth is only allowed if no token is used.
633 # Send update to the server.
634 response
= self
.send_request(self
.url
, data
=data
)
636 # Send update to the server.
637 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
640 # Get the full response message.
641 output
= response
.read()
643 # Handle success messages.
644 if "100" in output
or "101" in output
:
647 # Handle error codes.
648 if "401" or "402" in output
:
649 raise DDNSAuthenticationError
650 elif "408" in output
:
651 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
652 elif "409" in output
:
653 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
654 elif "412" in output
:
655 raise DDNSRequestError(_("No valid FQDN was given."))
656 elif "414" in output
:
657 raise DDNSInternalServerError
659 # If we got here, some other update error happened.
660 raise DDNSUpdateError
663 class DDNSProviderSelfhost(DDNSProviderDynDNS
):
664 handle
= "selfhost.de"
666 website
= "http://www.selfhost.de/"
668 url
= "https://carol.selfhost.de/nic/update"
670 def _prepare_request_data(self
):
671 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
679 class DDNSProviderSPDNS(DDNSProviderDynDNS
):
682 website
= "http://spdns.org/"
684 # Detailed information about request and response codes are provided
685 # by the vendor. They are using almost the same mechanism and status
686 # codes as dyndns.org so we can inherit all those stuff.
688 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
689 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
691 url
= "https://update.spdns.de/nic/update"
694 class DDNSProviderStrato(DDNSProviderDynDNS
):
695 handle
= "strato.com"
697 website
= "http:/www.strato.com/"
699 # Information about the request and response can be obtained here:
700 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
702 url
= "https://dyndns.strato.com/nic/update"
705 class DDNSProviderTwoDNS(DDNSProviderDynDNS
):
708 website
= "http://www.twodns.de"
710 # Detailed information about the request can be found here
711 # http://twodns.de/en/faqs
712 # http://twodns.de/en/api
714 url
= "https://update.twodns.de/update"
716 def _prepare_request_data(self
):
718 "ip" : self
.get_address("ipv4"),
719 "hostname" : self
.hostname
725 class DDNSProviderUdmedia(DDNSProviderDynDNS
):
726 handle
= "udmedia.de"
727 name
= "Udmedia GmbH"
728 website
= "http://www.udmedia.de"
730 # Information about the request can be found here
731 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
733 url
= "https://www.udmedia.de/nic/update"
736 class DDNSProviderVariomedia(DDNSProviderDynDNS
):
737 handle
= "variomedia.de"
739 website
= "http://www.variomedia.de/"
740 protocols
= ("ipv6", "ipv4",)
742 # Detailed information about the request can be found here
743 # https://dyndns.variomedia.de/
745 url
= "https://dyndns.variomedia.de/nic/update"
749 return self
.get("proto")
751 def _prepare_request_data(self
):
753 "hostname" : self
.hostname
,
754 "myip" : self
.get_address(self
.proto
)
760 class DDNSProviderZoneedit(DDNSProvider
):
761 handle
= "zoneedit.com"
763 website
= "http://www.zoneedit.com"
765 # Detailed information about the request and the response codes can be
767 # http://www.zoneedit.com/doc/api/other.html
768 # http://www.zoneedit.com/faq.html
770 url
= "https://dynamic.zoneedit.com/auth/dynamic.html"
774 return self
.get("proto")
778 "dnsto" : self
.get_address(self
.proto
),
779 "host" : self
.hostname
782 # Send update to the server.
783 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
786 # Get the full response message.
787 output
= response
.read()
789 # Handle success messages.
790 if output
.startswith("<SUCCESS"):
793 # Handle error codes.
794 if output
.startswith("invalid login"):
795 raise DDNSAuthenticationError
796 elif output
.startswith("<ERROR CODE=\"704\""):
797 raise DDNSRequestError(_("No valid FQDN was given."))
798 elif output
.startswith("<ERROR CODE=\"702\""):
799 raise DDNSInternalServerError
801 # If we got here, some other update error happened.
802 raise DDNSUpdateError