]>
git.ipfire.org Git - people/stevee/ddns.git/blob - src/ddns/providers.py
fdc86c69d53778f561d7a303e430f7b6915db7be
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 ###############################################################################
26 # Import all possible exception types.
29 logger
= logging
.getLogger("ddns.providers")
32 class DDNSProvider(object):
34 # A short string that uniquely identifies
38 # The full name of the provider.
41 # A weburl to the homepage of the provider.
42 # (Where to register a new account?)
45 # A list of supported protocols.
46 "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
)
68 Returns the name of the provider.
70 return self
.INFO
.get("name")
75 Returns the website URL of the provider
76 or None if that is not available.
78 return self
.INFO
.get("website", None)
83 Returns the handle of this provider.
85 return self
.INFO
.get("handle")
87 def get(self
, key
, default
=None):
89 Get a setting from the settings dictionary.
91 return self
.settings
.get(key
, default
)
96 Fast access to the hostname.
98 return self
.get("hostname")
103 Fast access to the username.
105 return self
.get("username")
110 Fast access to the password.
112 return self
.get("password")
116 return self
.INFO
.get("protocols")
121 Fast access to the token.
123 return self
.get("token")
125 def __call__(self
, force
=False):
127 logger
.info(_("Updating %s forced") % self
.hostname
)
129 # Check if we actually need to update this host.
130 elif self
.is_uptodate(self
.protocols
):
131 logger
.info(_("%s is already up to date") % self
.hostname
)
134 # Execute the update.
138 raise NotImplementedError
140 def is_uptodate(self
, protos
):
142 Returns True if this host is already up to date
143 and does not need to change the IP address on the
147 addresses
= self
.core
.system
.resolve(self
.hostname
, proto
)
149 current_address
= self
.get_address(proto
)
151 if not current_address
in addresses
:
156 def send_request(self
, *args
, **kwargs
):
158 Proxy connection to the send request
161 return self
.core
.system
.send_request(*args
, **kwargs
)
163 def get_address(self
, proto
):
165 Proxy method to get the current IP address.
167 return self
.core
.system
.get_address(proto
)
170 class DDNSProviderDHS(DDNSProvider
):
172 "handle" : "dhs.org",
173 "name" : "DHS International",
174 "website" : "http://dhs.org/",
175 "protocols" : ["ipv4",]
178 # No information about the used update api provided on webpage,
179 # grabed from source code of ez-ipudate.
180 url
= "http://members.dhs.org/nic/hosts"
184 "domain" : self
.hostname
,
185 "ip" : self
.get_address("ipv4"),
187 "hostcmdstage" : "2",
191 # Send update to the server.
192 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
195 # Handle success messages.
196 if response
.code
== 200:
199 # Handle error codes.
200 elif response
.code
== 401:
201 raise DDNSAuthenticationError
203 # If we got here, some other update error happened.
204 raise DDNSUpdateError
207 class DDNSProviderDNSpark(DDNSProvider
):
209 "handle" : "dnspark.com",
211 "website" : "http://dnspark.com/",
212 "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
):
258 "handle" : "dtdns.com",
260 "website" : "http://dtdns.com/",
261 "protocols" : ["ipv4",]
264 # Information about the format of the HTTPS request is to be found
265 # http://www.dtdns.com/dtsite/updatespec
266 url
= "https://www.dtdns.com/api/autodns.cfm"
270 "ip" : self
.get_address("ipv4"),
271 "id" : self
.hostname
,
275 # Send update to the server.
276 response
= self
.send_request(self
.url
, data
=data
)
278 # Get the full response message.
279 output
= response
.read()
281 # Remove all leading and trailing whitespace.
282 output
= output
.strip()
284 # Handle success messages.
285 if "now points to" in output
:
288 # Handle error codes.
289 if output
== "No hostname to update was supplied.":
290 raise DDNSRequestError(_("No hostname specified."))
292 elif output
== "The hostname you supplied is not valid.":
293 raise DDNSRequestError(_("Invalid hostname specified."))
295 elif output
== "The password you supplied is not valid.":
296 raise DDNSAuthenticationError
298 elif output
== "Administration has disabled this account.":
299 raise DDNSRequestError(_("Account has been disabled."))
301 elif output
== "Illegal character in IP.":
302 raise DDNSRequestError(_("Invalid IP address has been sent."))
304 elif output
== "Too many failed requests.":
305 raise DDNSRequestError(_("Too many failed requests."))
307 # If we got here, some other update error happened.
308 raise DDNSUpdateError
311 class DDNSProviderDynDNS(DDNSProvider
):
313 "handle" : "dyndns.org",
315 "website" : "http://dyn.com/dns/",
316 "protocols" : ["ipv4",]
319 # Information about the format of the request is to be found
320 # http://http://dyn.com/support/developers/api/perform-update/
321 # 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
364 class DDNSProviderDynU(DDNSProviderDynDNS
):
366 "handle" : "dynu.com",
368 "website" : "http://dynu.com/",
369 "protocols" : ["ipv6", "ipv4",]
373 # Detailed information about the request and response codes
374 # are available on the providers webpage.
375 # http://dynu.com/Default.aspx?page=dnsapi
377 url
= "https://api.dynu.com/nic/update"
379 def _prepare_request_data(self
):
380 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
382 # This one supports IPv6
384 "myipv6" : self
.get_address("ipv6"),
390 class DDNSProviderEasyDNS(DDNSProviderDynDNS
):
392 "handle" : "easydns.com",
394 "website" : "http://www.easydns.com/",
395 "protocols" : ["ipv4",]
398 # There is only some basic documentation provided by the vendor,
399 # also searching the web gain very poor results.
400 # http://mediawiki.easydns.com/index.php/Dynamic_DNS
402 url
= "http://api.cp.easydns.com/dyn/tomato.php"
405 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider
):
407 "handle" : "freedns.afraid.org",
408 "name" : "freedns.afraid.org",
409 "website" : "http://freedns.afraid.org/",
410 "protocols" : ["ipv6", "ipv4",]
413 # No information about the request or response could be found on the vendor
414 # page. All used values have been collected by testing.
415 url
= "https://freedns.afraid.org/dynamic/update.php"
419 return self
.get("proto")
422 address
= self
.get_address(self
.proto
)
428 # Add auth token to the update url.
429 url
= "%s?%s" % (self
.url
, self
.token
)
431 # Send update to the server.
432 response
= self
.send_request(url
, data
=data
)
434 if output
.startswith("Updated") or "has not changed" in output
:
437 # Handle error codes.
438 if output
== "ERROR: Unable to locate this record":
439 raise DDNSAuthenticationError
440 elif "is an invalid IP address" in output
:
441 raise DDNSRequestError(_("Invalid IP address has been sent."))
444 class DDNSProviderLightningWireLabs(DDNSProvider
):
446 "handle" : "dns.lightningwirelabs.com",
447 "name" : "Lightning Wire Labs",
448 "website" : "http://dns.lightningwirelabs.com/",
449 "protocols" : ["ipv6", "ipv4",]
452 # Information about the format of the HTTPS request is to be found
453 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
454 url
= "https://dns.lightningwirelabs.com/update"
458 "hostname" : self
.hostname
,
461 # Check if we update an IPv6 address.
462 address6
= self
.get_address("ipv6")
464 data
["address6"] = address6
466 # Check if we update an IPv4 address.
467 address4
= self
.get_address("ipv4")
469 data
["address4"] = address4
471 # Raise an error if none address is given.
472 if not data
.has_key("address6") and not data
.has_key("address4"):
473 raise DDNSConfigurationError
475 # Check if a token has been set.
477 data
["token"] = self
.token
479 # Check for username and password.
480 elif self
.username
and self
.password
:
482 "username" : self
.username
,
483 "password" : self
.password
,
486 # Raise an error if no auth details are given.
488 raise DDNSConfigurationError
490 # Send update to the server.
491 response
= self
.send_request(self
.url
, data
=data
)
493 # Handle success messages.
494 if response
.code
== 200:
497 # Handle error codes.
498 if response
.code
== 403:
499 raise DDNSAuthenticationError
500 elif response
.code
== 400:
501 raise DDNSRequestError
503 # If we got here, some other update error happened.
504 raise DDNSUpdateError
507 class DDNSProviderNOIP(DDNSProviderDynDNS
):
509 "handle" : "no-ip.com",
511 "website" : "http://www.no-ip.com/",
512 "protocols" : ["ipv4",]
515 # Information about the format of the HTTP request is to be found
516 # here: http://www.no-ip.com/integrate/request and
517 # here: http://www.no-ip.com/integrate/response
519 url
= "http://dynupdate.no-ip.com/nic/update"
521 def _prepare_request_data(self
):
523 "hostname" : self
.hostname
,
524 "address" : self
.get_address("ipv4"),
530 class DDNSProviderOVH(DDNSProviderDynDNS
):
532 "handle" : "ovh.com",
534 "website" : "http://www.ovh.com/",
535 "protocols" : ["ipv4",]
538 # OVH only provides very limited information about how to
539 # update a DynDNS host. They only provide the update url
540 # on the their german subpage.
542 # http://hilfe.ovh.de/DomainDynHost
544 url
= "https://www.ovh.com/nic/update"
546 def _prepare_request_data(self
):
547 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
555 class DDNSProviderRegfish(DDNSProvider
):
557 "handle" : "regfish.com",
558 "name" : "Regfish GmbH",
559 "website" : "http://www.regfish.com/",
560 "protocols" : ["ipv6", "ipv4",]
563 # A full documentation to the providers api can be found here
564 # but is only available in german.
565 # https://www.regfish.de/domains/dyndns/dokumentation
567 url
= "https://dyndns.regfish.de/"
571 "fqdn" : self
.hostname
,
574 # Check if we update an IPv6 address.
575 address6
= self
.get_address("ipv6")
577 data
["ipv6"] = address6
579 # Check if we update an IPv4 address.
580 address4
= self
.get_address("ipv4")
582 data
["ipv4"] = address4
584 # Raise an error if none address is given.
585 if not data
.has_key("ipv6") and not data
.has_key("ipv4"):
586 raise DDNSConfigurationError
588 # Check if a token has been set.
590 data
["token"] = self
.token
592 # Raise an error if no token and no useranem and password
594 elif not self
.username
and not self
.password
:
595 raise DDNSConfigurationError(_("No Auth details specified."))
597 # HTTP Basic Auth is only allowed if no token is used.
599 # Send update to the server.
600 response
= self
.send_request(self
.url
, data
=data
)
602 # Send update to the server.
603 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
606 # Get the full response message.
607 output
= response
.read()
609 # Handle success messages.
610 if "100" in output
or "101" in output
:
613 # Handle error codes.
614 if "401" or "402" in output
:
615 raise DDNSAuthenticationError
616 elif "408" in output
:
617 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
618 elif "409" in output
:
619 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
620 elif "412" in output
:
621 raise DDNSRequestError(_("No valid FQDN was given."))
622 elif "414" in output
:
623 raise DDNSInternalServerError
625 # If we got here, some other update error happened.
626 raise DDNSUpdateError
629 class DDNSProviderSelfhost(DDNSProvider
):
631 "handle" : "selfhost.de",
632 "name" : "Selfhost.de",
633 "website" : "http://www.selfhost.de/",
634 "protocols" : ["ipv4",],
637 url
= "https://carol.selfhost.de/update"
641 "username" : self
.username
,
642 "password" : self
.password
,
646 response
= self
.send_request(self
.url
, data
=data
)
648 match
= re
.search("status=20(0|4)", response
.read())
650 raise DDNSUpdateError
653 class DDNSProviderSPDNS(DDNSProviderDynDNS
):
655 "handle" : "spdns.org",
657 "website" : "http://spdns.org/",
658 "protocols" : ["ipv4",]
661 # Detailed information about request and response codes are provided
662 # by the vendor. They are using almost the same mechanism and status
663 # codes as dyndns.org so we can inherit all those stuff.
665 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
666 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
668 url
= "https://update.spdns.de/nic/update"
671 class DDNSProviderTwoDNS(DDNSProviderDynDNS
):
673 "handle" : "twodns.de",
675 "website" : "http://www.twodns.de",
676 "protocols" : ["ipv4",]
679 # Detailed information about the request can be found here
680 # http://twodns.de/en/faqs
681 # http://twodns.de/en/api
683 url
= "https://update.twodns.de/update"
685 def _prepare_request_data(self
):
687 "ip" : self
.get_address("ipv4"),
688 "hostname" : self
.hostname
694 class DDNSProviderVariomedia(DDNSProviderDynDNS
):
696 "handle" : "variomedia.de",
697 "name" : "Variomedia",
698 "website" : "http://www.variomedia.de/",
699 "protocols" : ["ipv6", "ipv4",]
702 # Detailed information about the request can be found here
703 # https://dyndns.variomedia.de/
705 url
= "https://dyndns.variomedia.de/nic/update"
709 return self
.get("proto")
711 def _prepare_request_data(self
):
713 "hostname" : self
.hostname
,
714 "myip" : self
.get_address(self
.proto
)