Add all-inkl.com as new provider.
[oddments/ddns.git] / src / ddns / providers.py
CommitLineData
f22ab085 1#!/usr/bin/python
3fdcb9d1
MT
2###############################################################################
3# #
4# ddns - A dynamic DNS client for IPFire #
5# Copyright (C) 2012 IPFire development team #
6# #
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. #
11# #
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. #
16# #
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/>. #
19# #
20###############################################################################
f22ab085 21
7399fc5b 22import logging
3b16fdb1 23import urllib2
7399fc5b
MT
24
25from i18n import _
26
f22ab085
MT
27# Import all possible exception types.
28from .errors import *
29
7399fc5b
MT
30logger = logging.getLogger("ddns.providers")
31logger.propagate = 1
32
f22ab085
MT
33class DDNSProvider(object):
34 INFO = {
35 # A short string that uniquely identifies
36 # this provider.
37 "handle" : None,
38
39 # The full name of the provider.
40 "name" : None,
41
42 # A weburl to the homepage of the provider.
43 # (Where to register a new account?)
44 "website" : None,
45
46 # A list of supported protocols.
47 "protocols" : ["ipv6", "ipv4"],
48 }
49
50 DEFAULT_SETTINGS = {}
51
52 def __init__(self, core, **settings):
53 self.core = core
54
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)
59
60 def __repr__(self):
61 return "<DDNS Provider %s (%s)>" % (self.name, self.handle)
62
63 def __cmp__(self, other):
64 return cmp(self.hostname, other.hostname)
65
66 @property
67 def name(self):
68 """
69 Returns the name of the provider.
70 """
71 return self.INFO.get("name")
72
73 @property
74 def website(self):
75 """
76 Returns the website URL of the provider
77 or None if that is not available.
78 """
79 return self.INFO.get("website", None)
80
81 @property
82 def handle(self):
83 """
84 Returns the handle of this provider.
85 """
86 return self.INFO.get("handle")
87
88 def get(self, key, default=None):
89 """
90 Get a setting from the settings dictionary.
91 """
92 return self.settings.get(key, default)
93
94 @property
95 def hostname(self):
96 """
97 Fast access to the hostname.
98 """
99 return self.get("hostname")
100
101 @property
102 def username(self):
103 """
104 Fast access to the username.
105 """
106 return self.get("username")
107
108 @property
109 def password(self):
110 """
111 Fast access to the password.
112 """
113 return self.get("password")
114
7399fc5b
MT
115 @property
116 def protocols(self):
117 return self.INFO.get("protocols")
118
46687828
SS
119 @property
120 def token(self):
121 """
122 Fast access to the token.
123 """
124 return self.get("token")
125
9da3e685
MT
126 def __call__(self, force=False):
127 if force:
128 logger.info(_("Updating %s forced") % self.hostname)
129
7399fc5b 130 # Check if we actually need to update this host.
9da3e685 131 elif self.is_uptodate(self.protocols):
7399fc5b
MT
132 logger.info(_("%s is already up to date") % self.hostname)
133 return
134
135 # Execute the update.
5f402f36
MT
136 self.update()
137
138 def update(self):
f22ab085
MT
139 raise NotImplementedError
140
7399fc5b
MT
141 def is_uptodate(self, protos):
142 """
143 Returns True if this host is already up to date
144 and does not need to change the IP address on the
145 name server.
146 """
147 for proto in protos:
148 addresses = self.core.system.resolve(self.hostname, proto)
149
150 current_address = self.get_address(proto)
151
152 if not current_address in addresses:
153 return False
154
155 return True
156
f22ab085
MT
157 def send_request(self, *args, **kwargs):
158 """
159 Proxy connection to the send request
160 method.
161 """
162 return self.core.system.send_request(*args, **kwargs)
163
164 def get_address(self, proto):
165 """
166 Proxy method to get the current IP address.
167 """
168 return self.core.system.get_address(proto)
169
170
3b16fdb1
SS
171class DDNSProviderAllInkl(DDNSProvider):
172 INFO = {
173 "handle" : "all-inkl.com",
174 "name" : "All-inkl.com",
175 "website" : "http://all-inkl.com/",
176 "protocols" : ["ipv4",]
177 }
178
179 # There are only information provided by the vendor how to
180 # perform an update on a FRITZ Box. Grab requried informations
181 # from the net.
182 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
183
184 url = "http://dyndns.kasserver.com"
185
186 def update(self):
187
188 # There is no additional data required so we directly can
189 # send our request.
190 try:
191 # Send request to the server.
192 response = self.send_request(self.url, username=self.username, password=self.password)
193
194 # Handle 401 HTTP Header (Authentication Error)
195 except urllib2.HTTPError, e:
196 if e.code == 401:
197 raise DDNSAuthenticationError
198
199 raise
200
201 # Get the full response message.
202 output = response.read()
203
204 # Handle success messages.
205 if output.startswith("good") or output.startswith("nochg"):
206 return
207
208 # If we got here, some other update error happened.
209 raise DDNSUpdateError
210
211
f3cf1f70
SS
212class DDNSProviderDHS(DDNSProvider):
213 INFO = {
214 "handle" : "dhs.org",
215 "name" : "DHS International",
216 "website" : "http://dhs.org/",
217 "protocols" : ["ipv4",]
218 }
219
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"
223
5f402f36 224 def update(self):
f3cf1f70
SS
225 data = {
226 "domain" : self.hostname,
227 "ip" : self.get_address("ipv4"),
228 "hostcmd" : "edit",
229 "hostcmdstage" : "2",
230 "type" : "4",
231 }
232
233 # Send update to the server.
175c9b80 234 response = self.send_request(self.url, username=self.username, password=self.password,
f3cf1f70
SS
235 data=data)
236
237 # Handle success messages.
238 if response.code == 200:
239 return
240
241 # Handle error codes.
4caed6ed 242 elif response.code == 401:
f3cf1f70
SS
243 raise DDNSAuthenticationError
244
245 # If we got here, some other update error happened.
246 raise DDNSUpdateError
247
248
39301272
SS
249class DDNSProviderDNSpark(DDNSProvider):
250 INFO = {
251 "handle" : "dnspark.com",
252 "name" : "DNS Park",
253 "website" : "http://dnspark.com/",
254 "protocols" : ["ipv4",]
255 }
256
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"
260
5f402f36 261 def update(self):
39301272
SS
262 data = {
263 "domain" : self.hostname,
264 "ip" : self.get_address("ipv4"),
265 }
266
267 # Send update to the server.
175c9b80 268 response = self.send_request(self.url, username=self.username, password=self.password,
39301272
SS
269 data=data)
270
271 # Get the full response message.
272 output = response.read()
273
274 # Handle success messages.
275 if output.startswith("ok") or output.startswith("nochange"):
276 return
277
278 # Handle error codes.
279 if output == "unauth":
280 raise DDNSAuthenticationError
281 elif output == "abuse":
282 raise DDNSAbuseError
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."))
293
294 # If we got here, some other update error happened.
295 raise DDNSUpdateError
296
43b2cd59
SS
297
298class DDNSProviderDtDNS(DDNSProvider):
299 INFO = {
300 "handle" : "dtdns.com",
301 "name" : "DtDNS",
302 "website" : "http://dtdns.com/",
303 "protocols" : ["ipv4",]
304 }
305
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"
309
43b2cd59
SS
310 def update(self):
311 data = {
312 "ip" : self.get_address("ipv4"),
313 "id" : self.hostname,
314 "pw" : self.password
315 }
316
317 # Send update to the server.
318 response = self.send_request(self.url, data=data)
319
320 # Get the full response message.
321 output = response.read()
322
323 # Remove all leading and trailing whitespace.
324 output = output.strip()
325
326 # Handle success messages.
327 if "now points to" in output:
328 return
329
330 # Handle error codes.
331 if output == "No hostname to update was supplied.":
332 raise DDNSRequestError(_("No hostname specified."))
333
334 elif output == "The hostname you supplied is not valid.":
335 raise DDNSRequestError(_("Invalid hostname specified."))
336
337 elif output == "The password you supplied is not valid.":
338 raise DDNSAuthenticationError
339
340 elif output == "Administration has disabled this account.":
341 raise DDNSRequestError(_("Account has been disabled."))
342
343 elif output == "Illegal character in IP.":
344 raise DDNSRequestError(_("Invalid IP address has been sent."))
345
346 elif output == "Too many failed requests.":
347 raise DDNSRequestError(_("Too many failed requests."))
348
349 # If we got here, some other update error happened.
350 raise DDNSUpdateError
351
352
bfed6701
SS
353class DDNSProviderDynDNS(DDNSProvider):
354 INFO = {
355 "handle" : "dyndns.org",
356 "name" : "Dyn",
357 "website" : "http://dyn.com/dns/",
358 "protocols" : ["ipv4",]
359 }
360
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"
365
88f39629 366 def _prepare_request_data(self):
bfed6701
SS
367 data = {
368 "hostname" : self.hostname,
369 "myip" : self.get_address("ipv4"),
370 }
371
88f39629
SS
372 return data
373
374 def update(self):
375 data = self._prepare_request_data()
376
bfed6701 377 # Send update to the server.
88f39629
SS
378 response = self.send_request(self.url, data=data,
379 username=self.username, password=self.password)
bfed6701
SS
380
381 # Get the full response message.
382 output = response.read()
383
384 # Handle success messages.
385 if output.startswith("good") or output.startswith("nochg"):
386 return
387
388 # Handle error codes.
389 if output == "badauth":
390 raise DDNSAuthenticationError
391 elif output == "aduse":
392 raise DDNSAbuseError
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."))
401
402 # If we got here, some other update error happened.
403 raise DDNSUpdateError
404
405
3a8407fa
SS
406class DDNSProviderDynU(DDNSProviderDynDNS):
407 INFO = {
408 "handle" : "dynu.com",
409 "name" : "Dynu",
410 "website" : "http://dynu.com/",
411 "protocols" : ["ipv6", "ipv4",]
412 }
413
414
415 # Detailed information about the request and response codes
416 # are available on the providers webpage.
417 # http://dynu.com/Default.aspx?page=dnsapi
418
419 url = "https://api.dynu.com/nic/update"
420
421 def _prepare_request_data(self):
54d3efc8
MT
422 data = DDNSProviderDynDNS._prepare_request_data(self)
423
424 # This one supports IPv6
425 data.update({
3a8407fa 426 "myipv6" : self.get_address("ipv6"),
54d3efc8
MT
427 })
428
429 return data
3a8407fa
SS
430
431
ee071271
SS
432class DDNSProviderEasyDNS(DDNSProviderDynDNS):
433 INFO = {
434 "handle" : "easydns.com",
435 "name" : "EasyDNS",
436 "website" : "http://www.easydns.com/",
437 "protocols" : ["ipv4",]
438 }
439
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
443
444 url = "http://api.cp.easydns.com/dyn/tomato.php"
445
446
aa21a4c6
SS
447class DDNSProviderFreeDNSAfraidOrg(DDNSProvider):
448 INFO = {
449 "handle" : "freedns.afraid.org",
450 "name" : "freedns.afraid.org",
451 "website" : "http://freedns.afraid.org/",
452 "protocols" : ["ipv6", "ipv4",]
453 }
454
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"
458
459 @property
460 def proto(self):
461 return self.get("proto")
462
463 def update(self):
464 address = self.get_address(self.proto)
465
466 data = {
467 "address" : address,
468 }
469
470 # Add auth token to the update url.
471 url = "%s?%s" % (self.url, self.token)
472
473 # Send update to the server.
474 response = self.send_request(url, data=data)
475
aa21a4c6
SS
476 if output.startswith("Updated") or "has not changed" in output:
477 return
478
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."))
484
aa21a4c6 485
a08c1b72
SS
486class DDNSProviderLightningWireLabs(DDNSProvider):
487 INFO = {
488 "handle" : "dns.lightningwirelabs.com",
489 "name" : "Lightning Wire Labs",
490 "website" : "http://dns.lightningwirelabs.com/",
491 "protocols" : ["ipv6", "ipv4",]
492 }
493
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"
497
5f402f36 498 def update(self):
a08c1b72
SS
499 data = {
500 "hostname" : self.hostname,
501 }
502
503 # Check if we update an IPv6 address.
504 address6 = self.get_address("ipv6")
505 if address6:
506 data["address6"] = address6
507
508 # Check if we update an IPv4 address.
509 address4 = self.get_address("ipv4")
510 if address4:
511 data["address4"] = address4
512
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
516
517 # Check if a token has been set.
518 if self.token:
519 data["token"] = self.token
520
521 # Check for username and password.
522 elif self.username and self.password:
523 data.update({
524 "username" : self.username,
525 "password" : self.password,
526 })
527
528 # Raise an error if no auth details are given.
529 else:
530 raise DDNSConfigurationError
531
532 # Send update to the server.
cb455540 533 response = self.send_request(self.url, data=data)
a08c1b72
SS
534
535 # Handle success messages.
536 if response.code == 200:
537 return
538
539 # Handle error codes.
2e5ad318 540 if response.code == 403:
a08c1b72 541 raise DDNSAuthenticationError
2e5ad318 542 elif response.code == 400:
a08c1b72
SS
543 raise DDNSRequestError
544
545 # If we got here, some other update error happened.
546 raise DDNSUpdateError
547
548
88f39629 549class DDNSProviderNOIP(DDNSProviderDynDNS):
f22ab085
MT
550 INFO = {
551 "handle" : "no-ip.com",
552 "name" : "No-IP",
553 "website" : "http://www.no-ip.com/",
554 "protocols" : ["ipv4",]
555 }
556
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
560
88f39629 561 url = "http://dynupdate.no-ip.com/nic/update"
2de06f59 562
88f39629 563 def _prepare_request_data(self):
2de06f59
MT
564 data = {
565 "hostname" : self.hostname,
f22ab085
MT
566 "address" : self.get_address("ipv4"),
567 }
568
88f39629 569 return data
f22ab085
MT
570
571
a508bda6
SS
572class DDNSProviderOVH(DDNSProviderDynDNS):
573 INFO = {
574 "handle" : "ovh.com",
575 "name" : "OVH",
576 "website" : "http://www.ovh.com/",
577 "protocols" : ["ipv4",]
578 }
579
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.
583 #
584 # http://hilfe.ovh.de/DomainDynHost
585
586 url = "https://www.ovh.com/nic/update"
587
588 def _prepare_request_data(self):
54d3efc8
MT
589 data = DDNSProviderDynDNS._prepare_request_data(self)
590 data.update({
591 "system" : "dyndns",
592 })
593
594 return data
a508bda6
SS
595
596
ef33455e
SS
597class DDNSProviderRegfish(DDNSProvider):
598 INFO = {
599 "handle" : "regfish.com",
600 "name" : "Regfish GmbH",
601 "website" : "http://www.regfish.com/",
602 "protocols" : ["ipv6", "ipv4",]
603 }
604
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
608
609 url = "https://dyndns.regfish.de/"
610
611 def update(self):
612 data = {
613 "fqdn" : self.hostname,
614 }
615
616 # Check if we update an IPv6 address.
617 address6 = self.get_address("ipv6")
618 if address6:
619 data["ipv6"] = address6
620
621 # Check if we update an IPv4 address.
622 address4 = self.get_address("ipv4")
623 if address4:
624 data["ipv4"] = address4
625
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
629
630 # Check if a token has been set.
631 if self.token:
632 data["token"] = self.token
633
634 # Raise an error if no token and no useranem and password
635 # are given.
636 elif not self.username and not self.password:
637 raise DDNSConfigurationError(_("No Auth details specified."))
638
639 # HTTP Basic Auth is only allowed if no token is used.
640 if self.token:
641 # Send update to the server.
642 response = self.send_request(self.url, data=data)
643 else:
644 # Send update to the server.
645 response = self.send_request(self.url, username=self.username, password=self.password,
646 data=data)
647
648 # Get the full response message.
649 output = response.read()
650
651 # Handle success messages.
652 if "100" in output or "101" in output:
653 return
654
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
666
667 # If we got here, some other update error happened.
668 raise DDNSUpdateError
669
670
f22ab085
MT
671class DDNSProviderSelfhost(DDNSProvider):
672 INFO = {
673 "handle" : "selfhost.de",
674 "name" : "Selfhost.de",
675 "website" : "http://www.selfhost.de/",
676 "protocols" : ["ipv4",],
677 }
678
2de06f59 679 url = "https://carol.selfhost.de/update"
f22ab085 680
5f402f36 681 def update(self):
2de06f59
MT
682 data = {
683 "username" : self.username,
684 "password" : self.password,
685 "textmodi" : "1",
686 }
f22ab085 687
2de06f59 688 response = self.send_request(self.url, data=data)
f22ab085
MT
689
690 match = re.search("status=20(0|4)", response.read())
691 if not match:
692 raise DDNSUpdateError
b09b1545
SS
693
694
695class DDNSProviderSPDNS(DDNSProviderDynDNS):
696 INFO = {
697 "handle" : "spdns.org",
698 "name" : "SPDNS",
699 "website" : "http://spdns.org/",
700 "protocols" : ["ipv4",]
701 }
702
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.
706 #
707 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
708 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
709
710 url = "https://update.spdns.de/nic/update"
4ec90b93
MT
711
712
c8c7ca8f
SS
713class DDNSProviderVariomedia(DDNSProviderDynDNS):
714 INFO = {
715 "handle" : "variomedia.de",
716 "name" : "Variomedia",
717 "website" : "http://www.variomedia.de/",
718 "protocols" : ["ipv6", "ipv4",]
719 }
720
721 # Detailed information about the request can be found here
722 # https://dyndns.variomedia.de/
723
724 url = "https://dyndns.variomedia.de/nic/update"
725
726 @property
727 def proto(self):
728 return self.get("proto")
729
730 def _prepare_request_data(self):
731 data = {
732 "hostname" : self.hostname,
733 "myip" : self.get_address(self.proto)
734 }
54d3efc8
MT
735
736 return data