]>
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 ###############################################################################
27 # Import all possible exception types.
30 logger
= logging
.getLogger("ddns.providers")
33 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"],
52 def __init__(self
, core
, **settings
):
55 # Copy a set of default settings and
56 # update them by those from the configuration file.
57 self
.settings
= self
.DEFAULT_SETTINGS
.copy()
58 self
.settings
.update(settings
)
61 return "<DDNS Provider %s (%s)>" % (self
.name
, self
.handle
)
63 def __cmp__(self
, other
):
64 return cmp(self
.hostname
, other
.hostname
)
69 Returns the name of the provider.
71 return self
.INFO
.get("name")
76 Returns the website URL of the provider
77 or None if that is not available.
79 return self
.INFO
.get("website", None)
84 Returns the handle of this provider.
86 return self
.INFO
.get("handle")
88 def get(self
, key
, default
=None):
90 Get a setting from the settings dictionary.
92 return self
.settings
.get(key
, default
)
97 Fast access to the hostname.
99 return self
.get("hostname")
104 Fast access to the username.
106 return self
.get("username")
111 Fast access to the password.
113 return self
.get("password")
117 return self
.INFO
.get("protocols")
122 Fast access to the token.
124 return self
.get("token")
126 def __call__(self
, force
=False):
128 logger
.info(_("Updating %s forced") % self
.hostname
)
130 # Check if we actually need to update this host.
131 elif self
.is_uptodate(self
.protocols
):
132 logger
.info(_("%s is already up to date") % self
.hostname
)
135 # Execute the update.
139 raise NotImplementedError
141 def is_uptodate(self
, protos
):
143 Returns True if this host is already up to date
144 and does not need to change the IP address on the
148 addresses
= self
.core
.system
.resolve(self
.hostname
, proto
)
150 current_address
= self
.get_address(proto
)
152 if not current_address
in addresses
:
157 def send_request(self
, *args
, **kwargs
):
159 Proxy connection to the send request
162 return self
.core
.system
.send_request(*args
, **kwargs
)
164 def get_address(self
, proto
):
166 Proxy method to get the current IP address.
168 return self
.core
.system
.get_address(proto
)
171 class DDNSProviderAllInkl(DDNSProvider
):
173 "handle" : "all-inkl.com",
174 "name" : "All-inkl.com",
175 "website" : "http://all-inkl.com/",
176 "protocols" : ["ipv4",]
179 # There are only information provided by the vendor how to
180 # perform an update on a FRITZ Box. Grab requried informations
182 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
184 url
= "http://dyndns.kasserver.com"
188 # There is no additional data required so we directly can
191 # Send request to the server.
192 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
)
194 # Handle 401 HTTP Header (Authentication Error)
195 except urllib2
.HTTPError
, e
:
197 raise DDNSAuthenticationError
201 # Get the full response message.
202 output
= response
.read()
204 # Handle success messages.
205 if output
.startswith("good") or output
.startswith("nochg"):
208 # If we got here, some other update error happened.
209 raise DDNSUpdateError
212 class DDNSProviderDHS(DDNSProvider
):
214 "handle" : "dhs.org",
215 "name" : "DHS International",
216 "website" : "http://dhs.org/",
217 "protocols" : ["ipv4",]
220 # No information about the used update api provided on webpage,
221 # grabed from source code of ez-ipudate.
222 url
= "http://members.dhs.org/nic/hosts"
226 "domain" : self
.hostname
,
227 "ip" : self
.get_address("ipv4"),
229 "hostcmdstage" : "2",
233 # Send update to the server.
234 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
237 # Handle success messages.
238 if response
.code
== 200:
241 # Handle error codes.
242 elif response
.code
== 401:
243 raise DDNSAuthenticationError
245 # If we got here, some other update error happened.
246 raise DDNSUpdateError
249 class DDNSProviderDNSpark(DDNSProvider
):
251 "handle" : "dnspark.com",
253 "website" : "http://dnspark.com/",
254 "protocols" : ["ipv4",]
257 # Informations to the used api can be found here:
258 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
259 url
= "https://control.dnspark.com/api/dynamic/update.php"
263 "domain" : self
.hostname
,
264 "ip" : self
.get_address("ipv4"),
267 # Send update to the server.
268 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
271 # Get the full response message.
272 output
= response
.read()
274 # Handle success messages.
275 if output
.startswith("ok") or output
.startswith("nochange"):
278 # Handle error codes.
279 if output
== "unauth":
280 raise DDNSAuthenticationError
281 elif output
== "abuse":
283 elif output
== "blocked":
284 raise DDNSBlockedError
285 elif output
== "nofqdn":
286 raise DDNSRequestError(_("No valid FQDN was given."))
287 elif output
== "nohost":
288 raise DDNSRequestError(_("Invalid hostname specified."))
289 elif output
== "notdyn":
290 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
291 elif output
== "invalid":
292 raise DDNSRequestError(_("Invalid IP address has been sent."))
294 # If we got here, some other update error happened.
295 raise DDNSUpdateError
298 class DDNSProviderDtDNS(DDNSProvider
):
300 "handle" : "dtdns.com",
302 "website" : "http://dtdns.com/",
303 "protocols" : ["ipv4",]
306 # Information about the format of the HTTPS request is to be found
307 # http://www.dtdns.com/dtsite/updatespec
308 url
= "https://www.dtdns.com/api/autodns.cfm"
312 "ip" : self
.get_address("ipv4"),
313 "id" : self
.hostname
,
317 # Send update to the server.
318 response
= self
.send_request(self
.url
, data
=data
)
320 # Get the full response message.
321 output
= response
.read()
323 # Remove all leading and trailing whitespace.
324 output
= output
.strip()
326 # Handle success messages.
327 if "now points to" in output
:
330 # Handle error codes.
331 if output
== "No hostname to update was supplied.":
332 raise DDNSRequestError(_("No hostname specified."))
334 elif output
== "The hostname you supplied is not valid.":
335 raise DDNSRequestError(_("Invalid hostname specified."))
337 elif output
== "The password you supplied is not valid.":
338 raise DDNSAuthenticationError
340 elif output
== "Administration has disabled this account.":
341 raise DDNSRequestError(_("Account has been disabled."))
343 elif output
== "Illegal character in IP.":
344 raise DDNSRequestError(_("Invalid IP address has been sent."))
346 elif output
== "Too many failed requests.":
347 raise DDNSRequestError(_("Too many failed requests."))
349 # If we got here, some other update error happened.
350 raise DDNSUpdateError
353 class DDNSProviderDynDNS(DDNSProvider
):
355 "handle" : "dyndns.org",
357 "website" : "http://dyn.com/dns/",
358 "protocols" : ["ipv4",]
361 # Information about the format of the request is to be found
362 # http://http://dyn.com/support/developers/api/perform-update/
363 # http://dyn.com/support/developers/api/return-codes/
364 url
= "https://members.dyndns.org/nic/update"
366 def _prepare_request_data(self
):
368 "hostname" : self
.hostname
,
369 "myip" : self
.get_address("ipv4"),
375 data
= self
._prepare
_request
_data
()
377 # Send update to the server.
378 response
= self
.send_request(self
.url
, data
=data
,
379 username
=self
.username
, password
=self
.password
)
381 # Get the full response message.
382 output
= response
.read()
384 # Handle success messages.
385 if output
.startswith("good") or output
.startswith("nochg"):
388 # Handle error codes.
389 if output
== "badauth":
390 raise DDNSAuthenticationError
391 elif output
== "aduse":
393 elif output
== "notfqdn":
394 raise DDNSRequestError(_("No valid FQDN was given."))
395 elif output
== "nohost":
396 raise DDNSRequestError(_("Specified host does not exist."))
397 elif output
== "911":
398 raise DDNSInternalServerError
399 elif output
== "dnserr":
400 raise DDNSInternalServerError(_("DNS error encountered."))
402 # If we got here, some other update error happened.
403 raise DDNSUpdateError
406 class DDNSProviderDynU(DDNSProviderDynDNS
):
408 "handle" : "dynu.com",
410 "website" : "http://dynu.com/",
411 "protocols" : ["ipv6", "ipv4",]
415 # Detailed information about the request and response codes
416 # are available on the providers webpage.
417 # http://dynu.com/Default.aspx?page=dnsapi
419 url
= "https://api.dynu.com/nic/update"
421 def _prepare_request_data(self
):
422 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
424 # This one supports IPv6
426 "myipv6" : self
.get_address("ipv6"),
432 class DDNSProviderEasyDNS(DDNSProviderDynDNS
):
434 "handle" : "easydns.com",
436 "website" : "http://www.easydns.com/",
437 "protocols" : ["ipv4",]
440 # There is only some basic documentation provided by the vendor,
441 # also searching the web gain very poor results.
442 # http://mediawiki.easydns.com/index.php/Dynamic_DNS
444 url
= "http://api.cp.easydns.com/dyn/tomato.php"
447 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider
):
449 "handle" : "freedns.afraid.org",
450 "name" : "freedns.afraid.org",
451 "website" : "http://freedns.afraid.org/",
452 "protocols" : ["ipv6", "ipv4",]
455 # No information about the request or response could be found on the vendor
456 # page. All used values have been collected by testing.
457 url
= "https://freedns.afraid.org/dynamic/update.php"
461 return self
.get("proto")
464 address
= self
.get_address(self
.proto
)
470 # Add auth token to the update url.
471 url
= "%s?%s" % (self
.url
, self
.token
)
473 # Send update to the server.
474 response
= self
.send_request(url
, data
=data
)
476 if output
.startswith("Updated") or "has not changed" in output
:
479 # Handle error codes.
480 if output
== "ERROR: Unable to locate this record":
481 raise DDNSAuthenticationError
482 elif "is an invalid IP address" in output
:
483 raise DDNSRequestError(_("Invalid IP address has been sent."))
486 class DDNSProviderLightningWireLabs(DDNSProvider
):
488 "handle" : "dns.lightningwirelabs.com",
489 "name" : "Lightning Wire Labs",
490 "website" : "http://dns.lightningwirelabs.com/",
491 "protocols" : ["ipv6", "ipv4",]
494 # Information about the format of the HTTPS request is to be found
495 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
496 url
= "https://dns.lightningwirelabs.com/update"
500 "hostname" : self
.hostname
,
503 # Check if we update an IPv6 address.
504 address6
= self
.get_address("ipv6")
506 data
["address6"] = address6
508 # Check if we update an IPv4 address.
509 address4
= self
.get_address("ipv4")
511 data
["address4"] = address4
513 # Raise an error if none address is given.
514 if not data
.has_key("address6") and not data
.has_key("address4"):
515 raise DDNSConfigurationError
517 # Check if a token has been set.
519 data
["token"] = self
.token
521 # Check for username and password.
522 elif self
.username
and self
.password
:
524 "username" : self
.username
,
525 "password" : self
.password
,
528 # Raise an error if no auth details are given.
530 raise DDNSConfigurationError
532 # Send update to the server.
533 response
= self
.send_request(self
.url
, data
=data
)
535 # Handle success messages.
536 if response
.code
== 200:
539 # Handle error codes.
540 if response
.code
== 403:
541 raise DDNSAuthenticationError
542 elif response
.code
== 400:
543 raise DDNSRequestError
545 # If we got here, some other update error happened.
546 raise DDNSUpdateError
549 class DDNSProviderNOIP(DDNSProviderDynDNS
):
551 "handle" : "no-ip.com",
553 "website" : "http://www.no-ip.com/",
554 "protocols" : ["ipv4",]
557 # Information about the format of the HTTP request is to be found
558 # here: http://www.no-ip.com/integrate/request and
559 # here: http://www.no-ip.com/integrate/response
561 url
= "http://dynupdate.no-ip.com/nic/update"
563 def _prepare_request_data(self
):
565 "hostname" : self
.hostname
,
566 "address" : self
.get_address("ipv4"),
572 class DDNSProviderOVH(DDNSProviderDynDNS
):
574 "handle" : "ovh.com",
576 "website" : "http://www.ovh.com/",
577 "protocols" : ["ipv4",]
580 # OVH only provides very limited information about how to
581 # update a DynDNS host. They only provide the update url
582 # on the their german subpage.
584 # http://hilfe.ovh.de/DomainDynHost
586 url
= "https://www.ovh.com/nic/update"
588 def _prepare_request_data(self
):
589 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
597 class DDNSProviderRegfish(DDNSProvider
):
599 "handle" : "regfish.com",
600 "name" : "Regfish GmbH",
601 "website" : "http://www.regfish.com/",
602 "protocols" : ["ipv6", "ipv4",]
605 # A full documentation to the providers api can be found here
606 # but is only available in german.
607 # https://www.regfish.de/domains/dyndns/dokumentation
609 url
= "https://dyndns.regfish.de/"
613 "fqdn" : self
.hostname
,
616 # Check if we update an IPv6 address.
617 address6
= self
.get_address("ipv6")
619 data
["ipv6"] = address6
621 # Check if we update an IPv4 address.
622 address4
= self
.get_address("ipv4")
624 data
["ipv4"] = address4
626 # Raise an error if none address is given.
627 if not data
.has_key("ipv6") and not data
.has_key("ipv4"):
628 raise DDNSConfigurationError
630 # Check if a token has been set.
632 data
["token"] = self
.token
634 # Raise an error if no token and no useranem and password
636 elif not self
.username
and not self
.password
:
637 raise DDNSConfigurationError(_("No Auth details specified."))
639 # HTTP Basic Auth is only allowed if no token is used.
641 # Send update to the server.
642 response
= self
.send_request(self
.url
, data
=data
)
644 # Send update to the server.
645 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
648 # Get the full response message.
649 output
= response
.read()
651 # Handle success messages.
652 if "100" in output
or "101" in output
:
655 # Handle error codes.
656 if "401" or "402" in output
:
657 raise DDNSAuthenticationError
658 elif "408" in output
:
659 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
660 elif "409" in output
:
661 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
662 elif "412" in output
:
663 raise DDNSRequestError(_("No valid FQDN was given."))
664 elif "414" in output
:
665 raise DDNSInternalServerError
667 # If we got here, some other update error happened.
668 raise DDNSUpdateError
671 class DDNSProviderSelfhost(DDNSProvider
):
673 "handle" : "selfhost.de",
674 "name" : "Selfhost.de",
675 "website" : "http://www.selfhost.de/",
676 "protocols" : ["ipv4",],
679 url
= "https://carol.selfhost.de/update"
683 "username" : self
.username
,
684 "password" : self
.password
,
688 response
= self
.send_request(self
.url
, data
=data
)
690 match
= re
.search("status=20(0|4)", response
.read())
692 raise DDNSUpdateError
695 class DDNSProviderSPDNS(DDNSProviderDynDNS
):
697 "handle" : "spdns.org",
699 "website" : "http://spdns.org/",
700 "protocols" : ["ipv4",]
703 # Detailed information about request and response codes are provided
704 # by the vendor. They are using almost the same mechanism and status
705 # codes as dyndns.org so we can inherit all those stuff.
707 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
708 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
710 url
= "https://update.spdns.de/nic/update"
713 class DDNSProviderVariomedia(DDNSProviderDynDNS
):
715 "handle" : "variomedia.de",
716 "name" : "Variomedia",
717 "website" : "http://www.variomedia.de/",
718 "protocols" : ["ipv6", "ipv4",]
721 # Detailed information about the request can be found here
722 # https://dyndns.variomedia.de/
724 url
= "https://dyndns.variomedia.de/nic/update"
728 return self
.get("proto")
730 def _prepare_request_data(self
):
732 "hostname" : self
.hostname
,
733 "myip" : self
.get_address(self
.proto
)