Add new provider it's DNS (https://www.itsdns.de)
[ddns.git] / src / ddns / providers.py
CommitLineData
f22ab085 1#!/usr/bin/python
3fdcb9d1
MT
2###############################################################################
3# #
4# ddns - A dynamic DNS client for IPFire #
ea32ab26 5# Copyright (C) 2012-2017 IPFire development team #
3fdcb9d1
MT
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
37e24fbf 22import datetime
7399fc5b 23import logging
64d3fad4 24import os
a892c594 25import subprocess
3b16fdb1 26import urllib2
d1cd57eb 27import xml.dom.minidom
7399fc5b
MT
28
29from i18n import _
30
f22ab085
MT
31# Import all possible exception types.
32from .errors import *
33
7399fc5b
MT
34logger = logging.getLogger("ddns.providers")
35logger.propagate = 1
36
adfe6272
MT
37_providers = {}
38
39def get():
40 """
41 Returns a dict with all automatically registered providers.
42 """
43 return _providers.copy()
44
f22ab085 45class DDNSProvider(object):
6a11646e
MT
46 # A short string that uniquely identifies
47 # this provider.
48 handle = None
f22ab085 49
6a11646e
MT
50 # The full name of the provider.
51 name = None
f22ab085 52
6a11646e
MT
53 # A weburl to the homepage of the provider.
54 # (Where to register a new account?)
55 website = None
f22ab085 56
6a11646e
MT
57 # A list of supported protocols.
58 protocols = ("ipv6", "ipv4")
f22ab085
MT
59
60 DEFAULT_SETTINGS = {}
61
37e24fbf
MT
62 # holdoff time - Number of days no update is performed unless
63 # the IP address has changed.
64 holdoff_days = 30
65
112d3fb8
MT
66 # holdoff time for update failures - Number of days no update
67 # is tried after the last one has failed.
68 holdoff_failure_days = 0.5
69
29c8c9c6
MT
70 # True if the provider is able to remove records, too.
71 # Required to remove AAAA records if IPv6 is absent again.
72 can_remove_records = True
73
adfe6272
MT
74 # Automatically register all providers.
75 class __metaclass__(type):
76 def __init__(provider, name, bases, dict):
77 type.__init__(provider, name, bases, dict)
78
79 # The main class from which is inherited is not registered
80 # as a provider.
81 if name == "DDNSProvider":
82 return
83
84 if not all((provider.handle, provider.name, provider.website)):
85 raise DDNSError(_("Provider is not properly configured"))
86
87 assert not _providers.has_key(provider.handle), \
88 "Provider '%s' has already been registered" % provider.handle
89
90 _providers[provider.handle] = provider
91
64d3fad4
MT
92 @staticmethod
93 def supported():
94 """
95 Should be overwritten to check if the system the code is running
96 on has all the required tools to support this provider.
97 """
98 return True
99
f22ab085
MT
100 def __init__(self, core, **settings):
101 self.core = core
102
103 # Copy a set of default settings and
104 # update them by those from the configuration file.
105 self.settings = self.DEFAULT_SETTINGS.copy()
106 self.settings.update(settings)
107
108 def __repr__(self):
109 return "<DDNS Provider %s (%s)>" % (self.name, self.handle)
110
111 def __cmp__(self, other):
112 return cmp(self.hostname, other.hostname)
113
37e24fbf
MT
114 @property
115 def db(self):
116 return self.core.db
117
f22ab085
MT
118 def get(self, key, default=None):
119 """
120 Get a setting from the settings dictionary.
121 """
122 return self.settings.get(key, default)
123
124 @property
125 def hostname(self):
126 """
127 Fast access to the hostname.
128 """
129 return self.get("hostname")
130
131 @property
132 def username(self):
133 """
134 Fast access to the username.
135 """
136 return self.get("username")
137
138 @property
139 def password(self):
140 """
141 Fast access to the password.
142 """
143 return self.get("password")
144
46687828
SS
145 @property
146 def token(self):
147 """
148 Fast access to the token.
149 """
150 return self.get("token")
151
9da3e685
MT
152 def __call__(self, force=False):
153 if force:
c3888f15 154 logger.debug(_("Updating %s forced") % self.hostname)
9da3e685 155
112d3fb8
MT
156 # Do nothing if the last update has failed or no update is required
157 elif self.has_failure or not self.requires_update:
7399fc5b
MT
158 return
159
160 # Execute the update.
37e24fbf
MT
161 try:
162 self.update()
163
2e09c70d 164 # 1) Catch network errors early, because we do not want to log
29a69850
MT
165 # them to the database. They are usually temporary and caused
166 # by the client side, so that we will retry quickly.
2e09c70d
MT
167 # 2) If there is an internet server error (HTTP code 500) on the
168 # provider's site, we will not log a failure and try again
169 # shortly.
170 except (DDNSNetworkError, DDNSInternalServerError):
29a69850
MT
171 raise
172
37e24fbf
MT
173 # In case of any errors, log the failed request and
174 # raise the exception.
175 except DDNSError as e:
176 self.core.db.log_failure(self.hostname, e)
177 raise
5f402f36 178
12b3818b
MT
179 logger.info(_("Dynamic DNS update for %(hostname)s (%(provider)s) successful") % \
180 { "hostname" : self.hostname, "provider" : self.name })
37e24fbf 181 self.core.db.log_success(self.hostname)
12b3818b 182
5f402f36 183 def update(self):
d45139f6
MT
184 for protocol in self.protocols:
185 if self.have_address(protocol):
186 self.update_protocol(protocol)
29c8c9c6 187 elif self.can_remove_records:
d45139f6
MT
188 self.remove_protocol(protocol)
189
190 def update_protocol(self, proto):
f22ab085
MT
191 raise NotImplementedError
192
d45139f6 193 def remove_protocol(self, proto):
29c8c9c6
MT
194 if not self.can_remove_records:
195 raise RuntimeError, "can_remove_records is enabled, but remove_protocol() not implemented"
d45139f6 196
29c8c9c6 197 raise NotImplementedError
d45139f6 198
37e24fbf
MT
199 @property
200 def requires_update(self):
201 # If the IP addresses have changed, an update is required
202 if self.ip_address_changed(self.protocols):
203 logger.debug(_("An update for %(hostname)s (%(provider)s)"
204 " is performed because of an IP address change") % \
205 { "hostname" : self.hostname, "provider" : self.name })
206
207 return True
208
209 # If the holdoff time has expired, an update is required, too
210 if self.holdoff_time_expired():
211 logger.debug(_("An update for %(hostname)s (%(provider)s)"
212 " is performed because the holdoff time has expired") % \
213 { "hostname" : self.hostname, "provider" : self.name })
214
215 return True
216
217 # Otherwise, we don't need to perform an update
218 logger.debug(_("No update required for %(hostname)s (%(provider)s)") % \
219 { "hostname" : self.hostname, "provider" : self.name })
220
221 return False
222
112d3fb8
MT
223 @property
224 def has_failure(self):
225 """
226 Returns True when the last update has failed and no retry
227 should be performed, yet.
228 """
229 last_status = self.db.last_update_status(self.hostname)
230
231 # Return False if the last update has not failed.
232 if not last_status == "failure":
233 return False
234
64018439
MT
235 # If there is no holdoff time, we won't update ever again.
236 if self.holdoff_failure_days is None:
237 logger.warning(_("An update has not been performed because earlier updates failed for %s") \
238 % self.hostname)
239 logger.warning(_("There will be no retries"))
240
241 return True
242
112d3fb8
MT
243 # Determine when the holdoff time ends
244 last_update = self.db.last_update(self.hostname, status=last_status)
245 holdoff_end = last_update + datetime.timedelta(days=self.holdoff_failure_days)
246
247 now = datetime.datetime.utcnow()
248 if now < holdoff_end:
249 failure_message = self.db.last_update_failure_message(self.hostname)
250
251 logger.warning(_("An update has not been performed because earlier updates failed for %s") \
252 % self.hostname)
253
254 if failure_message:
255 logger.warning(_("Last failure message:"))
256
257 for line in failure_message.splitlines():
258 logger.warning(" %s" % line)
259
260 logger.warning(_("Further updates will be withheld until %s") % holdoff_end)
261
262 return True
263
264 return False
265
37e24fbf 266 def ip_address_changed(self, protos):
7399fc5b
MT
267 """
268 Returns True if this host is already up to date
269 and does not need to change the IP address on the
270 name server.
271 """
272 for proto in protos:
273 addresses = self.core.system.resolve(self.hostname, proto)
7399fc5b
MT
274 current_address = self.get_address(proto)
275
29c8c9c6
MT
276 # Handle if the system has not got any IP address from a protocol
277 # (i.e. had full dual-stack connectivity which it has not any more)
278 if current_address is None:
279 # If addresses still exists in the DNS system and if this provider
280 # is able to remove records, we will do that.
281 if addresses and self.can_remove_records:
282 return True
283
284 # Otherwise, we cannot go on...
38d81db4
MT
285 continue
286
7399fc5b 287 if not current_address in addresses:
37e24fbf
MT
288 return True
289
290 return False
7399fc5b 291
37e24fbf
MT
292 def holdoff_time_expired(self):
293 """
294 Returns true if the holdoff time has expired
295 and the host requires an update
296 """
297 # If no holdoff days is defined, we cannot go on
298 if not self.holdoff_days:
299 return False
300
301 # Get the timestamp of the last successfull update
112d3fb8 302 last_update = self.db.last_update(self.hostname, status="success")
37e24fbf
MT
303
304 # If no timestamp has been recorded, no update has been
305 # performed. An update should be performed now.
306 if not last_update:
307 return True
7399fc5b 308
37e24fbf
MT
309 # Determine when the holdoff time ends
310 holdoff_end = last_update + datetime.timedelta(days=self.holdoff_days)
311
312 now = datetime.datetime.utcnow()
313
314 if now >= holdoff_end:
315 logger.debug("The holdoff time has expired for %s" % self.hostname)
316 return True
317 else:
318 logger.debug("Updates for %s are held off until %s" % \
319 (self.hostname, holdoff_end))
320 return False
7399fc5b 321
f22ab085
MT
322 def send_request(self, *args, **kwargs):
323 """
324 Proxy connection to the send request
325 method.
326 """
327 return self.core.system.send_request(*args, **kwargs)
328
e3c70807 329 def get_address(self, proto, default=None):
f22ab085
MT
330 """
331 Proxy method to get the current IP address.
332 """
e3c70807 333 return self.core.system.get_address(proto) or default
f22ab085 334
d45139f6
MT
335 def have_address(self, proto):
336 """
337 Returns True if an IP address for the given protocol
338 is known and usable.
339 """
340 address = self.get_address(proto)
341
342 if address:
343 return True
344
345 return False
346
f22ab085 347
5d4bec40
MT
348class DDNSProtocolDynDNS2(object):
349 """
350 This is an abstract class that implements the DynDNS updater
351 protocol version 2. As this is a popular way to update dynamic
352 DNS records, this class is supposed make the provider classes
353 shorter and simpler.
354 """
355
356 # Information about the format of the request is to be found
486c1b9d 357 # http://dyn.com/support/developers/api/perform-update/
5d4bec40
MT
358 # http://dyn.com/support/developers/api/return-codes/
359
29c8c9c6
MT
360 # The DynDNS protocol version 2 does not allow to remove records
361 can_remove_records = False
362
d45139f6 363 def prepare_request_data(self, proto):
5d4bec40
MT
364 data = {
365 "hostname" : self.hostname,
d45139f6 366 "myip" : self.get_address(proto),
5d4bec40
MT
367 }
368
369 return data
370
d45139f6
MT
371 def update_protocol(self, proto):
372 data = self.prepare_request_data(proto)
373
374 return self.send_request(data)
5d4bec40 375
d45139f6 376 def send_request(self, data):
5d4bec40 377 # Send update to the server.
d45139f6 378 response = DDNSProvider.send_request(self, self.url, data=data,
5d4bec40
MT
379 username=self.username, password=self.password)
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
af97e369 391 elif output == "abuse":
5d4bec40
MT
392 raise DDNSAbuseError
393 elif output == "notfqdn":
fb701db9 394 raise DDNSRequestError(_("No valid FQDN was given"))
5d4bec40 395 elif output == "nohost":
fb701db9 396 raise DDNSRequestError(_("Specified host does not exist"))
5d4bec40
MT
397 elif output == "911":
398 raise DDNSInternalServerError
399 elif output == "dnserr":
fb701db9 400 raise DDNSInternalServerError(_("DNS error encountered"))
6ddfd5c7
SS
401 elif output == "badagent":
402 raise DDNSBlockedError
9db9ea25
MP
403 elif output == "badip":
404 raise DDNSBlockedError
5d4bec40
MT
405
406 # If we got here, some other update error happened.
407 raise DDNSUpdateError(_("Server response: %s") % output)
408
409
78c9780b
SS
410class DDNSResponseParserXML(object):
411 """
412 This class provides a parser for XML responses which
413 will be sent by various providers. This class uses the python
414 shipped XML minidom module to walk through the XML tree and return
415 a requested element.
416 """
417
418 def get_xml_tag_value(self, document, content):
419 # Send input to the parser.
420 xmldoc = xml.dom.minidom.parseString(document)
421
422 # Get XML elements by the given content.
423 element = xmldoc.getElementsByTagName(content)
424
425 # If no element has been found, we directly can return None.
426 if not element:
427 return None
428
429 # Only get the first child from an element, even there are more than one.
430 firstchild = element[0].firstChild
431
432 # Get the value of the child.
433 value = firstchild.nodeValue
434
435 # Return the value.
436 return value
437
438
3b16fdb1 439class DDNSProviderAllInkl(DDNSProvider):
6a11646e
MT
440 handle = "all-inkl.com"
441 name = "All-inkl.com"
442 website = "http://all-inkl.com/"
443 protocols = ("ipv4",)
3b16fdb1
SS
444
445 # There are only information provided by the vendor how to
446 # perform an update on a FRITZ Box. Grab requried informations
447 # from the net.
448 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
449
450 url = "http://dyndns.kasserver.com"
29c8c9c6 451 can_remove_records = False
3b16fdb1
SS
452
453 def update(self):
3b16fdb1
SS
454 # There is no additional data required so we directly can
455 # send our request.
536e87d1 456 response = self.send_request(self.url, username=self.username, password=self.password)
3b16fdb1
SS
457
458 # Get the full response message.
459 output = response.read()
460
461 # Handle success messages.
462 if output.startswith("good") or output.startswith("nochg"):
463 return
464
465 # If we got here, some other update error happened.
466 raise DDNSUpdateError
467
468
a892c594
MT
469class DDNSProviderBindNsupdate(DDNSProvider):
470 handle = "nsupdate"
471 name = "BIND nsupdate utility"
472 website = "http://en.wikipedia.org/wiki/Nsupdate"
473
474 DEFAULT_TTL = 60
475
64d3fad4
MT
476 @staticmethod
477 def supported():
478 # Search if the nsupdate utility is available
479 paths = os.environ.get("PATH")
480
481 for path in paths.split(":"):
482 executable = os.path.join(path, "nsupdate")
483
484 if os.path.exists(executable):
485 return True
486
487 return False
488
a892c594
MT
489 def update(self):
490 scriptlet = self.__make_scriptlet()
491
492 # -v enables TCP hence we transfer keys and other data that may
493 # exceed the size of one packet.
494 # -t sets the timeout
495 command = ["nsupdate", "-v", "-t", "60"]
496
497 p = subprocess.Popen(command, shell=True,
498 stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
499 )
500 stdout, stderr = p.communicate(scriptlet)
501
502 if p.returncode == 0:
503 return
504
505 raise DDNSError("nsupdate terminated with error code: %s\n %s" % (p.returncode, stderr))
506
507 def __make_scriptlet(self):
508 scriptlet = []
509
510 # Set a different server the update is sent to.
511 server = self.get("server", None)
512 if server:
513 scriptlet.append("server %s" % server)
514
97998aac
MW
515 # Set the DNS zone the host should be added to.
516 zone = self.get("zone", None)
517 if zone:
518 scriptlet.append("zone %s" % zone)
519
a892c594
MT
520 key = self.get("key", None)
521 if key:
522 secret = self.get("secret")
523
524 scriptlet.append("key %s %s" % (key, secret))
525
526 ttl = self.get("ttl", self.DEFAULT_TTL)
527
528 # Perform an update for each supported protocol.
529 for rrtype, proto in (("AAAA", "ipv6"), ("A", "ipv4")):
530 address = self.get_address(proto)
531 if not address:
532 continue
533
534 scriptlet.append("update delete %s. %s" % (self.hostname, rrtype))
535 scriptlet.append("update add %s. %s %s %s" % \
536 (self.hostname, ttl, rrtype, address))
537
538 # Send the actions to the server.
539 scriptlet.append("send")
540 scriptlet.append("quit")
541
542 logger.debug(_("Scriptlet:"))
543 for line in scriptlet:
544 # Masquerade the line with the secret key.
545 if line.startswith("key"):
546 line = "key **** ****"
547
548 logger.debug(" %s" % line)
549
550 return "\n".join(scriptlet)
551
552
78046ffe
SS
553class DDNSProviderChangeIP(DDNSProvider):
554 handle = "changeip.com"
555 name = "ChangeIP.com"
556 website = "https://changeip.com"
557 protocols = ("ipv4",)
558
559 # Detailed information about the update api can be found here.
560 # http://www.changeip.com/accounts/knowledgebase.php?action=displayarticle&id=34
561
562 url = "https://nic.changeip.com/nic/update"
563 can_remove_records = False
564
565 def update_protocol(self, proto):
566 data = {
567 "hostname" : self.hostname,
568 "myip" : self.get_address(proto),
569 }
570
571 # Send update to the server.
572 try:
573 response = self.send_request(self.url, username=self.username, password=self.password,
574 data=data)
575
576 # Handle error codes.
577 except urllib2.HTTPError, e:
578 if e.code == 422:
579 raise DDNSRequestError(_("Domain not found."))
580
581 raise
582
583 # Handle success message.
584 if response.code == 200:
585 return
586
587 # If we got here, some other update error happened.
588 raise DDNSUpdateError(_("Server response: %s") % output)
589
590
d1a5be9e
JD
591class DDNSProviderDesecIO(DDNSProtocolDynDNS2, DDNSProvider):
592 handle = "desec.io"
593 name = "desec.io"
594 website = "https://www.desec.io"
595 protocols = ("ipv6", "ipv4",)
596
597 # ipv4 / ipv6 records are automatically removed when the update
598 # request originates from the respectively other protocol and no
599 # address is explicitly provided for the unused protocol.
600
601 url = "https://update.dedyn.io"
602
603 # desec.io sends the IPv6 and IPv4 address in one request
604
605 def update(self):
606 data = DDNSProtocolDynDNS2.prepare_request_data(self, "ipv4")
607
608 # This one supports IPv6
609 myipv6 = self.get_address("ipv6")
610
611 # Add update information if we have an IPv6 address.
612 if myipv6:
613 data["myipv6"] = myipv6
614
615 self.send_request(data)
616
617
f1332a16
SS
618class DDNSProviderDDNSS(DDNSProvider):
619 handle = "ddnss.de"
620 name = "DDNSS"
621 website = "http://www.ddnss.de"
622 protocols = ("ipv4",)
623
624 # Detailed information about how to send the update request and possible response
625 # codes can be obtained from here.
626 # http://www.ddnss.de/info.php
627 # http://www.megacomputing.de/2014/08/dyndns-service-response-time/#more-919
628
629 url = "http://www.ddnss.de/upd.php"
630 can_remove_records = False
631
632 def update_protocol(self, proto):
633 data = {
634 "ip" : self.get_address(proto),
635 "host" : self.hostname,
636 }
637
638 # Check if a token has been set.
639 if self.token:
640 data["key"] = self.token
641
642 # Check if username and hostname are given.
643 elif self.username and self.password:
644 data.update({
645 "user" : self.username,
646 "pwd" : self.password,
647 })
648
649 # Raise an error if no auth details are given.
650 else:
651 raise DDNSConfigurationError
652
653 # Send update to the server.
654 response = self.send_request(self.url, data=data)
655
656 # This provider sends the response code as part of the header.
657 header = response.info()
658
659 # Get status information from the header.
660 output = header.getheader('ddnss-response')
661
662 # Handle success messages.
663 if output == "good" or output == "nochg":
664 return
665
666 # Handle error codes.
667 if output == "badauth":
668 raise DDNSAuthenticationError
669 elif output == "notfqdn":
fb701db9 670 raise DDNSRequestError(_("No valid FQDN was given"))
f1332a16 671 elif output == "nohost":
fb701db9 672 raise DDNSRequestError(_("Specified host does not exist"))
f1332a16
SS
673 elif output == "911":
674 raise DDNSInternalServerError
675 elif output == "dnserr":
fb701db9 676 raise DDNSInternalServerError(_("DNS error encountered"))
f1332a16 677 elif output == "disabled":
fb701db9 678 raise DDNSRequestError(_("Account disabled or locked"))
f1332a16
SS
679
680 # If we got here, some other update error happened.
681 raise DDNSUpdateError
682
683
f3cf1f70 684class DDNSProviderDHS(DDNSProvider):
6a11646e
MT
685 handle = "dhs.org"
686 name = "DHS International"
687 website = "http://dhs.org/"
688 protocols = ("ipv4",)
f3cf1f70
SS
689
690 # No information about the used update api provided on webpage,
691 # grabed from source code of ez-ipudate.
b2b05ef3 692
f3cf1f70 693 url = "http://members.dhs.org/nic/hosts"
29c8c9c6 694 can_remove_records = False
f3cf1f70 695
d45139f6 696 def update_protocol(self, proto):
f3cf1f70
SS
697 data = {
698 "domain" : self.hostname,
d45139f6 699 "ip" : self.get_address(proto),
f3cf1f70
SS
700 "hostcmd" : "edit",
701 "hostcmdstage" : "2",
702 "type" : "4",
703 }
704
705 # Send update to the server.
175c9b80 706 response = self.send_request(self.url, username=self.username, password=self.password,
f3cf1f70
SS
707 data=data)
708
709 # Handle success messages.
710 if response.code == 200:
711 return
712
f3cf1f70
SS
713 # If we got here, some other update error happened.
714 raise DDNSUpdateError
715
716
39301272 717class DDNSProviderDNSpark(DDNSProvider):
6a11646e
MT
718 handle = "dnspark.com"
719 name = "DNS Park"
720 website = "http://dnspark.com/"
721 protocols = ("ipv4",)
39301272
SS
722
723 # Informations to the used api can be found here:
724 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
b2b05ef3 725
39301272 726 url = "https://control.dnspark.com/api/dynamic/update.php"
29c8c9c6 727 can_remove_records = False
39301272 728
d45139f6 729 def update_protocol(self, proto):
39301272
SS
730 data = {
731 "domain" : self.hostname,
d45139f6 732 "ip" : self.get_address(proto),
39301272
SS
733 }
734
735 # Send update to the server.
175c9b80 736 response = self.send_request(self.url, username=self.username, password=self.password,
39301272
SS
737 data=data)
738
739 # Get the full response message.
740 output = response.read()
741
742 # Handle success messages.
743 if output.startswith("ok") or output.startswith("nochange"):
744 return
745
746 # Handle error codes.
747 if output == "unauth":
748 raise DDNSAuthenticationError
749 elif output == "abuse":
750 raise DDNSAbuseError
751 elif output == "blocked":
752 raise DDNSBlockedError
753 elif output == "nofqdn":
fb701db9 754 raise DDNSRequestError(_("No valid FQDN was given"))
39301272 755 elif output == "nohost":
fb701db9 756 raise DDNSRequestError(_("Invalid hostname specified"))
39301272 757 elif output == "notdyn":
fb701db9 758 raise DDNSRequestError(_("Hostname not marked as a dynamic host"))
39301272 759 elif output == "invalid":
fb701db9 760 raise DDNSRequestError(_("Invalid IP address has been sent"))
39301272
SS
761
762 # If we got here, some other update error happened.
763 raise DDNSUpdateError
764
43b2cd59
SS
765
766class DDNSProviderDtDNS(DDNSProvider):
6a11646e
MT
767 handle = "dtdns.com"
768 name = "DtDNS"
769 website = "http://dtdns.com/"
770 protocols = ("ipv4",)
43b2cd59
SS
771
772 # Information about the format of the HTTPS request is to be found
773 # http://www.dtdns.com/dtsite/updatespec
b2b05ef3 774
43b2cd59 775 url = "https://www.dtdns.com/api/autodns.cfm"
29c8c9c6 776 can_remove_records = False
43b2cd59 777
d45139f6 778 def update_protocol(self, proto):
43b2cd59 779 data = {
d45139f6 780 "ip" : self.get_address(proto),
43b2cd59
SS
781 "id" : self.hostname,
782 "pw" : self.password
783 }
784
785 # Send update to the server.
786 response = self.send_request(self.url, data=data)
787
788 # Get the full response message.
789 output = response.read()
790
791 # Remove all leading and trailing whitespace.
792 output = output.strip()
793
794 # Handle success messages.
795 if "now points to" in output:
796 return
797
798 # Handle error codes.
799 if output == "No hostname to update was supplied.":
fb701db9 800 raise DDNSRequestError(_("No hostname specified"))
43b2cd59
SS
801
802 elif output == "The hostname you supplied is not valid.":
fb701db9 803 raise DDNSRequestError(_("Invalid hostname specified"))
43b2cd59
SS
804
805 elif output == "The password you supplied is not valid.":
806 raise DDNSAuthenticationError
807
808 elif output == "Administration has disabled this account.":
fb701db9 809 raise DDNSRequestError(_("Account has been disabled"))
43b2cd59
SS
810
811 elif output == "Illegal character in IP.":
fb701db9 812 raise DDNSRequestError(_("Invalid IP address has been sent"))
43b2cd59
SS
813
814 elif output == "Too many failed requests.":
fb701db9 815 raise DDNSRequestError(_("Too many failed requests"))
43b2cd59
SS
816
817 # If we got here, some other update error happened.
818 raise DDNSUpdateError
819
820
fc91be92
MT
821class DDNSProviderDuckDNS(DDNSProtocolDynDNS2, DDNSProvider):
822 handle = "duckdns.org"
823 name = "Duck DNS"
824 website = "http://www.duckdns.org/"
825 protocols = ("ipv4",)
826
827 # Information about the format of the request is to be found
828 # https://www.duckdns.org/install.jsp
829
830 url = "https://www.duckdns.org/nic/update"
831
832
9db9ea25
MP
833class DDNSProviderDyFi(DDNSProtocolDynDNS2, DDNSProvider):
834 handle = "dy.fi"
835 name = "dy.fi"
836 website = "https://www.dy.fi/"
837 protocols = ("ipv4",)
838
839 # Information about the format of the request is to be found
840 # https://www.dy.fi/page/clients?lang=en
841 # https://www.dy.fi/page/specification?lang=en
842
843 url = "http://www.dy.fi/nic/update"
844
845 # Please only send automatic updates when your IP address changes,
846 # or once per 5 to 6 days to refresh the address mapping (they will
847 # expire if not refreshed within 7 days).
848 holdoff_days = 6
849
850
5d4bec40 851class DDNSProviderDynDNS(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
852 handle = "dyndns.org"
853 name = "Dyn"
854 website = "http://dyn.com/dns/"
855 protocols = ("ipv4",)
bfed6701
SS
856
857 # Information about the format of the request is to be found
858 # http://http://dyn.com/support/developers/api/perform-update/
859 # http://dyn.com/support/developers/api/return-codes/
b2b05ef3 860
bfed6701
SS
861 url = "https://members.dyndns.org/nic/update"
862
bfed6701 863
ea32ab26
DW
864class DDNSProviderDomainOffensive(DDNSProtocolDynDNS2, DDNSProvider):
865 handle = "do.de"
866 name = "Domain-Offensive"
867 website = "https://www.do.de/"
868 protocols = ("ipv6", "ipv4")
869
870 # Detailed information about the request and response codes
871 # are available on the providers webpage.
872 # https://www.do.de/wiki/FlexDNS_-_Entwickler
873
874 url = "https://ddns.do.de/"
875
876
5d4bec40 877class DDNSProviderDynU(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
878 handle = "dynu.com"
879 name = "Dynu"
880 website = "http://dynu.com/"
881 protocols = ("ipv6", "ipv4",)
3a8407fa
SS
882
883 # Detailed information about the request and response codes
884 # are available on the providers webpage.
885 # http://dynu.com/Default.aspx?page=dnsapi
886
887 url = "https://api.dynu.com/nic/update"
888
d45139f6
MT
889 # DynU sends the IPv6 and IPv4 address in one request
890
891 def update(self):
892 data = DDNSProtocolDynDNS2.prepare_request_data(self, "ipv4")
54d3efc8
MT
893
894 # This one supports IPv6
cdc078dc
SS
895 myipv6 = self.get_address("ipv6")
896
897 # Add update information if we have an IPv6 address.
898 if myipv6:
899 data["myipv6"] = myipv6
54d3efc8 900
304026a3 901 self.send_request(data)
3a8407fa
SS
902
903
5420343a 904class DDNSProviderEasyDNS(DDNSProvider):
5d4bec40
MT
905 handle = "easydns.com"
906 name = "EasyDNS"
907 website = "http://www.easydns.com/"
908 protocols = ("ipv4",)
ee071271 909
5420343a
SS
910 # Detailed information about the request and response codes
911 # (API 1.3) are available on the providers webpage.
912 # https://fusion.easydns.com/index.php?/Knowledgebase/Article/View/102/7/dynamic-dns
ee071271
SS
913
914 url = "http://api.cp.easydns.com/dyn/tomato.php"
915
5420343a
SS
916 def update_protocol(self, proto):
917 data = {
918 "myip" : self.get_address(proto, "-"),
919 "hostname" : self.hostname,
920 }
921
922 # Send update to the server.
923 response = self.send_request(self.url, data=data,
924 username=self.username, password=self.password)
925
926 # Get the full response message.
927 output = response.read()
928
929 # Remove all leading and trailing whitespace.
930 output = output.strip()
931
932 # Handle success messages.
933 if output.startswith("NOERROR"):
934 return
935
936 # Handle error codes.
937 if output.startswith("NOACCESS"):
938 raise DDNSAuthenticationError
939
940 elif output.startswith("NOSERVICE"):
fb701db9 941 raise DDNSRequestError(_("Dynamic DNS is not turned on for this domain"))
5420343a
SS
942
943 elif output.startswith("ILLEGAL INPUT"):
fb701db9 944 raise DDNSRequestError(_("Invalid data has been sent"))
5420343a
SS
945
946 elif output.startswith("TOOSOON"):
fb701db9 947 raise DDNSRequestError(_("Too frequent update requests have been sent"))
5420343a
SS
948
949 # If we got here, some other update error happened.
950 raise DDNSUpdateError
951
ee071271 952
90fe8843
CE
953class DDNSProviderDomopoli(DDNSProtocolDynDNS2, DDNSProvider):
954 handle = "domopoli.de"
955 name = "domopoli.de"
956 website = "http://domopoli.de/"
957 protocols = ("ipv4",)
958
959 # https://www.domopoli.de/?page=howto#DynDns_start
960
961 url = "http://dyndns.domopoli.de/nic/update"
962
963
a197d1a6
SS
964class DDNSProviderDynsNet(DDNSProvider):
965 handle = "dyns.net"
966 name = "DyNS"
967 website = "http://www.dyns.net/"
968 protocols = ("ipv4",)
29c8c9c6 969 can_remove_records = False
a197d1a6
SS
970
971 # There is very detailed informatio about how to send the update request and
972 # the possible response codes. (Currently we are using the v1.1 proto)
973 # http://www.dyns.net/documentation/technical/protocol/
974
975 url = "http://www.dyns.net/postscript011.php"
976
d45139f6 977 def update_protocol(self, proto):
a197d1a6 978 data = {
d45139f6 979 "ip" : self.get_address(proto),
a197d1a6
SS
980 "host" : self.hostname,
981 "username" : self.username,
982 "password" : self.password,
983 }
984
985 # Send update to the server.
986 response = self.send_request(self.url, data=data)
987
988 # Get the full response message.
989 output = response.read()
990
991 # Handle success messages.
992 if output.startswith("200"):
993 return
994
995 # Handle error codes.
996 if output.startswith("400"):
fb701db9 997 raise DDNSRequestError(_("Malformed request has been sent"))
a197d1a6
SS
998 elif output.startswith("401"):
999 raise DDNSAuthenticationError
1000 elif output.startswith("402"):
fb701db9 1001 raise DDNSRequestError(_("Too frequent update requests have been sent"))
a197d1a6
SS
1002 elif output.startswith("403"):
1003 raise DDNSInternalServerError
1004
1005 # If we got here, some other update error happened.
1006 raise DDNSUpdateError(_("Server response: %s") % output)
1007
1008
35216523
SS
1009class DDNSProviderEnomCom(DDNSResponseParserXML, DDNSProvider):
1010 handle = "enom.com"
1011 name = "eNom Inc."
1012 website = "http://www.enom.com/"
d45139f6 1013 protocols = ("ipv4",)
35216523
SS
1014
1015 # There are very detailed information about how to send an update request and
1016 # the respone codes.
1017 # http://www.enom.com/APICommandCatalog/
1018
1019 url = "https://dynamic.name-services.com/interface.asp"
29c8c9c6 1020 can_remove_records = False
35216523 1021
d45139f6 1022 def update_protocol(self, proto):
35216523
SS
1023 data = {
1024 "command" : "setdnshost",
1025 "responsetype" : "xml",
d45139f6 1026 "address" : self.get_address(proto),
35216523
SS
1027 "domainpassword" : self.password,
1028 "zone" : self.hostname
1029 }
1030
1031 # Send update to the server.
1032 response = self.send_request(self.url, data=data)
1033
1034 # Get the full response message.
1035 output = response.read()
1036
1037 # Handle success messages.
1038 if self.get_xml_tag_value(output, "ErrCount") == "0":
1039 return
1040
1041 # Handle error codes.
1042 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
1043
1044 if errorcode == "304155":
1045 raise DDNSAuthenticationError
1046 elif errorcode == "304153":
fb701db9 1047 raise DDNSRequestError(_("Domain not found"))
35216523
SS
1048
1049 # If we got here, some other update error happened.
1050 raise DDNSUpdateError
1051
1052
ab4e352e
SS
1053class DDNSProviderEntryDNS(DDNSProvider):
1054 handle = "entrydns.net"
1055 name = "EntryDNS"
1056 website = "http://entrydns.net/"
1057 protocols = ("ipv4",)
1058
1059 # Some very tiny details about their so called "Simple API" can be found
1060 # here: https://entrydns.net/help
1061 url = "https://entrydns.net/records/modify"
29c8c9c6 1062 can_remove_records = False
ab4e352e 1063
d45139f6 1064 def update_protocol(self, proto):
ab4e352e 1065 data = {
d45139f6 1066 "ip" : self.get_address(proto),
ab4e352e
SS
1067 }
1068
1069 # Add auth token to the update url.
1070 url = "%s/%s" % (self.url, self.token)
1071
1072 # Send update to the server.
1073 try:
babc5e6d 1074 response = self.send_request(url, data=data)
ab4e352e
SS
1075
1076 # Handle error codes
1077 except urllib2.HTTPError, e:
1078 if e.code == 404:
1079 raise DDNSAuthenticationError
1080
1081 elif e.code == 422:
1082 raise DDNSRequestError(_("An invalid IP address was submitted"))
1083
1084 raise
1085
1086 # Handle success messages.
1087 if response.code == 200:
1088 return
1089
1090 # If we got here, some other update error happened.
1091 raise DDNSUpdateError
1092
1093
aa21a4c6 1094class DDNSProviderFreeDNSAfraidOrg(DDNSProvider):
6a11646e
MT
1095 handle = "freedns.afraid.org"
1096 name = "freedns.afraid.org"
1097 website = "http://freedns.afraid.org/"
aa21a4c6
SS
1098
1099 # No information about the request or response could be found on the vendor
1100 # page. All used values have been collected by testing.
1101 url = "https://freedns.afraid.org/dynamic/update.php"
29c8c9c6 1102 can_remove_records = False
aa21a4c6 1103
d45139f6 1104 def update_protocol(self, proto):
aa21a4c6 1105 data = {
d45139f6 1106 "address" : self.get_address(proto),
aa21a4c6
SS
1107 }
1108
1109 # Add auth token to the update url.
1110 url = "%s?%s" % (self.url, self.token)
1111
1112 # Send update to the server.
1113 response = self.send_request(url, data=data)
1114
a204b107
SS
1115 # Get the full response message.
1116 output = response.read()
1117
1118 # Handle success messages.
aa21a4c6
SS
1119 if output.startswith("Updated") or "has not changed" in output:
1120 return
1121
1122 # Handle error codes.
1123 if output == "ERROR: Unable to locate this record":
1124 raise DDNSAuthenticationError
1125 elif "is an invalid IP address" in output:
fb701db9 1126 raise DDNSRequestError(_("Invalid IP address has been sent"))
aa21a4c6 1127
3b524cf2
SS
1128 # If we got here, some other update error happened.
1129 raise DDNSUpdateError
1130
aa21a4c6 1131
327095f0
JS
1132class DDNSProviderItsdns(DDNSProtocolDynDNS2, DDNSProvider):
1133 handle = "itsdns.de"
1134 name = "it's DNS"
1135 website = "http://www.itsdns.de/"
1136 protocols = ("ipv6", "ipv4")
1137
1138 # Information about the format of the HTTP request is to be found
1139 # here: https://www.itsdns.de/dynupdatehelp.htm
1140
1141 url = "https://www.itsdns.de/update.php"
1142
1143
09b07b23
LAH
1144class DDNSProviderJoker(DDNSProtocolDynDNS2, DDNSProvider):
1145 handle = "joker.com"
1146 name = "Joker.com Dynamic DNS"
1147 website = "https://joker.com/"
1148 protocols = ("ipv4",)
1149
1150 # Information about the request can be found here:
1151 # https://joker.com/faq/content/11/427/en/what-is-dynamic-dns-dyndns.html
1152 # Using DynDNS V2 protocol over HTTPS here
1153
1154 url = "https://svc.joker.com/nic/update"
1155
1156
c510004d
SS
1157class DDNSProviderGoogle(DDNSProtocolDynDNS2, DDNSProvider):
1158 handle = "domains.google.com"
1159 name = "Google Domains"
1160 website = "https://domains.google.com/"
1161 protocols = ("ipv4",)
1162
1163 # Information about the format of the HTTP request is to be found
1164 # here: https://support.google.com/domains/answer/6147083?hl=en
1165
1166 url = "https://domains.google.com/nic/update"
1167
1168
a08c1b72 1169class DDNSProviderLightningWireLabs(DDNSProvider):
6a11646e 1170 handle = "dns.lightningwirelabs.com"
fb115fdc 1171 name = "Lightning Wire Labs DNS Service"
6a11646e 1172 website = "http://dns.lightningwirelabs.com/"
a08c1b72
SS
1173
1174 # Information about the format of the HTTPS request is to be found
1175 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
b2b05ef3 1176
a08c1b72
SS
1177 url = "https://dns.lightningwirelabs.com/update"
1178
5f402f36 1179 def update(self):
a08c1b72
SS
1180 data = {
1181 "hostname" : self.hostname,
e3c70807
MT
1182 "address6" : self.get_address("ipv6", "-"),
1183 "address4" : self.get_address("ipv4", "-"),
a08c1b72
SS
1184 }
1185
a08c1b72
SS
1186 # Check if a token has been set.
1187 if self.token:
1188 data["token"] = self.token
1189
1190 # Check for username and password.
1191 elif self.username and self.password:
1192 data.update({
1193 "username" : self.username,
1194 "password" : self.password,
1195 })
1196
1197 # Raise an error if no auth details are given.
1198 else:
1199 raise DDNSConfigurationError
1200
1201 # Send update to the server.
cb455540 1202 response = self.send_request(self.url, data=data)
a08c1b72
SS
1203
1204 # Handle success messages.
1205 if response.code == 200:
1206 return
1207
a08c1b72
SS
1208 # If we got here, some other update error happened.
1209 raise DDNSUpdateError
1210
1211
9d3cfba8
SS
1212class DDNSProviderLoopia(DDNSProtocolDynDNS2, DDNSProvider):
1213 handle = "loopia.se"
1214 name = "Loopia AB"
1215 website = "https://www.loopia.com"
1216 protocols = ("ipv4",)
1217
1218 # Information about the format of the HTTP request is to be found
1219 # here: https://support.loopia.com/wiki/About_the_DynDNS_support
1220
1221 url = "https://dns.loopia.se/XDynDNSServer/XDynDNS.php"
1222
1223
446e42af
GH
1224class DDNSProviderMyOnlinePortal(DDNSProtocolDynDNS2, DDNSProvider):
1225 handle = "myonlineportal.net"
1226 name = "myonlineportal.net"
1227 website = "https:/myonlineportal.net/"
1228
1229 # Information about the request and response can be obtained here:
1230 # https://myonlineportal.net/howto_dyndns
1231
1232 url = "https://myonlineportal.net/updateddns"
1233
1234 def prepare_request_data(self, proto):
1235 data = {
1236 "hostname" : self.hostname,
1237 "ip" : self.get_address(proto),
1238 }
1239
1240 return data
1241
1242
78c9780b 1243class DDNSProviderNamecheap(DDNSResponseParserXML, DDNSProvider):
6a11646e
MT
1244 handle = "namecheap.com"
1245 name = "Namecheap"
1246 website = "http://namecheap.com"
1247 protocols = ("ipv4",)
d1cd57eb
SS
1248
1249 # Information about the format of the HTTP request is to be found
1250 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
1251 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
1252
1253 url = "https://dynamicdns.park-your-domain.com/update"
29c8c9c6 1254 can_remove_records = False
d1cd57eb 1255
d45139f6 1256 def update_protocol(self, proto):
d1cd57eb
SS
1257 # Namecheap requires the hostname splitted into a host and domain part.
1258 host, domain = self.hostname.split(".", 1)
1259
47ea9f41
SS
1260 # Get and store curent IP address.
1261 address = self.get_address(proto)
1262
d1cd57eb 1263 data = {
47ea9f41 1264 "ip" : address,
d1cd57eb
SS
1265 "password" : self.password,
1266 "host" : host,
1267 "domain" : domain
1268 }
1269
1270 # Send update to the server.
1271 response = self.send_request(self.url, data=data)
1272
1273 # Get the full response message.
1274 output = response.read()
1275
1276 # Handle success messages.
d45139f6 1277 if self.get_xml_tag_value(output, "IP") == address:
d1cd57eb
SS
1278 return
1279
1280 # Handle error codes.
78c9780b 1281 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
d1cd57eb
SS
1282
1283 if errorcode == "304156":
1284 raise DDNSAuthenticationError
1285 elif errorcode == "316153":
fb701db9 1286 raise DDNSRequestError(_("Domain not found"))
d1cd57eb 1287 elif errorcode == "316154":
fb701db9 1288 raise DDNSRequestError(_("Domain not active"))
d1cd57eb
SS
1289 elif errorcode in ("380098", "380099"):
1290 raise DDNSInternalServerError
1291
1292 # If we got here, some other update error happened.
1293 raise DDNSUpdateError
1294
1295
5d4bec40
MT
1296class DDNSProviderNOIP(DDNSProtocolDynDNS2, DDNSProvider):
1297 handle = "no-ip.com"
1298 name = "No-IP"
1299 website = "http://www.no-ip.com/"
1300 protocols = ("ipv4",)
f22ab085
MT
1301
1302 # Information about the format of the HTTP request is to be found
1303 # here: http://www.no-ip.com/integrate/request and
1304 # here: http://www.no-ip.com/integrate/response
1305
88f39629 1306 url = "http://dynupdate.no-ip.com/nic/update"
2de06f59 1307
d45139f6
MT
1308 def prepare_request_data(self, proto):
1309 assert proto == "ipv4"
1310
2de06f59
MT
1311 data = {
1312 "hostname" : self.hostname,
d45139f6 1313 "address" : self.get_address(proto),
f22ab085
MT
1314 }
1315
88f39629 1316 return data
f22ab085
MT
1317
1318
c6e78218
SS
1319class DDNSProviderNowDNS(DDNSProtocolDynDNS2, DDNSProvider):
1320 handle = "now-dns.com"
1321 name = "NOW-DNS"
1322 website = "http://now-dns.com/"
1323 protocols = ("ipv6", "ipv4")
1324
1325 # Information about the format of the request is to be found
1326 # but only can be accessed by register an account and login
1327 # https://now-dns.com/?m=api
1328
1329 url = "https://now-dns.com/update"
1330
1331
31c95e4b
SS
1332class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2, DDNSProvider):
1333 handle = "nsupdate.info"
1334 name = "nsupdate.info"
b9221322 1335 website = "http://nsupdate.info/"
31c95e4b
SS
1336 protocols = ("ipv6", "ipv4",)
1337
1338 # Information about the format of the HTTP request can be found
b9221322 1339 # after login on the provider user interface and here:
31c95e4b
SS
1340 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
1341
7b5d382e
SS
1342 url = "https://nsupdate.info/nic/update"
1343
29c8c9c6
MT
1344 # TODO nsupdate.info can actually do this, but the functionality
1345 # has not been implemented here, yet.
1346 can_remove_records = False
1347
64018439
MT
1348 # After a failed update, there will be no retries
1349 # https://bugzilla.ipfire.org/show_bug.cgi?id=10603
1350 holdoff_failure_days = None
1351
31c95e4b
SS
1352 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
1353 # and for the password a so called secret.
1354 @property
1355 def username(self):
1356 return self.get("hostname")
1357
1358 @property
1359 def password(self):
9c777232 1360 return self.token or self.get("secret")
31c95e4b 1361
d45139f6 1362 def prepare_request_data(self, proto):
31c95e4b 1363 data = {
d45139f6 1364 "myip" : self.get_address(proto),
31c95e4b
SS
1365 }
1366
1367 return data
1368
1369
90663439
SS
1370class DDNSProviderOpenDNS(DDNSProtocolDynDNS2, DDNSProvider):
1371 handle = "opendns.com"
1372 name = "OpenDNS"
1373 website = "http://www.opendns.com"
1374
1375 # Detailed information about the update request and possible
1376 # response codes can be obtained from here:
1377 # https://support.opendns.com/entries/23891440
1378
1379 url = "https://updates.opendns.com/nic/update"
1380
d45139f6 1381 def prepare_request_data(self, proto):
90663439
SS
1382 data = {
1383 "hostname" : self.hostname,
d45139f6 1384 "myip" : self.get_address(proto),
90663439
SS
1385 }
1386
1387 return data
1388
1389
5d4bec40
MT
1390class DDNSProviderOVH(DDNSProtocolDynDNS2, DDNSProvider):
1391 handle = "ovh.com"
1392 name = "OVH"
1393 website = "http://www.ovh.com/"
1394 protocols = ("ipv4",)
a508bda6
SS
1395
1396 # OVH only provides very limited information about how to
1397 # update a DynDNS host. They only provide the update url
1398 # on the their german subpage.
1399 #
1400 # http://hilfe.ovh.de/DomainDynHost
1401
1402 url = "https://www.ovh.com/nic/update"
1403
d45139f6
MT
1404 def prepare_request_data(self, proto):
1405 data = DDNSProtocolDynDNS2.prepare_request_data(self, proto)
54d3efc8
MT
1406 data.update({
1407 "system" : "dyndns",
1408 })
1409
1410 return data
a508bda6
SS
1411
1412
ef33455e 1413class DDNSProviderRegfish(DDNSProvider):
6a11646e
MT
1414 handle = "regfish.com"
1415 name = "Regfish GmbH"
1416 website = "http://www.regfish.com/"
ef33455e
SS
1417
1418 # A full documentation to the providers api can be found here
1419 # but is only available in german.
1420 # https://www.regfish.de/domains/dyndns/dokumentation
1421
1422 url = "https://dyndns.regfish.de/"
29c8c9c6 1423 can_remove_records = False
ef33455e
SS
1424
1425 def update(self):
1426 data = {
1427 "fqdn" : self.hostname,
1428 }
1429
1430 # Check if we update an IPv6 address.
1431 address6 = self.get_address("ipv6")
1432 if address6:
1433 data["ipv6"] = address6
1434
1435 # Check if we update an IPv4 address.
1436 address4 = self.get_address("ipv4")
1437 if address4:
1438 data["ipv4"] = address4
1439
1440 # Raise an error if none address is given.
1441 if not data.has_key("ipv6") and not data.has_key("ipv4"):
1442 raise DDNSConfigurationError
1443
1444 # Check if a token has been set.
1445 if self.token:
1446 data["token"] = self.token
1447
1448 # Raise an error if no token and no useranem and password
1449 # are given.
1450 elif not self.username and not self.password:
fb701db9 1451 raise DDNSConfigurationError(_("No Auth details specified"))
ef33455e
SS
1452
1453 # HTTP Basic Auth is only allowed if no token is used.
1454 if self.token:
1455 # Send update to the server.
1456 response = self.send_request(self.url, data=data)
1457 else:
1458 # Send update to the server.
1459 response = self.send_request(self.url, username=self.username, password=self.password,
1460 data=data)
1461
1462 # Get the full response message.
1463 output = response.read()
1464
1465 # Handle success messages.
1466 if "100" in output or "101" in output:
1467 return
1468
1469 # Handle error codes.
1470 if "401" or "402" in output:
1471 raise DDNSAuthenticationError
1472 elif "408" in output:
fb701db9 1473 raise DDNSRequestError(_("Invalid IPv4 address has been sent"))
ef33455e 1474 elif "409" in output:
fb701db9 1475 raise DDNSRequestError(_("Invalid IPv6 address has been sent"))
ef33455e 1476 elif "412" in output:
fb701db9 1477 raise DDNSRequestError(_("No valid FQDN was given"))
ef33455e
SS
1478 elif "414" in output:
1479 raise DDNSInternalServerError
1480
1481 # If we got here, some other update error happened.
1482 raise DDNSUpdateError
1483
1484
f77e6bc9
SS
1485class DDNSProviderSchokokeksDNS(DDNSProtocolDynDNS2, DDNSProvider):
1486 handle = "schokokeks.org"
1487 name = "Schokokeks"
1488 website = "http://www.schokokeks.org/"
1489 protocols = ("ipv4",)
1490
1491 # Information about the format of the request is to be found
1492 # https://wiki.schokokeks.org/DynDNS
1493 url = "https://dyndns.schokokeks.org/nic/update"
1494
1495
5d4bec40 1496class DDNSProviderSelfhost(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
1497 handle = "selfhost.de"
1498 name = "Selfhost.de"
1499 website = "http://www.selfhost.de/"
5d4bec40 1500 protocols = ("ipv4",)
f22ab085 1501
04db1862 1502 url = "https://carol.selfhost.de/nic/update"
f22ab085 1503
d45139f6
MT
1504 def prepare_request_data(self, proto):
1505 data = DDNSProtocolDynDNS2.prepare_request_data(self, proto)
04db1862
MT
1506 data.update({
1507 "hostname" : "1",
1508 })
f22ab085 1509
04db1862 1510 return data
b09b1545
SS
1511
1512
52e8a969
JS
1513class DDNSProviderServercow(DDNSProvider):
1514 handle = "servercow.de"
1515 name = "servercow.de"
1516 website = "https://servercow.de/"
1517 protocols = ("ipv4", "ipv6")
1518
1519 url = "https://www.servercow.de/dnsupdate/update.php"
1520 can_remove_records = False
1521
1522 def update_protocol(self, proto):
1523 data = {
1524 "ipaddr" : self.get_address(proto),
1525 "hostname" : self.hostname,
1526 "username" : self.username,
1527 "pass" : self.password,
1528 }
1529
1530 # Send request to provider
1531 response = self.send_request(self.url, data=data)
1532
1533 # Read response
1534 output = response.read()
1535
1536 # Server responds with OK if update was successful
1537 if output.startswith("OK"):
1538 return
1539
1540 # Catch any errors
1541 elif output.startswith("FAILED - Authentication failed"):
1542 raise DDNSAuthenticationError
1543
1544 # If we got here, some other update error happened
1545 raise DDNSUpdateError(output)
1546
1547
5d4bec40
MT
1548class DDNSProviderSPDNS(DDNSProtocolDynDNS2, DDNSProvider):
1549 handle = "spdns.org"
74703885
MT
1550 name = "SPDYN"
1551 website = "https://www.spdyn.de/"
b09b1545
SS
1552
1553 # Detailed information about request and response codes are provided
1554 # by the vendor. They are using almost the same mechanism and status
1555 # codes as dyndns.org so we can inherit all those stuff.
1556 #
1557 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
1558 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
1559
74703885 1560 url = "https://update.spdyn.de/nic/update"
4ec90b93 1561
94ab4379
SS
1562 @property
1563 def username(self):
1564 return self.get("username") or self.hostname
1565
1566 @property
1567 def password(self):
25f39b4e 1568 return self.get("password") or self.token
94ab4379 1569
4ec90b93 1570
5d4bec40
MT
1571class DDNSProviderStrato(DDNSProtocolDynDNS2, DDNSProvider):
1572 handle = "strato.com"
1573 name = "Strato AG"
1574 website = "http:/www.strato.com/"
1575 protocols = ("ipv4",)
7488825c
SS
1576
1577 # Information about the request and response can be obtained here:
1578 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
1579
1580 url = "https://dyndns.strato.com/nic/update"
1581
34af066e
SS
1582 def prepare_request_data(self, proto):
1583 data = DDNSProtocolDynDNS2.prepare_request_data(self, proto)
1584 data.update({
1585 "mx" : "NOCHG",
1586 "backupmx" : "NOCHG"
1587 })
1588
1589 return data
1590
7488825c 1591
5d4bec40
MT
1592class DDNSProviderTwoDNS(DDNSProtocolDynDNS2, DDNSProvider):
1593 handle = "twodns.de"
1594 name = "TwoDNS"
1595 website = "http://www.twodns.de"
1596 protocols = ("ipv4",)
a6183090
SS
1597
1598 # Detailed information about the request can be found here
1599 # http://twodns.de/en/faqs
1600 # http://twodns.de/en/api
1601
1602 url = "https://update.twodns.de/update"
1603
d45139f6
MT
1604 def prepare_request_data(self, proto):
1605 assert proto == "ipv4"
1606
a6183090 1607 data = {
d45139f6 1608 "ip" : self.get_address(proto),
a6183090
SS
1609 "hostname" : self.hostname
1610 }
1611
1612 return data
1613
1614
5d4bec40
MT
1615class DDNSProviderUdmedia(DDNSProtocolDynDNS2, DDNSProvider):
1616 handle = "udmedia.de"
1617 name = "Udmedia GmbH"
1618 website = "http://www.udmedia.de"
1619 protocols = ("ipv4",)
03bdd188
SS
1620
1621 # Information about the request can be found here
1622 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1623
1624 url = "https://www.udmedia.de/nic/update"
1625
1626
5d4bec40 1627class DDNSProviderVariomedia(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
1628 handle = "variomedia.de"
1629 name = "Variomedia"
1630 website = "http://www.variomedia.de/"
1631 protocols = ("ipv6", "ipv4",)
c8c7ca8f
SS
1632
1633 # Detailed information about the request can be found here
1634 # https://dyndns.variomedia.de/
1635
1636 url = "https://dyndns.variomedia.de/nic/update"
1637
d45139f6 1638 def prepare_request_data(self, proto):
c8c7ca8f
SS
1639 data = {
1640 "hostname" : self.hostname,
d45139f6 1641 "myip" : self.get_address(proto),
c8c7ca8f 1642 }
54d3efc8
MT
1643
1644 return data
98fbe467
SS
1645
1646
6b81b43c
MB
1647class DDNSProviderXLhost(DDNSProtocolDynDNS2, DDNSProvider):
1648 handle = "xlhost.de"
1649 name = "XLhost"
1650 website = "http://xlhost.de/"
1651 protocols = ("ipv4",)
1652
1653 # Information about the format of the HTTP request is to be found
1654 # here: https://xlhost.de/faq/index_html?topicId=CQA2ELIPO4SQ
1655
1656 url = "https://nsupdate.xlhost.de/"
1657
1658
f0554226 1659class DDNSProviderZoneedit(DDNSProvider):
5d4bec40
MT
1660 handle = "zoneedit.com"
1661 name = "Zoneedit"
1662 website = "http://www.zoneedit.com"
1663 protocols = ("ipv4",)
98fbe467
SS
1664
1665 # Detailed information about the request and the response codes can be
1666 # obtained here:
1667 # http://www.zoneedit.com/doc/api/other.html
1668 # http://www.zoneedit.com/faq.html
1669
1670 url = "https://dynamic.zoneedit.com/auth/dynamic.html"
1671
d45139f6 1672 def update_protocol(self, proto):
98fbe467 1673 data = {
d45139f6 1674 "dnsto" : self.get_address(proto),
98fbe467
SS
1675 "host" : self.hostname
1676 }
1677
1678 # Send update to the server.
1679 response = self.send_request(self.url, username=self.username, password=self.password,
1680 data=data)
1681
1682 # Get the full response message.
1683 output = response.read()
1684
1685 # Handle success messages.
1686 if output.startswith("<SUCCESS"):
1687 return
1688
1689 # Handle error codes.
1690 if output.startswith("invalid login"):
1691 raise DDNSAuthenticationError
1692 elif output.startswith("<ERROR CODE=\"704\""):
fb701db9 1693 raise DDNSRequestError(_("No valid FQDN was given"))
98fbe467 1694 elif output.startswith("<ERROR CODE=\"702\""):
fb701db9 1695 raise DDNSRequestError(_("Too frequent update requests have been sent"))
98fbe467
SS
1696
1697 # If we got here, some other update error happened.
1698 raise DDNSUpdateError
e53d3225
SS
1699
1700
d1cb1b54
MG
1701class DDNSProviderDNSmadeEasy(DDNSProvider):
1702 handle = "dnsmadeeasy.com"
1703 name = "DNSmadeEasy.com"
1704 website = "http://www.dnsmadeeasy.com/"
1705 protocols = ("ipv4",)
1706
1707 # DNS Made Easy Nameserver Provider also offering Dynamic DNS
1708 # Documentation can be found here:
1709 # http://www.dnsmadeeasy.com/dynamic-dns/
1710
1711 url = "https://cp.dnsmadeeasy.com/servlet/updateip?"
1712 can_remove_records = False
1713
1714 def update_protocol(self, proto):
1715 data = {
1716 "ip" : self.get_address(proto),
1717 "id" : self.hostname,
1718 "username" : self.username,
1719 "password" : self.password,
1720 }
1721
1722 # Send update to the server.
1723 response = self.send_request(self.url, data=data)
1724
1725 # Get the full response message.
1726 output = response.read()
1727
1728 # Handle success messages.
1729 if output.startswith("success") or output.startswith("error-record-ip-same"):
1730 return
1731
1732 # Handle error codes.
1733 if output.startswith("error-auth-suspend"):
fb701db9 1734 raise DDNSRequestError(_("Account has been suspended"))
d1cb1b54
MG
1735
1736 elif output.startswith("error-auth-voided"):
fb701db9 1737 raise DDNSRequestError(_("Account has been revoked"))
d1cb1b54
MG
1738
1739 elif output.startswith("error-record-invalid"):
fb701db9 1740 raise DDNSRequestError(_("Specified host does not exist"))
d1cb1b54
MG
1741
1742 elif output.startswith("error-auth"):
1743 raise DDNSAuthenticationError
1744
1745 # If we got here, some other update error happened.
1746 raise DDNSUpdateError(_("Server response: %s") % output)
1747
1748
e53d3225
SS
1749class DDNSProviderZZZZ(DDNSProvider):
1750 handle = "zzzz.io"
1751 name = "zzzz"
1752 website = "https://zzzz.io"
fbdff678 1753 protocols = ("ipv6", "ipv4",)
e53d3225
SS
1754
1755 # Detailed information about the update request can be found here:
1756 # https://zzzz.io/faq/
1757
1758 # Details about the possible response codes have been provided in the bugtracker:
1759 # https://bugzilla.ipfire.org/show_bug.cgi?id=10584#c2
1760
1761 url = "https://zzzz.io/api/v1/update"
29c8c9c6 1762 can_remove_records = False
e53d3225 1763
d45139f6 1764 def update_protocol(self, proto):
e53d3225 1765 data = {
d45139f6 1766 "ip" : self.get_address(proto),
e53d3225
SS
1767 "token" : self.token,
1768 }
1769
fbdff678
MT
1770 if proto == "ipv6":
1771 data["type"] = "aaaa"
1772
e53d3225
SS
1773 # zzzz uses the host from the full hostname as part
1774 # of the update url.
1775 host, domain = self.hostname.split(".", 1)
1776
1777 # Add host value to the update url.
1778 url = "%s/%s" % (self.url, host)
1779
1780 # Send update to the server.
1781 try:
1782 response = self.send_request(url, data=data)
1783
1784 # Handle error codes.
ff43fa70
MT
1785 except DDNSNotFound:
1786 raise DDNSRequestError(_("Invalid hostname specified"))
e53d3225
SS
1787
1788 # Handle success messages.
1789 if response.code == 200:
1790 return
1791
1792 # If we got here, some other update error happened.
1793 raise DDNSUpdateError