]>
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):
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 not current_address
in addresses
:
158 def send_request(self
, *args
, **kwargs
):
160 Proxy connection to the send request
163 return self
.core
.system
.send_request(*args
, **kwargs
)
165 def get_address(self
, proto
):
167 Proxy method to get the current IP address.
169 return self
.core
.system
.get_address(proto
)
172 class DDNSProviderAllInkl(DDNSProvider
):
174 "handle" : "all-inkl.com",
175 "name" : "All-inkl.com",
176 "website" : "http://all-inkl.com/",
177 "protocols" : ["ipv4",]
180 # There are only information provided by the vendor how to
181 # perform an update on a FRITZ Box. Grab requried informations
183 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
185 url
= "http://dyndns.kasserver.com"
189 # There is no additional data required so we directly can
192 # Send request to the server.
193 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
)
195 # Handle 401 HTTP Header (Authentication Error)
196 except urllib2
.HTTPError
, e
:
198 raise DDNSAuthenticationError
202 # Get the full response message.
203 output
= response
.read()
205 # Handle success messages.
206 if output
.startswith("good") or output
.startswith("nochg"):
209 # If we got here, some other update error happened.
210 raise DDNSUpdateError
213 class DDNSProviderDHS(DDNSProvider
):
215 "handle" : "dhs.org",
216 "name" : "DHS International",
217 "website" : "http://dhs.org/",
218 "protocols" : ["ipv4",]
221 # No information about the used update api provided on webpage,
222 # grabed from source code of ez-ipudate.
223 url
= "http://members.dhs.org/nic/hosts"
227 "domain" : self
.hostname
,
228 "ip" : self
.get_address("ipv4"),
230 "hostcmdstage" : "2",
234 # Send update to the server.
235 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
238 # Handle success messages.
239 if response
.code
== 200:
242 # Handle error codes.
243 elif response
.code
== 401:
244 raise DDNSAuthenticationError
246 # If we got here, some other update error happened.
247 raise DDNSUpdateError
250 class DDNSProviderDNSpark(DDNSProvider
):
252 "handle" : "dnspark.com",
254 "website" : "http://dnspark.com/",
255 "protocols" : ["ipv4",]
258 # Informations to the used api can be found here:
259 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
260 url
= "https://control.dnspark.com/api/dynamic/update.php"
264 "domain" : self
.hostname
,
265 "ip" : self
.get_address("ipv4"),
268 # Send update to the server.
269 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
272 # Get the full response message.
273 output
= response
.read()
275 # Handle success messages.
276 if output
.startswith("ok") or output
.startswith("nochange"):
279 # Handle error codes.
280 if output
== "unauth":
281 raise DDNSAuthenticationError
282 elif output
== "abuse":
284 elif output
== "blocked":
285 raise DDNSBlockedError
286 elif output
== "nofqdn":
287 raise DDNSRequestError(_("No valid FQDN was given."))
288 elif output
== "nohost":
289 raise DDNSRequestError(_("Invalid hostname specified."))
290 elif output
== "notdyn":
291 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
292 elif output
== "invalid":
293 raise DDNSRequestError(_("Invalid IP address has been sent."))
295 # If we got here, some other update error happened.
296 raise DDNSUpdateError
299 class DDNSProviderDtDNS(DDNSProvider
):
301 "handle" : "dtdns.com",
303 "website" : "http://dtdns.com/",
304 "protocols" : ["ipv4",]
307 # Information about the format of the HTTPS request is to be found
308 # http://www.dtdns.com/dtsite/updatespec
309 url
= "https://www.dtdns.com/api/autodns.cfm"
313 "ip" : self
.get_address("ipv4"),
314 "id" : self
.hostname
,
318 # Send update to the server.
319 response
= self
.send_request(self
.url
, data
=data
)
321 # Get the full response message.
322 output
= response
.read()
324 # Remove all leading and trailing whitespace.
325 output
= output
.strip()
327 # Handle success messages.
328 if "now points to" in output
:
331 # Handle error codes.
332 if output
== "No hostname to update was supplied.":
333 raise DDNSRequestError(_("No hostname specified."))
335 elif output
== "The hostname you supplied is not valid.":
336 raise DDNSRequestError(_("Invalid hostname specified."))
338 elif output
== "The password you supplied is not valid.":
339 raise DDNSAuthenticationError
341 elif output
== "Administration has disabled this account.":
342 raise DDNSRequestError(_("Account has been disabled."))
344 elif output
== "Illegal character in IP.":
345 raise DDNSRequestError(_("Invalid IP address has been sent."))
347 elif output
== "Too many failed requests.":
348 raise DDNSRequestError(_("Too many failed requests."))
350 # If we got here, some other update error happened.
351 raise DDNSUpdateError
354 class DDNSProviderDynDNS(DDNSProvider
):
356 "handle" : "dyndns.org",
358 "website" : "http://dyn.com/dns/",
359 "protocols" : ["ipv4",]
362 # Information about the format of the request is to be found
363 # http://http://dyn.com/support/developers/api/perform-update/
364 # http://dyn.com/support/developers/api/return-codes/
365 url
= "https://members.dyndns.org/nic/update"
367 def _prepare_request_data(self
):
369 "hostname" : self
.hostname
,
370 "myip" : self
.get_address("ipv4"),
376 data
= self
._prepare
_request
_data
()
378 # Send update to the server.
379 response
= self
.send_request(self
.url
, data
=data
,
380 username
=self
.username
, password
=self
.password
)
382 # Get the full response message.
383 output
= response
.read()
385 # Handle success messages.
386 if output
.startswith("good") or output
.startswith("nochg"):
389 # Handle error codes.
390 if output
== "badauth":
391 raise DDNSAuthenticationError
392 elif output
== "aduse":
394 elif output
== "notfqdn":
395 raise DDNSRequestError(_("No valid FQDN was given."))
396 elif output
== "nohost":
397 raise DDNSRequestError(_("Specified host does not exist."))
398 elif output
== "911":
399 raise DDNSInternalServerError
400 elif output
== "dnserr":
401 raise DDNSInternalServerError(_("DNS error encountered."))
403 # If we got here, some other update error happened.
404 raise DDNSUpdateError
407 class DDNSProviderDynU(DDNSProviderDynDNS
):
409 "handle" : "dynu.com",
411 "website" : "http://dynu.com/",
412 "protocols" : ["ipv6", "ipv4",]
416 # Detailed information about the request and response codes
417 # are available on the providers webpage.
418 # http://dynu.com/Default.aspx?page=dnsapi
420 url
= "https://api.dynu.com/nic/update"
422 def _prepare_request_data(self
):
423 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
425 # This one supports IPv6
427 "myipv6" : self
.get_address("ipv6"),
433 class DDNSProviderEasyDNS(DDNSProviderDynDNS
):
435 "handle" : "easydns.com",
437 "website" : "http://www.easydns.com/",
438 "protocols" : ["ipv4",]
441 # There is only some basic documentation provided by the vendor,
442 # also searching the web gain very poor results.
443 # http://mediawiki.easydns.com/index.php/Dynamic_DNS
445 url
= "http://api.cp.easydns.com/dyn/tomato.php"
448 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider
):
450 "handle" : "freedns.afraid.org",
451 "name" : "freedns.afraid.org",
452 "website" : "http://freedns.afraid.org/",
453 "protocols" : ["ipv6", "ipv4",]
456 # No information about the request or response could be found on the vendor
457 # page. All used values have been collected by testing.
458 url
= "https://freedns.afraid.org/dynamic/update.php"
462 return self
.get("proto")
465 address
= self
.get_address(self
.proto
)
471 # Add auth token to the update url.
472 url
= "%s?%s" % (self
.url
, self
.token
)
474 # Send update to the server.
475 response
= self
.send_request(url
, data
=data
)
477 if output
.startswith("Updated") or "has not changed" in output
:
480 # Handle error codes.
481 if output
== "ERROR: Unable to locate this record":
482 raise DDNSAuthenticationError
483 elif "is an invalid IP address" in output
:
484 raise DDNSRequestError(_("Invalid IP address has been sent."))
487 class DDNSProviderLightningWireLabs(DDNSProvider
):
489 "handle" : "dns.lightningwirelabs.com",
490 "name" : "Lightning Wire Labs",
491 "website" : "http://dns.lightningwirelabs.com/",
492 "protocols" : ["ipv6", "ipv4",]
495 # Information about the format of the HTTPS request is to be found
496 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
497 url
= "https://dns.lightningwirelabs.com/update"
501 "hostname" : self
.hostname
,
504 # Check if we update an IPv6 address.
505 address6
= self
.get_address("ipv6")
507 data
["address6"] = address6
509 # Check if we update an IPv4 address.
510 address4
= self
.get_address("ipv4")
512 data
["address4"] = address4
514 # Raise an error if none address is given.
515 if not data
.has_key("address6") and not data
.has_key("address4"):
516 raise DDNSConfigurationError
518 # Check if a token has been set.
520 data
["token"] = self
.token
522 # Check for username and password.
523 elif self
.username
and self
.password
:
525 "username" : self
.username
,
526 "password" : self
.password
,
529 # Raise an error if no auth details are given.
531 raise DDNSConfigurationError
533 # Send update to the server.
534 response
= self
.send_request(self
.url
, data
=data
)
536 # Handle success messages.
537 if response
.code
== 200:
540 # Handle error codes.
541 if response
.code
== 403:
542 raise DDNSAuthenticationError
543 elif response
.code
== 400:
544 raise DDNSRequestError
546 # If we got here, some other update error happened.
547 raise DDNSUpdateError
550 class DDNSProviderNamecheap(DDNSProvider
):
552 "handle" : "namecheap.com",
553 "name" : "Namecheap",
554 "website" : "http://namecheap.com",
555 "protocols" : ["ipv4",]
558 # Information about the format of the HTTP request is to be found
559 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
560 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
562 url
= "https://dynamicdns.park-your-domain.com/update"
564 def parse_xml(self
, document
, content
):
565 # Send input to the parser.
566 xmldoc
= xml
.dom
.minidom
.parseString(document
)
568 # Get XML elements by the given content.
569 element
= xmldoc
.getElementsByTagName(content
)
571 # If no element has been found, we directly can return None.
575 # Only get the first child from an element, even there are more than one.
576 firstchild
= element
[0].firstChild
578 # Get the value of the child.
579 value
= firstchild
.nodeValue
585 # Namecheap requires the hostname splitted into a host and domain part.
586 host
, domain
= self
.hostname
.split(".", 1)
589 "ip" : self
.get_address("ipv4"),
590 "password" : self
.password
,
595 # Send update to the server.
596 response
= self
.send_request(self
.url
, data
=data
)
598 # Get the full response message.
599 output
= response
.read()
601 # Handle success messages.
602 if self
.parse_xml(output
, "IP") == self
.get_address("ipv4"):
605 # Handle error codes.
606 errorcode
= self
.parse_xml(output
, "ResponseNumber")
608 if errorcode
== "304156":
609 raise DDNSAuthenticationError
610 elif errorcode
== "316153":
611 raise DDNSRequestError(_("Domain not found."))
612 elif errorcode
== "316154":
613 raise DDNSRequestError(_("Domain not active."))
614 elif errorcode
in ("380098", "380099"):
615 raise DDNSInternalServerError
617 # If we got here, some other update error happened.
618 raise DDNSUpdateError
621 class DDNSProviderNOIP(DDNSProviderDynDNS
):
623 "handle" : "no-ip.com",
625 "website" : "http://www.no-ip.com/",
626 "protocols" : ["ipv4",]
629 # Information about the format of the HTTP request is to be found
630 # here: http://www.no-ip.com/integrate/request and
631 # here: http://www.no-ip.com/integrate/response
633 url
= "http://dynupdate.no-ip.com/nic/update"
635 def _prepare_request_data(self
):
637 "hostname" : self
.hostname
,
638 "address" : self
.get_address("ipv4"),
644 class DDNSProviderOVH(DDNSProviderDynDNS
):
646 "handle" : "ovh.com",
648 "website" : "http://www.ovh.com/",
649 "protocols" : ["ipv4",]
652 # OVH only provides very limited information about how to
653 # update a DynDNS host. They only provide the update url
654 # on the their german subpage.
656 # http://hilfe.ovh.de/DomainDynHost
658 url
= "https://www.ovh.com/nic/update"
660 def _prepare_request_data(self
):
661 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
669 class DDNSProviderRegfish(DDNSProvider
):
671 "handle" : "regfish.com",
672 "name" : "Regfish GmbH",
673 "website" : "http://www.regfish.com/",
674 "protocols" : ["ipv6", "ipv4",]
677 # A full documentation to the providers api can be found here
678 # but is only available in german.
679 # https://www.regfish.de/domains/dyndns/dokumentation
681 url
= "https://dyndns.regfish.de/"
685 "fqdn" : self
.hostname
,
688 # Check if we update an IPv6 address.
689 address6
= self
.get_address("ipv6")
691 data
["ipv6"] = address6
693 # Check if we update an IPv4 address.
694 address4
= self
.get_address("ipv4")
696 data
["ipv4"] = address4
698 # Raise an error if none address is given.
699 if not data
.has_key("ipv6") and not data
.has_key("ipv4"):
700 raise DDNSConfigurationError
702 # Check if a token has been set.
704 data
["token"] = self
.token
706 # Raise an error if no token and no useranem and password
708 elif not self
.username
and not self
.password
:
709 raise DDNSConfigurationError(_("No Auth details specified."))
711 # HTTP Basic Auth is only allowed if no token is used.
713 # Send update to the server.
714 response
= self
.send_request(self
.url
, data
=data
)
716 # Send update to the server.
717 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
720 # Get the full response message.
721 output
= response
.read()
723 # Handle success messages.
724 if "100" in output
or "101" in output
:
727 # Handle error codes.
728 if "401" or "402" in output
:
729 raise DDNSAuthenticationError
730 elif "408" in output
:
731 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
732 elif "409" in output
:
733 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
734 elif "412" in output
:
735 raise DDNSRequestError(_("No valid FQDN was given."))
736 elif "414" in output
:
737 raise DDNSInternalServerError
739 # If we got here, some other update error happened.
740 raise DDNSUpdateError
743 class DDNSProviderSelfhost(DDNSProvider
):
745 "handle" : "selfhost.de",
746 "name" : "Selfhost.de",
747 "website" : "http://www.selfhost.de/",
748 "protocols" : ["ipv4",],
751 url
= "https://carol.selfhost.de/update"
755 "username" : self
.username
,
756 "password" : self
.password
,
760 response
= self
.send_request(self
.url
, data
=data
)
762 match
= re
.search("status=20(0|4)", response
.read())
764 raise DDNSUpdateError
767 class DDNSProviderSPDNS(DDNSProviderDynDNS
):
769 "handle" : "spdns.org",
771 "website" : "http://spdns.org/",
772 "protocols" : ["ipv4",]
775 # Detailed information about request and response codes are provided
776 # by the vendor. They are using almost the same mechanism and status
777 # codes as dyndns.org so we can inherit all those stuff.
779 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
780 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
782 url
= "https://update.spdns.de/nic/update"
785 class DDNSProviderStrato(DDNSProviderDynDNS
):
787 "handle" : "strato.com",
788 "name" : "Strato AG",
789 "website" : "http:/www.strato.com/",
790 "protocols" : ["ipv4",]
793 # Information about the request and response can be obtained here:
794 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
796 url
= "https://dyndns.strato.com/nic/update"
799 class DDNSProviderTwoDNS(DDNSProviderDynDNS
):
801 "handle" : "twodns.de",
803 "website" : "http://www.twodns.de",
804 "protocols" : ["ipv4",]
807 # Detailed information about the request can be found here
808 # http://twodns.de/en/faqs
809 # http://twodns.de/en/api
811 url
= "https://update.twodns.de/update"
813 def _prepare_request_data(self
):
815 "ip" : self
.get_address("ipv4"),
816 "hostname" : self
.hostname
822 class DDNSProviderUdmedia(DDNSProviderDynDNS
):
824 "handle" : "udmedia.de",
825 "name" : "Udmedia GmbH",
826 "website" : "http://www.udmedia.de",
827 "protocols" : ["ipv4",]
830 # Information about the request can be found here
831 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
833 url
= "https://www.udmedia.de/nic/update"
836 class DDNSProviderVariomedia(DDNSProviderDynDNS
):
838 "handle" : "variomedia.de",
839 "name" : "Variomedia",
840 "website" : "http://www.variomedia.de/",
841 "protocols" : ["ipv6", "ipv4",]
844 # Detailed information about the request can be found here
845 # https://dyndns.variomedia.de/
847 url
= "https://dyndns.variomedia.de/nic/update"
851 return self
.get("proto")
853 def _prepare_request_data(self
):
855 "hostname" : self
.hostname
,
856 "myip" : self
.get_address(self
.proto
)
862 class DDNSProviderZoneedit(DDNSProvider
):
864 "handle" : "zoneedit.com",
866 "website" : "http://www.zoneedit.com",
867 "protocols" : ["ipv6", "ipv4",]
870 # Detailed information about the request and the response codes can be
872 # http://www.zoneedit.com/doc/api/other.html
873 # http://www.zoneedit.com/faq.html
875 url
= "https://dynamic.zoneedit.com/auth/dynamic.html"
879 return self
.get("proto")
883 "dnsto" : self
.get_address(self
.proto
),
884 "host" : self
.hostname
887 # Send update to the server.
888 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
891 # Get the full response message.
892 output
= response
.read()
894 # Handle success messages.
895 if output
.startswith("<SUCCESS"):
898 # Handle error codes.
899 if output
.startswith("invalid login"):
900 raise DDNSAuthenticationError
901 elif output
.startswith("<ERROR CODE=\"704\""):
902 raise DDNSRequestError(_("No valid FQDN was given."))
903 elif output
.startswith("<ERROR CODE=\"702\""):
904 raise DDNSInternalServerError
906 # If we got here, some other update error happened.
907 raise DDNSUpdateError