]>
git.ipfire.org Git - oddments/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 DDNSProtocolDynDNS2(object):
151 This is an abstract class that implements the DynDNS updater
152 protocol version 2. As this is a popular way to update dynamic
153 DNS records, this class is supposed make the provider classes
157 # Information about the format of the request is to be found
158 # http://http://dyn.com/support/developers/api/perform-update/
159 # http://dyn.com/support/developers/api/return-codes/
161 def _prepare_request_data(self
):
163 "hostname" : self
.hostname
,
164 "myip" : self
.get_address("ipv4"),
170 data
= self
._prepare
_request
_data
()
172 # Send update to the server.
173 response
= self
.send_request(self
.url
, data
=data
,
174 username
=self
.username
, password
=self
.password
)
176 # Get the full response message.
177 output
= response
.read()
179 # Handle success messages.
180 if output
.startswith("good") or output
.startswith("nochg"):
183 # Handle error codes.
184 if output
== "badauth":
185 raise DDNSAuthenticationError
186 elif output
== "aduse":
188 elif output
== "notfqdn":
189 raise DDNSRequestError(_("No valid FQDN was given."))
190 elif output
== "nohost":
191 raise DDNSRequestError(_("Specified host does not exist."))
192 elif output
== "911":
193 raise DDNSInternalServerError
194 elif output
== "dnserr":
195 raise DDNSInternalServerError(_("DNS error encountered."))
197 # If we got here, some other update error happened.
198 raise DDNSUpdateError(_("Server response: %s") % output
)
201 class DDNSProviderAllInkl(DDNSProvider
):
202 handle
= "all-inkl.com"
203 name
= "All-inkl.com"
204 website
= "http://all-inkl.com/"
205 protocols
= ("ipv4",)
207 # There are only information provided by the vendor how to
208 # perform an update on a FRITZ Box. Grab requried informations
210 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
212 url
= "http://dyndns.kasserver.com"
215 # There is no additional data required so we directly can
217 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
)
219 # Get the full response message.
220 output
= response
.read()
222 # Handle success messages.
223 if output
.startswith("good") or output
.startswith("nochg"):
226 # If we got here, some other update error happened.
227 raise DDNSUpdateError
230 class DDNSProviderDHS(DDNSProvider
):
232 name
= "DHS International"
233 website
= "http://dhs.org/"
234 protocols
= ("ipv4",)
236 # No information about the used update api provided on webpage,
237 # grabed from source code of ez-ipudate.
239 url
= "http://members.dhs.org/nic/hosts"
243 "domain" : self
.hostname
,
244 "ip" : self
.get_address("ipv4"),
246 "hostcmdstage" : "2",
250 # Send update to the server.
251 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
254 # Handle success messages.
255 if response
.code
== 200:
258 # If we got here, some other update error happened.
259 raise DDNSUpdateError
262 class DDNSProviderDNSpark(DDNSProvider
):
263 handle
= "dnspark.com"
265 website
= "http://dnspark.com/"
266 protocols
= ("ipv4",)
268 # Informations to the used api can be found here:
269 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
271 url
= "https://control.dnspark.com/api/dynamic/update.php"
275 "domain" : self
.hostname
,
276 "ip" : self
.get_address("ipv4"),
279 # Send update to the server.
280 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
283 # Get the full response message.
284 output
= response
.read()
286 # Handle success messages.
287 if output
.startswith("ok") or output
.startswith("nochange"):
290 # Handle error codes.
291 if output
== "unauth":
292 raise DDNSAuthenticationError
293 elif output
== "abuse":
295 elif output
== "blocked":
296 raise DDNSBlockedError
297 elif output
== "nofqdn":
298 raise DDNSRequestError(_("No valid FQDN was given."))
299 elif output
== "nohost":
300 raise DDNSRequestError(_("Invalid hostname specified."))
301 elif output
== "notdyn":
302 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
303 elif output
== "invalid":
304 raise DDNSRequestError(_("Invalid IP address has been sent."))
306 # If we got here, some other update error happened.
307 raise DDNSUpdateError
310 class DDNSProviderDtDNS(DDNSProvider
):
313 website
= "http://dtdns.com/"
314 protocols
= ("ipv4",)
316 # Information about the format of the HTTPS request is to be found
317 # http://www.dtdns.com/dtsite/updatespec
319 url
= "https://www.dtdns.com/api/autodns.cfm"
323 "ip" : self
.get_address("ipv4"),
324 "id" : self
.hostname
,
328 # Send update to the server.
329 response
= self
.send_request(self
.url
, data
=data
)
331 # Get the full response message.
332 output
= response
.read()
334 # Remove all leading and trailing whitespace.
335 output
= output
.strip()
337 # Handle success messages.
338 if "now points to" in output
:
341 # Handle error codes.
342 if output
== "No hostname to update was supplied.":
343 raise DDNSRequestError(_("No hostname specified."))
345 elif output
== "The hostname you supplied is not valid.":
346 raise DDNSRequestError(_("Invalid hostname specified."))
348 elif output
== "The password you supplied is not valid.":
349 raise DDNSAuthenticationError
351 elif output
== "Administration has disabled this account.":
352 raise DDNSRequestError(_("Account has been disabled."))
354 elif output
== "Illegal character in IP.":
355 raise DDNSRequestError(_("Invalid IP address has been sent."))
357 elif output
== "Too many failed requests.":
358 raise DDNSRequestError(_("Too many failed requests."))
360 # If we got here, some other update error happened.
361 raise DDNSUpdateError
364 class DDNSProviderDynDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
365 handle
= "dyndns.org"
367 website
= "http://dyn.com/dns/"
368 protocols
= ("ipv4",)
370 # Information about the format of the request is to be found
371 # http://http://dyn.com/support/developers/api/perform-update/
372 # http://dyn.com/support/developers/api/return-codes/
374 url
= "https://members.dyndns.org/nic/update"
377 class DDNSProviderDynU(DDNSProtocolDynDNS2
, DDNSProvider
):
380 website
= "http://dynu.com/"
381 protocols
= ("ipv6", "ipv4",)
383 # Detailed information about the request and response codes
384 # are available on the providers webpage.
385 # http://dynu.com/Default.aspx?page=dnsapi
387 url
= "https://api.dynu.com/nic/update"
389 def _prepare_request_data(self
):
390 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
392 # This one supports IPv6
394 "myipv6" : self
.get_address("ipv6"),
400 class DDNSProviderEasyDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
401 handle
= "easydns.com"
403 website
= "http://www.easydns.com/"
404 protocols
= ("ipv4",)
406 # There is only some basic documentation provided by the vendor,
407 # also searching the web gain very poor results.
408 # http://mediawiki.easydns.com/index.php/Dynamic_DNS
410 url
= "http://api.cp.easydns.com/dyn/tomato.php"
413 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider
):
414 handle
= "freedns.afraid.org"
415 name
= "freedns.afraid.org"
416 website
= "http://freedns.afraid.org/"
418 # No information about the request or response could be found on the vendor
419 # page. All used values have been collected by testing.
420 url
= "https://freedns.afraid.org/dynamic/update.php"
424 return self
.get("proto")
427 address
= self
.get_address(self
.proto
)
433 # Add auth token to the update url.
434 url
= "%s?%s" % (self
.url
, self
.token
)
436 # Send update to the server.
437 response
= self
.send_request(url
, data
=data
)
439 if output
.startswith("Updated") or "has not changed" in output
:
442 # Handle error codes.
443 if output
== "ERROR: Unable to locate this record":
444 raise DDNSAuthenticationError
445 elif "is an invalid IP address" in output
:
446 raise DDNSRequestError(_("Invalid IP address has been sent."))
448 # If we got here, some other update error happened.
449 raise DDNSUpdateError
452 class DDNSProviderLightningWireLabs(DDNSProvider
):
453 handle
= "dns.lightningwirelabs.com"
454 name
= "Lightning Wire Labs DNS Service"
455 website
= "http://dns.lightningwirelabs.com/"
457 # Information about the format of the HTTPS request is to be found
458 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
460 url
= "https://dns.lightningwirelabs.com/update"
464 "hostname" : self
.hostname
,
465 "address6" : self
.get_address("ipv6", "-"),
466 "address4" : self
.get_address("ipv4", "-"),
469 # Check if a token has been set.
471 data
["token"] = self
.token
473 # Check for username and password.
474 elif self
.username
and self
.password
:
476 "username" : self
.username
,
477 "password" : self
.password
,
480 # Raise an error if no auth details are given.
482 raise DDNSConfigurationError
484 # Send update to the server.
485 response
= self
.send_request(self
.url
, data
=data
)
487 # Handle success messages.
488 if response
.code
== 200:
491 # If we got here, some other update error happened.
492 raise DDNSUpdateError
495 class DDNSProviderNamecheap(DDNSProvider
):
496 handle
= "namecheap.com"
498 website
= "http://namecheap.com"
499 protocols
= ("ipv4",)
501 # Information about the format of the HTTP request is to be found
502 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
503 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
505 url
= "https://dynamicdns.park-your-domain.com/update"
507 def parse_xml(self
, document
, content
):
508 # Send input to the parser.
509 xmldoc
= xml
.dom
.minidom
.parseString(document
)
511 # Get XML elements by the given content.
512 element
= xmldoc
.getElementsByTagName(content
)
514 # If no element has been found, we directly can return None.
518 # Only get the first child from an element, even there are more than one.
519 firstchild
= element
[0].firstChild
521 # Get the value of the child.
522 value
= firstchild
.nodeValue
528 # Namecheap requires the hostname splitted into a host and domain part.
529 host
, domain
= self
.hostname
.split(".", 1)
532 "ip" : self
.get_address("ipv4"),
533 "password" : self
.password
,
538 # Send update to the server.
539 response
= self
.send_request(self
.url
, data
=data
)
541 # Get the full response message.
542 output
= response
.read()
544 # Handle success messages.
545 if self
.parse_xml(output
, "IP") == self
.get_address("ipv4"):
548 # Handle error codes.
549 errorcode
= self
.parse_xml(output
, "ResponseNumber")
551 if errorcode
== "304156":
552 raise DDNSAuthenticationError
553 elif errorcode
== "316153":
554 raise DDNSRequestError(_("Domain not found."))
555 elif errorcode
== "316154":
556 raise DDNSRequestError(_("Domain not active."))
557 elif errorcode
in ("380098", "380099"):
558 raise DDNSInternalServerError
560 # If we got here, some other update error happened.
561 raise DDNSUpdateError
564 class DDNSProviderNOIP(DDNSProtocolDynDNS2
, DDNSProvider
):
567 website
= "http://www.no-ip.com/"
568 protocols
= ("ipv4",)
570 # Information about the format of the HTTP request is to be found
571 # here: http://www.no-ip.com/integrate/request and
572 # here: http://www.no-ip.com/integrate/response
574 url
= "http://dynupdate.no-ip.com/nic/update"
576 def _prepare_request_data(self
):
578 "hostname" : self
.hostname
,
579 "address" : self
.get_address("ipv4"),
585 class DDNSProviderOVH(DDNSProtocolDynDNS2
, DDNSProvider
):
588 website
= "http://www.ovh.com/"
589 protocols
= ("ipv4",)
591 # OVH only provides very limited information about how to
592 # update a DynDNS host. They only provide the update url
593 # on the their german subpage.
595 # http://hilfe.ovh.de/DomainDynHost
597 url
= "https://www.ovh.com/nic/update"
599 def _prepare_request_data(self
):
600 data
= DDNSProtocolDynDNS2
._prepare_request_data(self
)
608 class DDNSProviderRegfish(DDNSProvider
):
609 handle
= "regfish.com"
610 name
= "Regfish GmbH"
611 website
= "http://www.regfish.com/"
613 # A full documentation to the providers api can be found here
614 # but is only available in german.
615 # https://www.regfish.de/domains/dyndns/dokumentation
617 url
= "https://dyndns.regfish.de/"
621 "fqdn" : self
.hostname
,
624 # Check if we update an IPv6 address.
625 address6
= self
.get_address("ipv6")
627 data
["ipv6"] = address6
629 # Check if we update an IPv4 address.
630 address4
= self
.get_address("ipv4")
632 data
["ipv4"] = address4
634 # Raise an error if none address is given.
635 if not data
.has_key("ipv6") and not data
.has_key("ipv4"):
636 raise DDNSConfigurationError
638 # Check if a token has been set.
640 data
["token"] = self
.token
642 # Raise an error if no token and no useranem and password
644 elif not self
.username
and not self
.password
:
645 raise DDNSConfigurationError(_("No Auth details specified."))
647 # HTTP Basic Auth is only allowed if no token is used.
649 # Send update to the server.
650 response
= self
.send_request(self
.url
, data
=data
)
652 # Send update to the server.
653 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
656 # Get the full response message.
657 output
= response
.read()
659 # Handle success messages.
660 if "100" in output
or "101" in output
:
663 # Handle error codes.
664 if "401" or "402" in output
:
665 raise DDNSAuthenticationError
666 elif "408" in output
:
667 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
668 elif "409" in output
:
669 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
670 elif "412" in output
:
671 raise DDNSRequestError(_("No valid FQDN was given."))
672 elif "414" in output
:
673 raise DDNSInternalServerError
675 # If we got here, some other update error happened.
676 raise DDNSUpdateError
679 class DDNSProviderSelfhost(DDNSProtocolDynDNS2
, DDNSProvider
):
680 handle
= "selfhost.de"
682 website
= "http://www.selfhost.de/"
683 protocols
= ("ipv4",)
685 url
= "https://carol.selfhost.de/nic/update"
687 def _prepare_request_data(self
):
688 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
696 class DDNSProviderSPDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
699 website
= "http://spdns.org/"
700 protocols
= ("ipv4",)
702 # Detailed information about request and response codes are provided
703 # by the vendor. They are using almost the same mechanism and status
704 # codes as dyndns.org so we can inherit all those stuff.
706 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
707 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
709 url
= "https://update.spdns.de/nic/update"
712 class DDNSProviderStrato(DDNSProtocolDynDNS2
, DDNSProvider
):
713 handle
= "strato.com"
715 website
= "http:/www.strato.com/"
716 protocols
= ("ipv4",)
718 # Information about the request and response can be obtained here:
719 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
721 url
= "https://dyndns.strato.com/nic/update"
724 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
727 website
= "http://www.twodns.de"
728 protocols
= ("ipv4",)
730 # Detailed information about the request can be found here
731 # http://twodns.de/en/faqs
732 # http://twodns.de/en/api
734 url
= "https://update.twodns.de/update"
736 def _prepare_request_data(self
):
738 "ip" : self
.get_address("ipv4"),
739 "hostname" : self
.hostname
745 class DDNSProviderUdmedia(DDNSProtocolDynDNS2
, DDNSProvider
):
746 handle
= "udmedia.de"
747 name
= "Udmedia GmbH"
748 website
= "http://www.udmedia.de"
749 protocols
= ("ipv4",)
751 # Information about the request can be found here
752 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
754 url
= "https://www.udmedia.de/nic/update"
757 class DDNSProviderVariomedia(DDNSProtocolDynDNS2
, DDNSProvider
):
758 handle
= "variomedia.de"
760 website
= "http://www.variomedia.de/"
761 protocols
= ("ipv6", "ipv4",)
763 # Detailed information about the request can be found here
764 # https://dyndns.variomedia.de/
766 url
= "https://dyndns.variomedia.de/nic/update"
770 return self
.get("proto")
772 def _prepare_request_data(self
):
774 "hostname" : self
.hostname
,
775 "myip" : self
.get_address(self
.proto
)
781 class DDNSProviderZoneedit(DDNSProtocolDynDNS2
, DDNSProvider
):
782 handle
= "zoneedit.com"
784 website
= "http://www.zoneedit.com"
785 protocols
= ("ipv4",)
787 # Detailed information about the request and the response codes can be
789 # http://www.zoneedit.com/doc/api/other.html
790 # http://www.zoneedit.com/faq.html
792 url
= "https://dynamic.zoneedit.com/auth/dynamic.html"
796 return self
.get("proto")
800 "dnsto" : self
.get_address(self
.proto
),
801 "host" : self
.hostname
804 # Send update to the server.
805 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
808 # Get the full response message.
809 output
= response
.read()
811 # Handle success messages.
812 if output
.startswith("<SUCCESS"):
815 # Handle error codes.
816 if output
.startswith("invalid login"):
817 raise DDNSAuthenticationError
818 elif output
.startswith("<ERROR CODE=\"704\""):
819 raise DDNSRequestError(_("No valid FQDN was given."))
820 elif output
.startswith("<ERROR CODE=\"702\""):
821 raise DDNSInternalServerError
823 # If we got here, some other update error happened.
824 raise DDNSUpdateError