]>
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
.info(_("Updating %s forced") % self
.hostname
)
103 # Check if we actually need to update this host.
104 elif self
.is_uptodate(self
.protocols
):
105 logger
.info(_("%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
):
144 Proxy method to get the current IP address.
146 return self
.core
.system
.get_address(proto
)
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.
186 url
= "http://members.dhs.org/nic/hosts"
190 "domain" : self
.hostname
,
191 "ip" : self
.get_address("ipv4"),
193 "hostcmdstage" : "2",
197 # Send update to the server.
198 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
201 # Handle success messages.
202 if response
.code
== 200:
205 # If we got here, some other update error happened.
206 raise DDNSUpdateError
209 class DDNSProviderDNSpark(DDNSProvider
):
210 handle
= "dnspark.com"
212 website
= "http://dnspark.com/"
213 protocols
= ("ipv4",)
215 # Informations to the used api can be found here:
216 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
217 url
= "https://control.dnspark.com/api/dynamic/update.php"
221 "domain" : self
.hostname
,
222 "ip" : self
.get_address("ipv4"),
225 # Send update to the server.
226 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
229 # Get the full response message.
230 output
= response
.read()
232 # Handle success messages.
233 if output
.startswith("ok") or output
.startswith("nochange"):
236 # Handle error codes.
237 if output
== "unauth":
238 raise DDNSAuthenticationError
239 elif output
== "abuse":
241 elif output
== "blocked":
242 raise DDNSBlockedError
243 elif output
== "nofqdn":
244 raise DDNSRequestError(_("No valid FQDN was given."))
245 elif output
== "nohost":
246 raise DDNSRequestError(_("Invalid hostname specified."))
247 elif output
== "notdyn":
248 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
249 elif output
== "invalid":
250 raise DDNSRequestError(_("Invalid IP address has been sent."))
252 # If we got here, some other update error happened.
253 raise DDNSUpdateError
256 class DDNSProviderDtDNS(DDNSProvider
):
259 website
= "http://dtdns.com/"
260 protocols
= ("ipv4",)
262 # Information about the format of the HTTPS request is to be found
263 # http://www.dtdns.com/dtsite/updatespec
264 url
= "https://www.dtdns.com/api/autodns.cfm"
268 "ip" : self
.get_address("ipv4"),
269 "id" : self
.hostname
,
273 # Send update to the server.
274 response
= self
.send_request(self
.url
, data
=data
)
276 # Get the full response message.
277 output
= response
.read()
279 # Remove all leading and trailing whitespace.
280 output
= output
.strip()
282 # Handle success messages.
283 if "now points to" in output
:
286 # Handle error codes.
287 if output
== "No hostname to update was supplied.":
288 raise DDNSRequestError(_("No hostname specified."))
290 elif output
== "The hostname you supplied is not valid.":
291 raise DDNSRequestError(_("Invalid hostname specified."))
293 elif output
== "The password you supplied is not valid.":
294 raise DDNSAuthenticationError
296 elif output
== "Administration has disabled this account.":
297 raise DDNSRequestError(_("Account has been disabled."))
299 elif output
== "Illegal character in IP.":
300 raise DDNSRequestError(_("Invalid IP address has been sent."))
302 elif output
== "Too many failed requests.":
303 raise DDNSRequestError(_("Too many failed requests."))
305 # If we got here, some other update error happened.
306 raise DDNSUpdateError
309 class DDNSProviderDynDNS(DDNSProvider
):
310 handle
= "dyndns.org"
312 website
= "http://dyn.com/dns/"
313 protocols
= ("ipv4",)
315 # Information about the format of the request is to be found
316 # http://http://dyn.com/support/developers/api/perform-update/
317 # http://dyn.com/support/developers/api/return-codes/
318 url
= "https://members.dyndns.org/nic/update"
320 def _prepare_request_data(self
):
322 "hostname" : self
.hostname
,
323 "myip" : self
.get_address("ipv4"),
329 data
= self
._prepare
_request
_data
()
331 # Send update to the server.
332 response
= self
.send_request(self
.url
, data
=data
,
333 username
=self
.username
, password
=self
.password
)
335 # Get the full response message.
336 output
= response
.read()
338 # Handle success messages.
339 if output
.startswith("good") or output
.startswith("nochg"):
342 # Handle error codes.
343 if output
== "badauth":
344 raise DDNSAuthenticationError
345 elif output
== "aduse":
347 elif output
== "notfqdn":
348 raise DDNSRequestError(_("No valid FQDN was given."))
349 elif output
== "nohost":
350 raise DDNSRequestError(_("Specified host does not exist."))
351 elif output
== "911":
352 raise DDNSInternalServerError
353 elif output
== "dnserr":
354 raise DDNSInternalServerError(_("DNS error encountered."))
356 # If we got here, some other update error happened.
357 raise DDNSUpdateError(_("Server response: %s") % output
)
360 class DDNSProviderDynU(DDNSProviderDynDNS
):
363 website
= "http://dynu.com/"
364 protocols
= ("ipv6", "ipv4",)
366 # Detailed information about the request and response codes
367 # are available on the providers webpage.
368 # http://dynu.com/Default.aspx?page=dnsapi
370 url
= "https://api.dynu.com/nic/update"
372 def _prepare_request_data(self
):
373 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
375 # This one supports IPv6
377 "myipv6" : self
.get_address("ipv6"),
383 class DDNSProviderEasyDNS(DDNSProviderDynDNS
):
384 handle
= "easydns.com"
386 website
= "http://www.easydns.com/"
388 # There is only some basic documentation provided by the vendor,
389 # also searching the web gain very poor results.
390 # http://mediawiki.easydns.com/index.php/Dynamic_DNS
392 url
= "http://api.cp.easydns.com/dyn/tomato.php"
395 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider
):
396 handle
= "freedns.afraid.org"
397 name
= "freedns.afraid.org"
398 website
= "http://freedns.afraid.org/"
400 # No information about the request or response could be found on the vendor
401 # page. All used values have been collected by testing.
402 url
= "https://freedns.afraid.org/dynamic/update.php"
406 return self
.get("proto")
409 address
= self
.get_address(self
.proto
)
415 # Add auth token to the update url.
416 url
= "%s?%s" % (self
.url
, self
.token
)
418 # Send update to the server.
419 response
= self
.send_request(url
, data
=data
)
421 if output
.startswith("Updated") or "has not changed" in output
:
424 # Handle error codes.
425 if output
== "ERROR: Unable to locate this record":
426 raise DDNSAuthenticationError
427 elif "is an invalid IP address" in output
:
428 raise DDNSRequestError(_("Invalid IP address has been sent."))
431 class DDNSProviderLightningWireLabs(DDNSProvider
):
432 handle
= "dns.lightningwirelabs.com"
433 name
= "Lightning Wire Labs"
434 website
= "http://dns.lightningwirelabs.com/"
436 # Information about the format of the HTTPS request is to be found
437 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
438 url
= "https://dns.lightningwirelabs.com/update"
442 "hostname" : self
.hostname
,
445 # Check if we update an IPv6 address.
446 address6
= self
.get_address("ipv6")
448 data
["address6"] = address6
450 # Check if we update an IPv4 address.
451 address4
= self
.get_address("ipv4")
453 data
["address4"] = address4
455 # Raise an error if none address is given.
456 if not data
.has_key("address6") and not data
.has_key("address4"):
457 raise DDNSConfigurationError
459 # Check if a token has been set.
461 data
["token"] = self
.token
463 # Check for username and password.
464 elif self
.username
and self
.password
:
466 "username" : self
.username
,
467 "password" : self
.password
,
470 # Raise an error if no auth details are given.
472 raise DDNSConfigurationError
474 # Send update to the server.
475 response
= self
.send_request(self
.url
, data
=data
)
477 # Handle success messages.
478 if response
.code
== 200:
481 # If we got here, some other update error happened.
482 raise DDNSUpdateError
485 class DDNSProviderNamecheap(DDNSProvider
):
486 handle
= "namecheap.com"
488 website
= "http://namecheap.com"
489 protocols
= ("ipv4",)
491 # Information about the format of the HTTP request is to be found
492 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
493 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
495 url
= "https://dynamicdns.park-your-domain.com/update"
497 def parse_xml(self
, document
, content
):
498 # Send input to the parser.
499 xmldoc
= xml
.dom
.minidom
.parseString(document
)
501 # Get XML elements by the given content.
502 element
= xmldoc
.getElementsByTagName(content
)
504 # If no element has been found, we directly can return None.
508 # Only get the first child from an element, even there are more than one.
509 firstchild
= element
[0].firstChild
511 # Get the value of the child.
512 value
= firstchild
.nodeValue
518 # Namecheap requires the hostname splitted into a host and domain part.
519 host
, domain
= self
.hostname
.split(".", 1)
522 "ip" : self
.get_address("ipv4"),
523 "password" : self
.password
,
528 # Send update to the server.
529 response
= self
.send_request(self
.url
, data
=data
)
531 # Get the full response message.
532 output
= response
.read()
534 # Handle success messages.
535 if self
.parse_xml(output
, "IP") == self
.get_address("ipv4"):
538 # Handle error codes.
539 errorcode
= self
.parse_xml(output
, "ResponseNumber")
541 if errorcode
== "304156":
542 raise DDNSAuthenticationError
543 elif errorcode
== "316153":
544 raise DDNSRequestError(_("Domain not found."))
545 elif errorcode
== "316154":
546 raise DDNSRequestError(_("Domain not active."))
547 elif errorcode
in ("380098", "380099"):
548 raise DDNSInternalServerError
550 # If we got here, some other update error happened.
551 raise DDNSUpdateError
554 class DDNSProviderNOIP(DDNSProviderDynDNS
):
557 website
= "http://www.no-ip.com/"
559 # Information about the format of the HTTP request is to be found
560 # here: http://www.no-ip.com/integrate/request and
561 # here: http://www.no-ip.com/integrate/response
563 url
= "http://dynupdate.no-ip.com/nic/update"
565 def _prepare_request_data(self
):
567 "hostname" : self
.hostname
,
568 "address" : self
.get_address("ipv4"),
574 class DDNSProviderOVH(DDNSProviderDynDNS
):
577 website
= "http://www.ovh.com/"
579 # OVH only provides very limited information about how to
580 # update a DynDNS host. They only provide the update url
581 # on the their german subpage.
583 # http://hilfe.ovh.de/DomainDynHost
585 url
= "https://www.ovh.com/nic/update"
587 def _prepare_request_data(self
):
588 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
596 class DDNSProviderRegfish(DDNSProvider
):
597 handle
= "regfish.com"
598 name
= "Regfish GmbH"
599 website
= "http://www.regfish.com/"
601 # A full documentation to the providers api can be found here
602 # but is only available in german.
603 # https://www.regfish.de/domains/dyndns/dokumentation
605 url
= "https://dyndns.regfish.de/"
609 "fqdn" : self
.hostname
,
612 # Check if we update an IPv6 address.
613 address6
= self
.get_address("ipv6")
615 data
["ipv6"] = address6
617 # Check if we update an IPv4 address.
618 address4
= self
.get_address("ipv4")
620 data
["ipv4"] = address4
622 # Raise an error if none address is given.
623 if not data
.has_key("ipv6") and not data
.has_key("ipv4"):
624 raise DDNSConfigurationError
626 # Check if a token has been set.
628 data
["token"] = self
.token
630 # Raise an error if no token and no useranem and password
632 elif not self
.username
and not self
.password
:
633 raise DDNSConfigurationError(_("No Auth details specified."))
635 # HTTP Basic Auth is only allowed if no token is used.
637 # Send update to the server.
638 response
= self
.send_request(self
.url
, data
=data
)
640 # Send update to the server.
641 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
644 # Get the full response message.
645 output
= response
.read()
647 # Handle success messages.
648 if "100" in output
or "101" in output
:
651 # Handle error codes.
652 if "401" or "402" in output
:
653 raise DDNSAuthenticationError
654 elif "408" in output
:
655 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
656 elif "409" in output
:
657 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
658 elif "412" in output
:
659 raise DDNSRequestError(_("No valid FQDN was given."))
660 elif "414" in output
:
661 raise DDNSInternalServerError
663 # If we got here, some other update error happened.
664 raise DDNSUpdateError
667 class DDNSProviderSelfhost(DDNSProviderDynDNS
):
668 handle
= "selfhost.de"
670 website
= "http://www.selfhost.de/"
672 url
= "https://carol.selfhost.de/nic/update"
674 def _prepare_request_data(self
):
675 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
683 class DDNSProviderSPDNS(DDNSProviderDynDNS
):
686 website
= "http://spdns.org/"
688 # Detailed information about request and response codes are provided
689 # by the vendor. They are using almost the same mechanism and status
690 # codes as dyndns.org so we can inherit all those stuff.
692 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
693 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
695 url
= "https://update.spdns.de/nic/update"
698 class DDNSProviderStrato(DDNSProviderDynDNS
):
699 handle
= "strato.com"
701 website
= "http:/www.strato.com/"
703 # Information about the request and response can be obtained here:
704 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
706 url
= "https://dyndns.strato.com/nic/update"
709 class DDNSProviderTwoDNS(DDNSProviderDynDNS
):
712 website
= "http://www.twodns.de"
714 # Detailed information about the request can be found here
715 # http://twodns.de/en/faqs
716 # http://twodns.de/en/api
718 url
= "https://update.twodns.de/update"
720 def _prepare_request_data(self
):
722 "ip" : self
.get_address("ipv4"),
723 "hostname" : self
.hostname
729 class DDNSProviderUdmedia(DDNSProviderDynDNS
):
730 handle
= "udmedia.de"
731 name
= "Udmedia GmbH"
732 website
= "http://www.udmedia.de"
734 # Information about the request can be found here
735 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
737 url
= "https://www.udmedia.de/nic/update"
740 class DDNSProviderVariomedia(DDNSProviderDynDNS
):
741 handle
= "variomedia.de"
743 website
= "http://www.variomedia.de/"
744 protocols
= ("ipv6", "ipv4",)
746 # Detailed information about the request can be found here
747 # https://dyndns.variomedia.de/
749 url
= "https://dyndns.variomedia.de/nic/update"
753 return self
.get("proto")
755 def _prepare_request_data(self
):
757 "hostname" : self
.hostname
,
758 "myip" : self
.get_address(self
.proto
)
764 class DDNSProviderZoneedit(DDNSProvider
):
765 handle
= "zoneedit.com"
767 website
= "http://www.zoneedit.com"
769 # Detailed information about the request and the response codes can be
771 # http://www.zoneedit.com/doc/api/other.html
772 # http://www.zoneedit.com/faq.html
774 url
= "https://dynamic.zoneedit.com/auth/dynamic.html"
778 return self
.get("proto")
782 "dnsto" : self
.get_address(self
.proto
),
783 "host" : self
.hostname
786 # Send update to the server.
787 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
790 # Get the full response message.
791 output
= response
.read()
793 # Handle success messages.
794 if output
.startswith("<SUCCESS"):
797 # Handle error codes.
798 if output
.startswith("invalid login"):
799 raise DDNSAuthenticationError
800 elif output
.startswith("<ERROR CODE=\"704\""):
801 raise DDNSRequestError(_("No valid FQDN was given."))
802 elif output
.startswith("<ERROR CODE=\"702\""):
803 raise DDNSInternalServerError
805 # If we got here, some other update error happened.
806 raise DDNSUpdateError