]>
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 ###############################################################################
23 import xml
.dom
.minidom
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 DDNSProviderDHS(DDNSProvider
):
173 "handle" : "dhs.org",
174 "name" : "DHS International",
175 "website" : "http://dhs.org/",
176 "protocols" : ["ipv4",]
179 # No information about the used update api provided on webpage,
180 # grabed from source code of ez-ipudate.
181 url
= "http://members.dhs.org/nic/hosts"
185 "domain" : self
.hostname
,
186 "ip" : self
.get_address("ipv4"),
188 "hostcmdstage" : "2",
192 # Send update to the server.
193 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
196 # Handle success messages.
197 if response
.code
== 200:
200 # Handle error codes.
201 elif response
.code
== 401:
202 raise DDNSAuthenticationError
204 # If we got here, some other update error happened.
205 raise DDNSUpdateError
208 class DDNSProviderDNSpark(DDNSProvider
):
210 "handle" : "dnspark.com",
212 "website" : "http://dnspark.com/",
213 "protocols" : ["ipv4",]
216 # Informations to the used api can be found here:
217 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
218 url
= "https://control.dnspark.com/api/dynamic/update.php"
222 "domain" : self
.hostname
,
223 "ip" : self
.get_address("ipv4"),
226 # Send update to the server.
227 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
230 # Get the full response message.
231 output
= response
.read()
233 # Handle success messages.
234 if output
.startswith("ok") or output
.startswith("nochange"):
237 # Handle error codes.
238 if output
== "unauth":
239 raise DDNSAuthenticationError
240 elif output
== "abuse":
242 elif output
== "blocked":
243 raise DDNSBlockedError
244 elif output
== "nofqdn":
245 raise DDNSRequestError(_("No valid FQDN was given."))
246 elif output
== "nohost":
247 raise DDNSRequestError(_("Invalid hostname specified."))
248 elif output
== "notdyn":
249 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
250 elif output
== "invalid":
251 raise DDNSRequestError(_("Invalid IP address has been sent."))
253 # If we got here, some other update error happened.
254 raise DDNSUpdateError
257 class DDNSProviderDtDNS(DDNSProvider
):
259 "handle" : "dtdns.com",
261 "website" : "http://dtdns.com/",
262 "protocols" : ["ipv4",]
265 # Information about the format of the HTTPS request is to be found
266 # http://www.dtdns.com/dtsite/updatespec
267 url
= "https://www.dtdns.com/api/autodns.cfm"
271 "ip" : self
.get_address("ipv4"),
272 "id" : self
.hostname
,
276 # Send update to the server.
277 response
= self
.send_request(self
.url
, data
=data
)
279 # Get the full response message.
280 output
= response
.read()
282 # Remove all leading and trailing whitespace.
283 output
= output
.strip()
285 # Handle success messages.
286 if "now points to" in output
:
289 # Handle error codes.
290 if output
== "No hostname to update was supplied.":
291 raise DDNSRequestError(_("No hostname specified."))
293 elif output
== "The hostname you supplied is not valid.":
294 raise DDNSRequestError(_("Invalid hostname specified."))
296 elif output
== "The password you supplied is not valid.":
297 raise DDNSAuthenticationError
299 elif output
== "Administration has disabled this account.":
300 raise DDNSRequestError(_("Account has been disabled."))
302 elif output
== "Illegal character in IP.":
303 raise DDNSRequestError(_("Invalid IP address has been sent."))
305 elif output
== "Too many failed requests.":
306 raise DDNSRequestError(_("Too many failed requests."))
308 # If we got here, some other update error happened.
309 raise DDNSUpdateError
312 class DDNSProviderDynDNS(DDNSProvider
):
314 "handle" : "dyndns.org",
316 "website" : "http://dyn.com/dns/",
317 "protocols" : ["ipv4",]
320 # Information about the format of the request is to be found
321 # http://http://dyn.com/support/developers/api/perform-update/
322 # http://dyn.com/support/developers/api/return-codes/
323 url
= "https://members.dyndns.org/nic/update"
325 def _prepare_request_data(self
):
327 "hostname" : self
.hostname
,
328 "myip" : self
.get_address("ipv4"),
334 data
= self
._prepare
_request
_data
()
336 # Send update to the server.
337 response
= self
.send_request(self
.url
, data
=data
,
338 username
=self
.username
, password
=self
.password
)
340 # Get the full response message.
341 output
= response
.read()
343 # Handle success messages.
344 if output
.startswith("good") or output
.startswith("nochg"):
347 # Handle error codes.
348 if output
== "badauth":
349 raise DDNSAuthenticationError
350 elif output
== "aduse":
352 elif output
== "notfqdn":
353 raise DDNSRequestError(_("No valid FQDN was given."))
354 elif output
== "nohost":
355 raise DDNSRequestError(_("Specified host does not exist."))
356 elif output
== "911":
357 raise DDNSInternalServerError
358 elif output
== "dnserr":
359 raise DDNSInternalServerError(_("DNS error encountered."))
361 # If we got here, some other update error happened.
362 raise DDNSUpdateError
365 class DDNSProviderDynU(DDNSProviderDynDNS
):
367 "handle" : "dynu.com",
369 "website" : "http://dynu.com/",
370 "protocols" : ["ipv6", "ipv4",]
374 # Detailed information about the request and response codes
375 # are available on the providers webpage.
376 # http://dynu.com/Default.aspx?page=dnsapi
378 url
= "https://api.dynu.com/nic/update"
380 def _prepare_request_data(self
):
381 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
383 # This one supports IPv6
385 "myipv6" : self
.get_address("ipv6"),
391 class DDNSProviderEasyDNS(DDNSProviderDynDNS
):
393 "handle" : "easydns.com",
395 "website" : "http://www.easydns.com/",
396 "protocols" : ["ipv4",]
399 # There is only some basic documentation provided by the vendor,
400 # also searching the web gain very poor results.
401 # http://mediawiki.easydns.com/index.php/Dynamic_DNS
403 url
= "http://api.cp.easydns.com/dyn/tomato.php"
406 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider
):
408 "handle" : "freedns.afraid.org",
409 "name" : "freedns.afraid.org",
410 "website" : "http://freedns.afraid.org/",
411 "protocols" : ["ipv6", "ipv4",]
414 # No information about the request or response could be found on the vendor
415 # page. All used values have been collected by testing.
416 url
= "https://freedns.afraid.org/dynamic/update.php"
420 return self
.get("proto")
423 address
= self
.get_address(self
.proto
)
429 # Add auth token to the update url.
430 url
= "%s?%s" % (self
.url
, self
.token
)
432 # Send update to the server.
433 response
= self
.send_request(url
, data
=data
)
435 if output
.startswith("Updated") or "has not changed" in output
:
438 # Handle error codes.
439 if output
== "ERROR: Unable to locate this record":
440 raise DDNSAuthenticationError
441 elif "is an invalid IP address" in output
:
442 raise DDNSRequestError(_("Invalid IP address has been sent."))
445 class DDNSProviderLightningWireLabs(DDNSProvider
):
447 "handle" : "dns.lightningwirelabs.com",
448 "name" : "Lightning Wire Labs",
449 "website" : "http://dns.lightningwirelabs.com/",
450 "protocols" : ["ipv6", "ipv4",]
453 # Information about the format of the HTTPS request is to be found
454 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
455 url
= "https://dns.lightningwirelabs.com/update"
459 "hostname" : self
.hostname
,
462 # Check if we update an IPv6 address.
463 address6
= self
.get_address("ipv6")
465 data
["address6"] = address6
467 # Check if we update an IPv4 address.
468 address4
= self
.get_address("ipv4")
470 data
["address4"] = address4
472 # Raise an error if none address is given.
473 if not data
.has_key("address6") and not data
.has_key("address4"):
474 raise DDNSConfigurationError
476 # Check if a token has been set.
478 data
["token"] = self
.token
480 # Check for username and password.
481 elif self
.username
and self
.password
:
483 "username" : self
.username
,
484 "password" : self
.password
,
487 # Raise an error if no auth details are given.
489 raise DDNSConfigurationError
491 # Send update to the server.
492 response
= self
.send_request(self
.url
, data
=data
)
494 # Handle success messages.
495 if response
.code
== 200:
498 # Handle error codes.
499 if response
.code
== 403:
500 raise DDNSAuthenticationError
501 elif response
.code
== 400:
502 raise DDNSRequestError
504 # If we got here, some other update error happened.
505 raise DDNSUpdateError
508 class DDNSProviderNamecheap(DDNSProvider
):
510 "handle" : "namecheap.com",
511 "name" : "Namecheap",
512 "website" : "http://namecheap.com",
513 "protocols" : ["ipv4",]
516 # Information about the format of the HTTP request is to be found
517 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
518 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
520 url
= "https://dynamicdns.park-your-domain.com/update"
522 def parse_xml(self
, document
, content
):
523 # Send input to the parser.
524 xmldoc
= xml
.dom
.minidom
.parseString(document
)
526 # Get XML elements by the given content.
527 element
= xmldoc
.getElementsByTagName(content
)
529 # If no element has been found, we directly can return None.
533 # Only get the first child from an element, even there are more than one.
534 firstchild
= element
[0].firstChild
536 # Get the value of the child.
537 value
= firstchild
.nodeValue
543 # Namecheap requires the hostname splitted into a host and domain part.
544 host
, domain
= self
.hostname
.split(".", 1)
547 "ip" : self
.get_address("ipv4"),
548 "password" : self
.password
,
553 # Send update to the server.
554 response
= self
.send_request(self
.url
, data
=data
)
556 # Get the full response message.
557 output
= response
.read()
559 # Handle success messages.
560 if self
.parse_xml(output
, "IP") == self
.get_address("ipv4"):
563 # Handle error codes.
564 errorcode
= self
.parse_xml(output
, "ResponseNumber")
566 if errorcode
== "304156":
567 raise DDNSAuthenticationError
568 elif errorcode
== "316153":
569 raise DDNSRequestError(_("Domain not found."))
570 elif errorcode
== "316154":
571 raise DDNSRequestError(_("Domain not active."))
572 elif errorcode
in ("380098", "380099"):
573 raise DDNSInternalServerError
575 # If we got here, some other update error happened.
576 raise DDNSUpdateError
579 class DDNSProviderNOIP(DDNSProviderDynDNS
):
581 "handle" : "no-ip.com",
583 "website" : "http://www.no-ip.com/",
584 "protocols" : ["ipv4",]
587 # Information about the format of the HTTP request is to be found
588 # here: http://www.no-ip.com/integrate/request and
589 # here: http://www.no-ip.com/integrate/response
591 url
= "http://dynupdate.no-ip.com/nic/update"
593 def _prepare_request_data(self
):
595 "hostname" : self
.hostname
,
596 "address" : self
.get_address("ipv4"),
602 class DDNSProviderOVH(DDNSProviderDynDNS
):
604 "handle" : "ovh.com",
606 "website" : "http://www.ovh.com/",
607 "protocols" : ["ipv4",]
610 # OVH only provides very limited information about how to
611 # update a DynDNS host. They only provide the update url
612 # on the their german subpage.
614 # http://hilfe.ovh.de/DomainDynHost
616 url
= "https://www.ovh.com/nic/update"
618 def _prepare_request_data(self
):
619 data
= DDNSProviderDynDNS
._prepare
_request
_data
(self
)
627 class DDNSProviderRegfish(DDNSProvider
):
629 "handle" : "regfish.com",
630 "name" : "Regfish GmbH",
631 "website" : "http://www.regfish.com/",
632 "protocols" : ["ipv6", "ipv4",]
635 # A full documentation to the providers api can be found here
636 # but is only available in german.
637 # https://www.regfish.de/domains/dyndns/dokumentation
639 url
= "https://dyndns.regfish.de/"
643 "fqdn" : self
.hostname
,
646 # Check if we update an IPv6 address.
647 address6
= self
.get_address("ipv6")
649 data
["ipv6"] = address6
651 # Check if we update an IPv4 address.
652 address4
= self
.get_address("ipv4")
654 data
["ipv4"] = address4
656 # Raise an error if none address is given.
657 if not data
.has_key("ipv6") and not data
.has_key("ipv4"):
658 raise DDNSConfigurationError
660 # Check if a token has been set.
662 data
["token"] = self
.token
664 # Raise an error if no token and no useranem and password
666 elif not self
.username
and not self
.password
:
667 raise DDNSConfigurationError(_("No Auth details specified."))
669 # HTTP Basic Auth is only allowed if no token is used.
671 # Send update to the server.
672 response
= self
.send_request(self
.url
, data
=data
)
674 # Send update to the server.
675 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
678 # Get the full response message.
679 output
= response
.read()
681 # Handle success messages.
682 if "100" in output
or "101" in output
:
685 # Handle error codes.
686 if "401" or "402" in output
:
687 raise DDNSAuthenticationError
688 elif "408" in output
:
689 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
690 elif "409" in output
:
691 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
692 elif "412" in output
:
693 raise DDNSRequestError(_("No valid FQDN was given."))
694 elif "414" in output
:
695 raise DDNSInternalServerError
697 # If we got here, some other update error happened.
698 raise DDNSUpdateError
701 class DDNSProviderSelfhost(DDNSProvider
):
703 "handle" : "selfhost.de",
704 "name" : "Selfhost.de",
705 "website" : "http://www.selfhost.de/",
706 "protocols" : ["ipv4",],
709 url
= "https://carol.selfhost.de/update"
713 "username" : self
.username
,
714 "password" : self
.password
,
718 response
= self
.send_request(self
.url
, data
=data
)
720 match
= re
.search("status=20(0|4)", response
.read())
722 raise DDNSUpdateError
725 class DDNSProviderSPDNS(DDNSProviderDynDNS
):
727 "handle" : "spdns.org",
729 "website" : "http://spdns.org/",
730 "protocols" : ["ipv4",]
733 # Detailed information about request and response codes are provided
734 # by the vendor. They are using almost the same mechanism and status
735 # codes as dyndns.org so we can inherit all those stuff.
737 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
738 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
740 url
= "https://update.spdns.de/nic/update"
743 class DDNSProviderVariomedia(DDNSProviderDynDNS
):
745 "handle" : "variomedia.de",
746 "name" : "Variomedia",
747 "website" : "http://www.variomedia.de/",
748 "protocols" : ["ipv6", "ipv4",]
751 # Detailed information about the request can be found here
752 # https://dyndns.variomedia.de/
754 url
= "https://dyndns.variomedia.de/nic/update"
758 return self
.get("proto")
760 def _prepare_request_data(self
):
762 "hostname" : self
.hostname
,
763 "myip" : self
.get_address(self
.proto
)