]> git.ipfire.org Git - ddns.git/blame - src/ddns/providers.py
dyns.net: Send request via HTTPS
[ddns.git] / src / ddns / providers.py
CommitLineData
91aead36 1#!/usr/bin/python3
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
91aead36
KB
26import urllib.request
27import urllib.error
28import urllib.parse
d1cd57eb 29import xml.dom.minidom
7399fc5b 30
91aead36 31from .i18n import _
7399fc5b 32
f22ab085
MT
33# Import all possible exception types.
34from .errors import *
35
7399fc5b
MT
36logger = logging.getLogger("ddns.providers")
37logger.propagate = 1
38
adfe6272
MT
39_providers = {}
40
41def get():
42 """
43 Returns a dict with all automatically registered providers.
44 """
45 return _providers.copy()
46
f22ab085 47class DDNSProvider(object):
6a11646e
MT
48 # A short string that uniquely identifies
49 # this provider.
50 handle = None
f22ab085 51
6a11646e
MT
52 # The full name of the provider.
53 name = None
f22ab085 54
6a11646e
MT
55 # A weburl to the homepage of the provider.
56 # (Where to register a new account?)
57 website = None
f22ab085 58
6a11646e
MT
59 # A list of supported protocols.
60 protocols = ("ipv6", "ipv4")
f22ab085
MT
61
62 DEFAULT_SETTINGS = {}
63
37e24fbf
MT
64 # holdoff time - Number of days no update is performed unless
65 # the IP address has changed.
66 holdoff_days = 30
67
112d3fb8
MT
68 # holdoff time for update failures - Number of days no update
69 # is tried after the last one has failed.
70 holdoff_failure_days = 0.5
71
29c8c9c6
MT
72 # True if the provider is able to remove records, too.
73 # Required to remove AAAA records if IPv6 is absent again.
74 can_remove_records = True
75
287b2bfe
SS
76 # True if the provider supports authentication via a random
77 # generated token instead of username and password.
78 supports_token_auth = True
79
64d3fad4
MT
80 @staticmethod
81 def supported():
82 """
83 Should be overwritten to check if the system the code is running
84 on has all the required tools to support this provider.
85 """
86 return True
87
f22ab085
MT
88 def __init__(self, core, **settings):
89 self.core = core
90
91 # Copy a set of default settings and
92 # update them by those from the configuration file.
93 self.settings = self.DEFAULT_SETTINGS.copy()
94 self.settings.update(settings)
95
571271bc
MT
96 def __init_subclass__(cls, **kwargs):
97 super().__init_subclass__(**kwargs)
98
99 if not all((cls.handle, cls.name, cls.website)):
100 raise DDNSError(_("Provider is not properly configured"))
101
102 assert cls.handle not in _providers, \
103 "Provider '%s' has already been registered" % cls.handle
104
105 # Register class
106 _providers[cls.handle] = cls
107
f22ab085
MT
108 def __repr__(self):
109 return "<DDNS Provider %s (%s)>" % (self.name, self.handle)
110
111 def __cmp__(self, other):
91aead36 112 return (lambda a, b: (a > b)-(a < b))(self.hostname, other.hostname)
f22ab085 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
91aead36
KB
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 194 if not self.can_remove_records:
91aead36 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):
91aead36
KB
203 logger.debug(_("An update for %(hostname)s (%(provider)s) is performed because of an IP address change") %
204 {"hostname": self.hostname, "provider": self.name})
37e24fbf
MT
205
206 return True
207
208 # If the holdoff time has expired, an update is required, too
209 if self.holdoff_time_expired():
91aead36
KB
210 logger.debug(_("An update for %(hostname)s (%(provider)s) is performed because the holdoff time has expired") %
211 {"hostname": self.hostname, "provider": self.name})
37e24fbf
MT
212
213 return True
214
215 # Otherwise, we don't need to perform an update
91aead36
KB
216 logger.debug(_("No update required for %(hostname)s (%(provider)s)") %
217 {"hostname": self.hostname, "provider": self.name})
37e24fbf
MT
218
219 return False
220
112d3fb8
MT
221 @property
222 def has_failure(self):
223 """
224 Returns True when the last update has failed and no retry
225 should be performed, yet.
226 """
227 last_status = self.db.last_update_status(self.hostname)
228
229 # Return False if the last update has not failed.
230 if not last_status == "failure":
231 return False
232
64018439
MT
233 # If there is no holdoff time, we won't update ever again.
234 if self.holdoff_failure_days is None:
91aead36 235 logger.warning(_("An update has not been performed because earlier updates failed for %s") % self.hostname)
64018439
MT
236 logger.warning(_("There will be no retries"))
237
238 return True
239
112d3fb8
MT
240 # Determine when the holdoff time ends
241 last_update = self.db.last_update(self.hostname, status=last_status)
242 holdoff_end = last_update + datetime.timedelta(days=self.holdoff_failure_days)
243
244 now = datetime.datetime.utcnow()
245 if now < holdoff_end:
246 failure_message = self.db.last_update_failure_message(self.hostname)
247
91aead36 248 logger.warning(_("An update has not been performed because earlier updates failed for %s") % self.hostname)
112d3fb8
MT
249
250 if failure_message:
251 logger.warning(_("Last failure message:"))
252
253 for line in failure_message.splitlines():
254 logger.warning(" %s" % line)
255
256 logger.warning(_("Further updates will be withheld until %s") % holdoff_end)
257
258 return True
259
260 return False
261
37e24fbf 262 def ip_address_changed(self, protos):
7399fc5b
MT
263 """
264 Returns True if this host is already up to date
265 and does not need to change the IP address on the
266 name server.
267 """
268 for proto in protos:
269 addresses = self.core.system.resolve(self.hostname, proto)
7399fc5b
MT
270 current_address = self.get_address(proto)
271
29c8c9c6
MT
272 # Handle if the system has not got any IP address from a protocol
273 # (i.e. had full dual-stack connectivity which it has not any more)
274 if current_address is None:
275 # If addresses still exists in the DNS system and if this provider
276 # is able to remove records, we will do that.
277 if addresses and self.can_remove_records:
278 return True
279
280 # Otherwise, we cannot go on...
38d81db4
MT
281 continue
282
7399fc5b 283 if not current_address in addresses:
37e24fbf
MT
284 return True
285
286 return False
7399fc5b 287
37e24fbf
MT
288 def holdoff_time_expired(self):
289 """
290 Returns true if the holdoff time has expired
291 and the host requires an update
292 """
293 # If no holdoff days is defined, we cannot go on
294 if not self.holdoff_days:
295 return False
296
297 # Get the timestamp of the last successfull update
112d3fb8 298 last_update = self.db.last_update(self.hostname, status="success")
37e24fbf
MT
299
300 # If no timestamp has been recorded, no update has been
301 # performed. An update should be performed now.
302 if not last_update:
303 return True
7399fc5b 304
37e24fbf
MT
305 # Determine when the holdoff time ends
306 holdoff_end = last_update + datetime.timedelta(days=self.holdoff_days)
307
308 now = datetime.datetime.utcnow()
309
310 if now >= holdoff_end:
311 logger.debug("The holdoff time has expired for %s" % self.hostname)
312 return True
313 else:
91aead36
KB
314 logger.debug("Updates for %s are held off until %s" %
315 (self.hostname, holdoff_end))
37e24fbf 316 return False
7399fc5b 317
f22ab085
MT
318 def send_request(self, *args, **kwargs):
319 """
320 Proxy connection to the send request
321 method.
322 """
323 return self.core.system.send_request(*args, **kwargs)
324
e3c70807 325 def get_address(self, proto, default=None):
f22ab085
MT
326 """
327 Proxy method to get the current IP address.
328 """
e3c70807 329 return self.core.system.get_address(proto) or default
f22ab085 330
d45139f6
MT
331 def have_address(self, proto):
332 """
333 Returns True if an IP address for the given protocol
334 is known and usable.
335 """
336 address = self.get_address(proto)
337
338 if address:
339 return True
340
341 return False
342
f22ab085 343
5d4bec40
MT
344class DDNSProtocolDynDNS2(object):
345 """
346 This is an abstract class that implements the DynDNS updater
347 protocol version 2. As this is a popular way to update dynamic
348 DNS records, this class is supposed make the provider classes
349 shorter and simpler.
350 """
351
352 # Information about the format of the request is to be found
486c1b9d 353 # http://dyn.com/support/developers/api/perform-update/
5d4bec40
MT
354 # http://dyn.com/support/developers/api/return-codes/
355
29c8c9c6
MT
356 # The DynDNS protocol version 2 does not allow to remove records
357 can_remove_records = False
358
287b2bfe
SS
359 # The DynDNS protocol version 2 only supports authentication via
360 # username and password.
361 supports_token_auth = 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.
91aead36 378 response = DDNSProvider.send_request(self, self.url, data=data, username=self.username, password=self.password)
5d4bec40
MT
379
380 # Get the full response message.
472b2408 381 output = response.read().decode()
5d4bec40
MT
382
383 # Handle success messages.
384 if output.startswith("good") or output.startswith("nochg"):
385 return
386
387 # Handle error codes.
388 if output == "badauth":
389 raise DDNSAuthenticationError
af97e369 390 elif output == "abuse":
5d4bec40
MT
391 raise DDNSAbuseError
392 elif output == "notfqdn":
fb701db9 393 raise DDNSRequestError(_("No valid FQDN was given"))
5d4bec40 394 elif output == "nohost":
fb701db9 395 raise DDNSRequestError(_("Specified host does not exist"))
5d4bec40
MT
396 elif output == "911":
397 raise DDNSInternalServerError
398 elif output == "dnserr":
fb701db9 399 raise DDNSInternalServerError(_("DNS error encountered"))
6ddfd5c7
SS
400 elif output == "badagent":
401 raise DDNSBlockedError
9db9ea25
MP
402 elif output == "badip":
403 raise DDNSBlockedError
5d4bec40
MT
404
405 # If we got here, some other update error happened.
406 raise DDNSUpdateError(_("Server response: %s") % output)
407
408
78c9780b
SS
409class DDNSResponseParserXML(object):
410 """
411 This class provides a parser for XML responses which
412 will be sent by various providers. This class uses the python
413 shipped XML minidom module to walk through the XML tree and return
414 a requested element.
91aead36 415 """
78c9780b
SS
416
417 def get_xml_tag_value(self, document, content):
418 # Send input to the parser.
419 xmldoc = xml.dom.minidom.parseString(document)
420
421 # Get XML elements by the given content.
422 element = xmldoc.getElementsByTagName(content)
423
424 # If no element has been found, we directly can return None.
425 if not element:
426 return None
427
428 # Only get the first child from an element, even there are more than one.
429 firstchild = element[0].firstChild
430
431 # Get the value of the child.
432 value = firstchild.nodeValue
433
434 # Return the value.
435 return value
436
437
3b16fdb1 438class DDNSProviderAllInkl(DDNSProvider):
6a11646e
MT
439 handle = "all-inkl.com"
440 name = "All-inkl.com"
441 website = "http://all-inkl.com/"
442 protocols = ("ipv4",)
3b16fdb1
SS
443
444 # There are only information provided by the vendor how to
445 # perform an update on a FRITZ Box. Grab requried informations
446 # from the net.
447 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
448
86aff9ea 449 url = "https://dyndns.kasserver.com"
29c8c9c6 450 can_remove_records = False
287b2bfe 451 supports_token_auth = 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.
472b2408 459 output = response.read().decode()
3b16fdb1
SS
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
287b2bfe
SS
476 supports_token_auth = False
477
64d3fad4
MT
478 @staticmethod
479 def supported():
480 # Search if the nsupdate utility is available
481 paths = os.environ.get("PATH")
482
483 for path in paths.split(":"):
484 executable = os.path.join(path, "nsupdate")
485
486 if os.path.exists(executable):
487 return True
488
489 return False
490
a892c594
MT
491 def update(self):
492 scriptlet = self.__make_scriptlet()
493
494 # -v enables TCP hence we transfer keys and other data that may
495 # exceed the size of one packet.
496 # -t sets the timeout
497 command = ["nsupdate", "-v", "-t", "60"]
498
91aead36 499 p = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
a892c594
MT
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
27aea61b 550 return "\n".join(scriptlet).encode()
a892c594
MT
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
287b2bfe 564 supports_token_auth = False
78046ffe
SS
565
566 def update_protocol(self, proto):
567 data = {
568 "hostname" : self.hostname,
569 "myip" : self.get_address(proto),
570 }
571
572 # Send update to the server.
573 try:
91aead36 574 response = self.send_request(self.url, username=self.username, password=self.password, data=data)
78046ffe
SS
575
576 # Handle error codes.
91aead36 577 except urllib.error.HTTPError as e:
78046ffe
SS
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
aae1dece 629 url = "https://www.ddnss.de/upd.php"
f1332a16 630 can_remove_records = False
287b2bfe 631 supports_token_auth = False
f1332a16
SS
632
633 def update_protocol(self, proto):
634 data = {
635 "ip" : self.get_address(proto),
636 "host" : self.hostname,
637 }
638
639 # Check if a token has been set.
640 if self.token:
641 data["key"] = self.token
642
643 # Check if username and hostname are given.
644 elif self.username and self.password:
645 data.update({
646 "user" : self.username,
647 "pwd" : self.password,
648 })
649
650 # Raise an error if no auth details are given.
651 else:
652 raise DDNSConfigurationError
653
654 # Send update to the server.
655 response = self.send_request(self.url, data=data)
656
657 # This provider sends the response code as part of the header.
f1332a16 658 # Get status information from the header.
7d0956d1 659 output = response.getheader('ddnss-response')
f1332a16
SS
660
661 # Handle success messages.
662 if output == "good" or output == "nochg":
663 return
664
665 # Handle error codes.
666 if output == "badauth":
667 raise DDNSAuthenticationError
668 elif output == "notfqdn":
fb701db9 669 raise DDNSRequestError(_("No valid FQDN was given"))
f1332a16 670 elif output == "nohost":
fb701db9 671 raise DDNSRequestError(_("Specified host does not exist"))
f1332a16
SS
672 elif output == "911":
673 raise DDNSInternalServerError
674 elif output == "dnserr":
fb701db9 675 raise DDNSInternalServerError(_("DNS error encountered"))
f1332a16 676 elif output == "disabled":
fb701db9 677 raise DDNSRequestError(_("Account disabled or locked"))
f1332a16
SS
678
679 # If we got here, some other update error happened.
680 raise DDNSUpdateError
681
682
f3cf1f70 683class DDNSProviderDHS(DDNSProvider):
6a11646e
MT
684 handle = "dhs.org"
685 name = "DHS International"
686 website = "http://dhs.org/"
687 protocols = ("ipv4",)
f3cf1f70
SS
688
689 # No information about the used update api provided on webpage,
690 # grabed from source code of ez-ipudate.
b2b05ef3 691
d156590b
SS
692 # Provider currently does not support TLS 1.2.
693 url = "https://members.dhs.org/nic/hosts"
29c8c9c6 694 can_remove_records = False
287b2bfe 695 supports_token_auth = False
f3cf1f70 696
d45139f6 697 def update_protocol(self, proto):
f3cf1f70
SS
698 data = {
699 "domain" : self.hostname,
d45139f6 700 "ip" : self.get_address(proto),
f3cf1f70
SS
701 "hostcmd" : "edit",
702 "hostcmdstage" : "2",
703 "type" : "4",
704 }
705
706 # Send update to the server.
91aead36 707 response = self.send_request(self.url, username=self.username, password=self.password, data=data)
f3cf1f70
SS
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
287b2bfe 728 supports_token_auth = False
39301272 729
d45139f6 730 def update_protocol(self, proto):
39301272
SS
731 data = {
732 "domain" : self.hostname,
d45139f6 733 "ip" : self.get_address(proto),
39301272
SS
734 }
735
736 # Send update to the server.
91aead36 737 response = self.send_request(self.url, username=self.username, password=self.password, data=data)
39301272
SS
738
739 # Get the full response message.
472b2408 740 output = response.read().decode()
39301272
SS
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
287b2bfe 777 supports_token_auth = False
43b2cd59 778
d45139f6 779 def update_protocol(self, proto):
43b2cd59 780 data = {
d45139f6 781 "ip" : self.get_address(proto),
43b2cd59
SS
782 "id" : self.hostname,
783 "pw" : self.password
784 }
785
786 # Send update to the server.
787 response = self.send_request(self.url, data=data)
788
789 # Get the full response message.
472b2408 790 output = response.read().decode()
43b2cd59
SS
791
792 # Remove all leading and trailing whitespace.
793 output = output.strip()
794
795 # Handle success messages.
796 if "now points to" in output:
797 return
798
799 # Handle error codes.
800 if output == "No hostname to update was supplied.":
fb701db9 801 raise DDNSRequestError(_("No hostname specified"))
43b2cd59
SS
802
803 elif output == "The hostname you supplied is not valid.":
fb701db9 804 raise DDNSRequestError(_("Invalid hostname specified"))
43b2cd59
SS
805
806 elif output == "The password you supplied is not valid.":
807 raise DDNSAuthenticationError
808
809 elif output == "Administration has disabled this account.":
fb701db9 810 raise DDNSRequestError(_("Account has been disabled"))
43b2cd59
SS
811
812 elif output == "Illegal character in IP.":
fb701db9 813 raise DDNSRequestError(_("Invalid IP address has been sent"))
43b2cd59
SS
814
815 elif output == "Too many failed requests.":
fb701db9 816 raise DDNSRequestError(_("Too many failed requests"))
43b2cd59
SS
817
818 # If we got here, some other update error happened.
819 raise DDNSUpdateError
820
821
ebdb3724 822class DDNSProviderDuckDNS(DDNSProvider):
fc91be92
MT
823 handle = "duckdns.org"
824 name = "Duck DNS"
825 website = "http://www.duckdns.org/"
ebdb3724 826 protocols = ("ipv6", "ipv4",)
fc91be92
MT
827
828 # Information about the format of the request is to be found
ebdb3724
CM
829 # https://www.duckdns.org/spec.jsp
830
831 url = "https://www.duckdns.org/update"
832 can_remove_records = False
287b2bfe 833 supports_token_auth = True
ebdb3724
CM
834
835 def update(self):
836 # Raise an error if no auth details are given.
837 if not self.token:
838 raise DDNSConfigurationError
fc91be92 839
ebdb3724
CM
840 data = {
841 "domains" : self.hostname,
842 "token" : self.token,
843 }
844
845 # Check if we update an IPv4 address.
846 address4 = self.get_address("ipv4")
847 if address4:
848 data["ip"] = address4
849
850 # Check if we update an IPv6 address.
851 address6 = self.get_address("ipv6")
852 if address6:
853 data["ipv6"] = address6
854
855 # Raise an error if no address is given.
856 if "ip" not in data and "ipv6" not in data:
857 raise DDNSConfigurationError
858
859 # Send update to the server.
860 response = self.send_request(self.url, data=data)
861
862 # Get the full response message.
863 output = response.read().decode()
864
865 # Remove all leading and trailing whitespace.
866 output = output.strip()
867
868 # Handle success messages.
869 if output == "OK":
870 return
871
872 # The provider does not give detailed information
873 # if the update fails. Only a "KO" will be sent back.
874 if output == "KO":
875 raise DDNSUpdateError
876
877 # If we got here, some other update error happened.
878 raise DDNSUpdateError
fc91be92
MT
879
880
9db9ea25
MP
881class DDNSProviderDyFi(DDNSProtocolDynDNS2, DDNSProvider):
882 handle = "dy.fi"
883 name = "dy.fi"
884 website = "https://www.dy.fi/"
885 protocols = ("ipv4",)
886
887 # Information about the format of the request is to be found
888 # https://www.dy.fi/page/clients?lang=en
889 # https://www.dy.fi/page/specification?lang=en
890
ce6e977f 891 url = "https://www.dy.fi/nic/update"
9db9ea25
MP
892
893 # Please only send automatic updates when your IP address changes,
894 # or once per 5 to 6 days to refresh the address mapping (they will
895 # expire if not refreshed within 7 days).
896 holdoff_days = 6
897
898
5d4bec40 899class DDNSProviderDynDNS(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
900 handle = "dyndns.org"
901 name = "Dyn"
902 website = "http://dyn.com/dns/"
903 protocols = ("ipv4",)
bfed6701
SS
904
905 # Information about the format of the request is to be found
906 # http://http://dyn.com/support/developers/api/perform-update/
907 # http://dyn.com/support/developers/api/return-codes/
b2b05ef3 908
bfed6701
SS
909 url = "https://members.dyndns.org/nic/update"
910
bfed6701 911
ea32ab26
DW
912class DDNSProviderDomainOffensive(DDNSProtocolDynDNS2, DDNSProvider):
913 handle = "do.de"
914 name = "Domain-Offensive"
915 website = "https://www.do.de/"
916 protocols = ("ipv6", "ipv4")
917
918 # Detailed information about the request and response codes
919 # are available on the providers webpage.
920 # https://www.do.de/wiki/FlexDNS_-_Entwickler
921
922 url = "https://ddns.do.de/"
923
ac8b9f48
CS
924class DDNSProviderDynUp(DDNSProvider):
925 handle = "dynup.de"
926 name = "DynUp.DE"
927 website = "http://dynup.de/"
928 protocols = ("ipv4",)
929
930 # Information about the format of the HTTPS request is to be found
931 # https://dyndnsfree.de/user/hilfe.php
932
933 url = "https://dynup.de/dyn.php"
934 can_remove_records = False
287b2bfe 935 supports_token_auth = False
ac8b9f48
CS
936
937 def update_protocol(self, proto):
938 data = {
939 "username" : self.username,
940 "password" : self.password,
941 "hostname" : self.hostname,
942 "print" : '1',
943 }
944
945 # Send update to the server.
946 response = self.send_request(self.url, data=data)
947
948 # Get the full response message.
472b2408 949 output = response.read().decode()
ac8b9f48
CS
950
951 # Remove all leading and trailing whitespace.
952 output = output.strip()
953
954 # Handle success messages.
91aead36 955 if output.startswith("I:OK"):
ac8b9f48
CS
956 return
957
958 # If we got here, some other update error happened.
959 raise DDNSUpdateError
960
961
5d4bec40 962class DDNSProviderDynU(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
963 handle = "dynu.com"
964 name = "Dynu"
965 website = "http://dynu.com/"
966 protocols = ("ipv6", "ipv4",)
3a8407fa
SS
967
968 # Detailed information about the request and response codes
969 # are available on the providers webpage.
970 # http://dynu.com/Default.aspx?page=dnsapi
971
972 url = "https://api.dynu.com/nic/update"
973
d45139f6
MT
974 # DynU sends the IPv6 and IPv4 address in one request
975
976 def update(self):
977 data = DDNSProtocolDynDNS2.prepare_request_data(self, "ipv4")
54d3efc8
MT
978
979 # This one supports IPv6
cdc078dc
SS
980 myipv6 = self.get_address("ipv6")
981
982 # Add update information if we have an IPv6 address.
983 if myipv6:
984 data["myipv6"] = myipv6
54d3efc8 985
304026a3 986 self.send_request(data)
3a8407fa
SS
987
988
5420343a 989class DDNSProviderEasyDNS(DDNSProvider):
5d4bec40
MT
990 handle = "easydns.com"
991 name = "EasyDNS"
992 website = "http://www.easydns.com/"
993 protocols = ("ipv4",)
ee071271 994
5420343a
SS
995 # Detailed information about the request and response codes
996 # (API 1.3) are available on the providers webpage.
997 # https://fusion.easydns.com/index.php?/Knowledgebase/Article/View/102/7/dynamic-dns
ee071271 998
c0886a42 999 url = "https://api.cp.easydns.com/dyn/tomato.php"
ee071271 1000
287b2bfe
SS
1001 supports_token_auth = False
1002
5420343a
SS
1003 def update_protocol(self, proto):
1004 data = {
1005 "myip" : self.get_address(proto, "-"),
1006 "hostname" : self.hostname,
1007 }
1008
1009 # Send update to the server.
91aead36 1010 response = self.send_request(self.url, data=data, username=self.username, password=self.password)
5420343a
SS
1011
1012 # Get the full response message.
472b2408 1013 output = response.read().decode()
5420343a
SS
1014
1015 # Remove all leading and trailing whitespace.
1016 output = output.strip()
1017
1018 # Handle success messages.
1019 if output.startswith("NOERROR"):
1020 return
1021
1022 # Handle error codes.
1023 if output.startswith("NOACCESS"):
1024 raise DDNSAuthenticationError
1025
1026 elif output.startswith("NOSERVICE"):
fb701db9 1027 raise DDNSRequestError(_("Dynamic DNS is not turned on for this domain"))
5420343a
SS
1028
1029 elif output.startswith("ILLEGAL INPUT"):
fb701db9 1030 raise DDNSRequestError(_("Invalid data has been sent"))
5420343a
SS
1031
1032 elif output.startswith("TOOSOON"):
fb701db9 1033 raise DDNSRequestError(_("Too frequent update requests have been sent"))
5420343a
SS
1034
1035 # If we got here, some other update error happened.
1036 raise DDNSUpdateError
1037
ee071271 1038
90fe8843
CE
1039class DDNSProviderDomopoli(DDNSProtocolDynDNS2, DDNSProvider):
1040 handle = "domopoli.de"
1041 name = "domopoli.de"
1042 website = "http://domopoli.de/"
1043 protocols = ("ipv4",)
1044
1045 # https://www.domopoli.de/?page=howto#DynDns_start
1046
7211a5cf
SS
1047 # This provider does not support TLS 1.2.
1048 url = "https://dyndns.domopoli.de/nic/update"
90fe8843
CE
1049
1050
a197d1a6
SS
1051class DDNSProviderDynsNet(DDNSProvider):
1052 handle = "dyns.net"
1053 name = "DyNS"
1054 website = "http://www.dyns.net/"
1055 protocols = ("ipv4",)
29c8c9c6 1056 can_remove_records = False
287b2bfe 1057 supports_token_auth = False
a197d1a6
SS
1058
1059 # There is very detailed informatio about how to send the update request and
1060 # the possible response codes. (Currently we are using the v1.1 proto)
1061 # http://www.dyns.net/documentation/technical/protocol/
1062
c395275d 1063 url = "https://www.dyns.net/postscript011.php"
a197d1a6 1064
d45139f6 1065 def update_protocol(self, proto):
a197d1a6 1066 data = {
d45139f6 1067 "ip" : self.get_address(proto),
a197d1a6
SS
1068 "host" : self.hostname,
1069 "username" : self.username,
1070 "password" : self.password,
1071 }
1072
1073 # Send update to the server.
1074 response = self.send_request(self.url, data=data)
1075
1076 # Get the full response message.
472b2408 1077 output = response.read().decode()
a197d1a6
SS
1078
1079 # Handle success messages.
1080 if output.startswith("200"):
1081 return
1082
1083 # Handle error codes.
1084 if output.startswith("400"):
fb701db9 1085 raise DDNSRequestError(_("Malformed request has been sent"))
a197d1a6
SS
1086 elif output.startswith("401"):
1087 raise DDNSAuthenticationError
1088 elif output.startswith("402"):
fb701db9 1089 raise DDNSRequestError(_("Too frequent update requests have been sent"))
a197d1a6
SS
1090 elif output.startswith("403"):
1091 raise DDNSInternalServerError
1092
1093 # If we got here, some other update error happened.
d8c43654 1094 raise DDNSUpdateError(_("Server response: %s") % output)
a197d1a6
SS
1095
1096
35216523
SS
1097class DDNSProviderEnomCom(DDNSResponseParserXML, DDNSProvider):
1098 handle = "enom.com"
1099 name = "eNom Inc."
1100 website = "http://www.enom.com/"
d45139f6 1101 protocols = ("ipv4",)
35216523
SS
1102
1103 # There are very detailed information about how to send an update request and
1104 # the respone codes.
1105 # http://www.enom.com/APICommandCatalog/
1106
1107 url = "https://dynamic.name-services.com/interface.asp"
29c8c9c6 1108 can_remove_records = False
287b2bfe 1109 supports_token_auth = False
35216523 1110
d45139f6 1111 def update_protocol(self, proto):
35216523
SS
1112 data = {
1113 "command" : "setdnshost",
1114 "responsetype" : "xml",
d45139f6 1115 "address" : self.get_address(proto),
35216523
SS
1116 "domainpassword" : self.password,
1117 "zone" : self.hostname
1118 }
1119
1120 # Send update to the server.
1121 response = self.send_request(self.url, data=data)
1122
1123 # Get the full response message.
472b2408 1124 output = response.read().decode()
35216523
SS
1125
1126 # Handle success messages.
1127 if self.get_xml_tag_value(output, "ErrCount") == "0":
1128 return
1129
1130 # Handle error codes.
1131 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
1132
1133 if errorcode == "304155":
1134 raise DDNSAuthenticationError
1135 elif errorcode == "304153":
fb701db9 1136 raise DDNSRequestError(_("Domain not found"))
35216523
SS
1137
1138 # If we got here, some other update error happened.
1139 raise DDNSUpdateError
1140
1141
ab4e352e
SS
1142class DDNSProviderEntryDNS(DDNSProvider):
1143 handle = "entrydns.net"
1144 name = "EntryDNS"
1145 website = "http://entrydns.net/"
1146 protocols = ("ipv4",)
1147
1148 # Some very tiny details about their so called "Simple API" can be found
1149 # here: https://entrydns.net/help
1150 url = "https://entrydns.net/records/modify"
29c8c9c6 1151 can_remove_records = False
287b2bfe 1152 supports_token_auth = True
ab4e352e 1153
d45139f6 1154 def update_protocol(self, proto):
ab4e352e 1155 data = {
d45139f6 1156 "ip" : self.get_address(proto),
ab4e352e
SS
1157 }
1158
1159 # Add auth token to the update url.
1160 url = "%s/%s" % (self.url, self.token)
1161
1162 # Send update to the server.
1163 try:
babc5e6d 1164 response = self.send_request(url, data=data)
ab4e352e
SS
1165
1166 # Handle error codes
91aead36 1167 except urllib.error.HTTPError as e:
ab4e352e
SS
1168 if e.code == 404:
1169 raise DDNSAuthenticationError
1170
1171 elif e.code == 422:
1172 raise DDNSRequestError(_("An invalid IP address was submitted"))
1173
1174 raise
1175
1176 # Handle success messages.
1177 if response.code == 200:
1178 return
1179
1180 # If we got here, some other update error happened.
1181 raise DDNSUpdateError
1182
1183
aa21a4c6 1184class DDNSProviderFreeDNSAfraidOrg(DDNSProvider):
6a11646e
MT
1185 handle = "freedns.afraid.org"
1186 name = "freedns.afraid.org"
1187 website = "http://freedns.afraid.org/"
aa21a4c6
SS
1188
1189 # No information about the request or response could be found on the vendor
1190 # page. All used values have been collected by testing.
1191 url = "https://freedns.afraid.org/dynamic/update.php"
29c8c9c6 1192 can_remove_records = False
287b2bfe 1193 supports_token_auth = True
aa21a4c6 1194
d45139f6 1195 def update_protocol(self, proto):
aa21a4c6 1196 data = {
d45139f6 1197 "address" : self.get_address(proto),
aa21a4c6
SS
1198 }
1199
1200 # Add auth token to the update url.
1201 url = "%s?%s" % (self.url, self.token)
1202
1203 # Send update to the server.
1204 response = self.send_request(url, data=data)
1205
a204b107 1206 # Get the full response message.
472b2408 1207 output = response.read().decode()
a204b107
SS
1208
1209 # Handle success messages.
aa21a4c6
SS
1210 if output.startswith("Updated") or "has not changed" in output:
1211 return
1212
1213 # Handle error codes.
1214 if output == "ERROR: Unable to locate this record":
1215 raise DDNSAuthenticationError
1216 elif "is an invalid IP address" in output:
fb701db9 1217 raise DDNSRequestError(_("Invalid IP address has been sent"))
aa21a4c6 1218
3b524cf2
SS
1219 # If we got here, some other update error happened.
1220 raise DDNSUpdateError
1221
aa21a4c6 1222
a026b273
JC
1223class DDNSProviderHENet(DDNSProtocolDynDNS2, DDNSProvider):
1224 handle = "he.net"
1225 name = "he.net"
1226 website = "https://he.net"
1227 protocols = ("ipv6", "ipv4",)
1228
1229 # Detailed information about the update api can be found here.
1230 # http://dns.he.net/docs.html
1231
1232 url = "https://dyn.dns.he.net/nic/update"
1233 @property
1234 def username(self):
1235 return self.get("hostname")
1236
1237
1238
b39971d1
AK
1239class DDNSProviderItsdns(DDNSProtocolDynDNS2, DDNSProvider):
1240 handle = "inwx.com"
1241 name = "INWX"
1242 website = "https://www.inwx.com"
1243 protocols = ("ipv6", "ipv4")
1244
1245 # Information about the format of the HTTP request is to be found
1246 # here: https://www.inwx.com/en/nameserver2/dyndns (requires login)
1247 # Notice: The URL is the same for: inwx.com|de|at|ch|es
1248
1249 url = "https://dyndns.inwx.com/nic/update"
1250
1251
327095f0
JS
1252class DDNSProviderItsdns(DDNSProtocolDynDNS2, DDNSProvider):
1253 handle = "itsdns.de"
1254 name = "it's DNS"
1255 website = "http://www.itsdns.de/"
1256 protocols = ("ipv6", "ipv4")
1257
1258 # Information about the format of the HTTP request is to be found
1259 # here: https://www.itsdns.de/dynupdatehelp.htm
1260
1261 url = "https://www.itsdns.de/update.php"
1262
1263
09b07b23
LAH
1264class DDNSProviderJoker(DDNSProtocolDynDNS2, DDNSProvider):
1265 handle = "joker.com"
1266 name = "Joker.com Dynamic DNS"
1267 website = "https://joker.com/"
1268 protocols = ("ipv4",)
1269
1270 # Information about the request can be found here:
1271 # https://joker.com/faq/content/11/427/en/what-is-dynamic-dns-dyndns.html
1272 # Using DynDNS V2 protocol over HTTPS here
1273
1274 url = "https://svc.joker.com/nic/update"
1275
1276
f1c737c0
CW
1277class DDNSProviderKEYSYSTEMS(DDNSProvider):
1278 handle = "key-systems.net"
1279 name = "dynamicdns.key-systems.net"
1280 website = "https://domaindiscount24.com/"
1281 protocols = ("ipv4",)
1282
1283 # There are only information provided by the domaindiscount24 how to
1284 # perform an update with HTTP APIs
1285 # https://www.domaindiscount24.com/faq/dynamic-dns
1286 # examples: https://dynamicdns.key-systems.net/update.php?hostname=hostname&password=password&ip=auto
1287 # https://dynamicdns.key-systems.net/update.php?hostname=hostname&password=password&ip=213.x.x.x&mx=213.x.x.x
1288
1289 url = "https://dynamicdns.key-systems.net/update.php"
1290 can_remove_records = False
287b2bfe 1291 supports_token_auth = False
f1c737c0
CW
1292
1293 def update_protocol(self, proto):
1294 address = self.get_address(proto)
1295 data = {
1296 "hostname" : self.hostname,
1297 "password" : self.password,
1298 "ip" : address,
1299 }
1300
1301 # Send update to the server.
1302 response = self.send_request(self.url, data=data)
1303
1304 # Get the full response message.
1305 output = response.read().decode()
1306
1307 # Handle success messages.
1308 if "code = 200" in output:
1309 return
1310
1311 # Handle error messages.
1312 if "abuse prevention triggered" in output:
1313 raise DDNSAbuseError
1314 elif "invalid password" in output:
1315 raise DDNSAuthenticationError
1316 elif "Authorization failed" in output:
1317 raise DDNSRequestError(_("Invalid hostname specified"))
1318
1319 # If we got here, some other update error happened.
1320 raise DDNSUpdateError
1321
1322
c510004d
SS
1323class DDNSProviderGoogle(DDNSProtocolDynDNS2, DDNSProvider):
1324 handle = "domains.google.com"
1325 name = "Google Domains"
1326 website = "https://domains.google.com/"
1327 protocols = ("ipv4",)
1328
1329 # Information about the format of the HTTP request is to be found
1330 # here: https://support.google.com/domains/answer/6147083?hl=en
1331
1332 url = "https://domains.google.com/nic/update"
1333
1334
a08c1b72 1335class DDNSProviderLightningWireLabs(DDNSProvider):
6a11646e 1336 handle = "dns.lightningwirelabs.com"
fb115fdc 1337 name = "Lightning Wire Labs DNS Service"
92cd7211 1338 website = "https://dns.lightningwirelabs.com/"
a08c1b72
SS
1339
1340 # Information about the format of the HTTPS request is to be found
1341 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
b2b05ef3 1342
287b2bfe
SS
1343 supports_token_auth = True
1344
a08c1b72
SS
1345 url = "https://dns.lightningwirelabs.com/update"
1346
5f402f36 1347 def update(self):
c772f5f2
MT
1348 # Raise an error if no auth details are given.
1349 if not self.token:
1350 raise DDNSConfigurationError
1351
a08c1b72
SS
1352 data = {
1353 "hostname" : self.hostname,
c772f5f2 1354 "token" : self.token,
e3c70807
MT
1355 "address6" : self.get_address("ipv6", "-"),
1356 "address4" : self.get_address("ipv4", "-"),
a08c1b72
SS
1357 }
1358
a08c1b72 1359 # Send update to the server.
cb455540 1360 response = self.send_request(self.url, data=data)
a08c1b72
SS
1361
1362 # Handle success messages.
1363 if response.code == 200:
1364 return
1365
a08c1b72
SS
1366 # If we got here, some other update error happened.
1367 raise DDNSUpdateError
1368
1369
9d3cfba8
SS
1370class DDNSProviderLoopia(DDNSProtocolDynDNS2, DDNSProvider):
1371 handle = "loopia.se"
1372 name = "Loopia AB"
1373 website = "https://www.loopia.com"
1374 protocols = ("ipv4",)
1375
1376 # Information about the format of the HTTP request is to be found
1377 # here: https://support.loopia.com/wiki/About_the_DynDNS_support
1378
1379 url = "https://dns.loopia.se/XDynDNSServer/XDynDNS.php"
1380
1381
446e42af
GH
1382class DDNSProviderMyOnlinePortal(DDNSProtocolDynDNS2, DDNSProvider):
1383 handle = "myonlineportal.net"
1384 name = "myonlineportal.net"
1385 website = "https:/myonlineportal.net/"
1386
1387 # Information about the request and response can be obtained here:
1388 # https://myonlineportal.net/howto_dyndns
1389
1390 url = "https://myonlineportal.net/updateddns"
1391
1392 def prepare_request_data(self, proto):
1393 data = {
1394 "hostname" : self.hostname,
1395 "ip" : self.get_address(proto),
1396 }
1397
1398 return data
1399
1400
78c9780b 1401class DDNSProviderNamecheap(DDNSResponseParserXML, DDNSProvider):
6a11646e
MT
1402 handle = "namecheap.com"
1403 name = "Namecheap"
1404 website = "http://namecheap.com"
1405 protocols = ("ipv4",)
d1cd57eb
SS
1406
1407 # Information about the format of the HTTP request is to be found
1408 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
1409 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
1410
1411 url = "https://dynamicdns.park-your-domain.com/update"
29c8c9c6 1412 can_remove_records = False
287b2bfe 1413 supports_token_auth = False
d1cd57eb 1414
d45139f6 1415 def update_protocol(self, proto):
d1cd57eb
SS
1416 # Namecheap requires the hostname splitted into a host and domain part.
1417 host, domain = self.hostname.split(".", 1)
1418
47ea9f41
SS
1419 # Get and store curent IP address.
1420 address = self.get_address(proto)
1421
d1cd57eb 1422 data = {
47ea9f41 1423 "ip" : address,
d1cd57eb
SS
1424 "password" : self.password,
1425 "host" : host,
1426 "domain" : domain
1427 }
1428
1429 # Send update to the server.
1430 response = self.send_request(self.url, data=data)
1431
1432 # Get the full response message.
472b2408 1433 output = response.read().decode()
d1cd57eb
SS
1434
1435 # Handle success messages.
d45139f6 1436 if self.get_xml_tag_value(output, "IP") == address:
d1cd57eb
SS
1437 return
1438
1439 # Handle error codes.
78c9780b 1440 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
d1cd57eb
SS
1441
1442 if errorcode == "304156":
1443 raise DDNSAuthenticationError
1444 elif errorcode == "316153":
fb701db9 1445 raise DDNSRequestError(_("Domain not found"))
d1cd57eb 1446 elif errorcode == "316154":
fb701db9 1447 raise DDNSRequestError(_("Domain not active"))
d1cd57eb
SS
1448 elif errorcode in ("380098", "380099"):
1449 raise DDNSInternalServerError
1450
1451 # If we got here, some other update error happened.
1452 raise DDNSUpdateError
1453
1454
5d4bec40 1455class DDNSProviderNOIP(DDNSProtocolDynDNS2, DDNSProvider):
c0277eee 1456 handle = "no-ip.com"
bfdba55a
SS
1457 name = "NoIP"
1458 website = "http://www.noip.com/"
5d4bec40 1459 protocols = ("ipv4",)
f22ab085
MT
1460
1461 # Information about the format of the HTTP request is to be found
bfdba55a
SS
1462 # here: http://www.noip.com/integrate/request and
1463 # here: http://www.noip.com/integrate/response
f22ab085 1464
e00128d3 1465 url = "https://dynupdate.noip.com/nic/update"
2de06f59 1466
d45139f6
MT
1467 def prepare_request_data(self, proto):
1468 assert proto == "ipv4"
1469
2de06f59
MT
1470 data = {
1471 "hostname" : self.hostname,
d45139f6 1472 "address" : self.get_address(proto),
f22ab085
MT
1473 }
1474
88f39629 1475 return data
f22ab085
MT
1476
1477
c6e78218
SS
1478class DDNSProviderNowDNS(DDNSProtocolDynDNS2, DDNSProvider):
1479 handle = "now-dns.com"
1480 name = "NOW-DNS"
1481 website = "http://now-dns.com/"
1482 protocols = ("ipv6", "ipv4")
1483
1484 # Information about the format of the request is to be found
1485 # but only can be accessed by register an account and login
1486 # https://now-dns.com/?m=api
1487
1488 url = "https://now-dns.com/update"
1489
1490
31c95e4b
SS
1491class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2, DDNSProvider):
1492 handle = "nsupdate.info"
1493 name = "nsupdate.info"
b9221322 1494 website = "http://nsupdate.info/"
31c95e4b
SS
1495 protocols = ("ipv6", "ipv4",)
1496
1497 # Information about the format of the HTTP request can be found
b9221322 1498 # after login on the provider user interface and here:
31c95e4b
SS
1499 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
1500
7b5d382e
SS
1501 url = "https://nsupdate.info/nic/update"
1502
29c8c9c6
MT
1503 # TODO nsupdate.info can actually do this, but the functionality
1504 # has not been implemented here, yet.
1505 can_remove_records = False
1506
287b2bfe
SS
1507 supports_token_auth = True
1508
64018439
MT
1509 # After a failed update, there will be no retries
1510 # https://bugzilla.ipfire.org/show_bug.cgi?id=10603
1511 holdoff_failure_days = None
1512
31c95e4b
SS
1513 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
1514 # and for the password a so called secret.
1515 @property
1516 def username(self):
1517 return self.get("hostname")
1518
1519 @property
1520 def password(self):
9c777232 1521 return self.token or self.get("secret")
31c95e4b 1522
d45139f6 1523 def prepare_request_data(self, proto):
31c95e4b 1524 data = {
d45139f6 1525 "myip" : self.get_address(proto),
31c95e4b
SS
1526 }
1527
1528 return data
1529
1530
90663439
SS
1531class DDNSProviderOpenDNS(DDNSProtocolDynDNS2, DDNSProvider):
1532 handle = "opendns.com"
1533 name = "OpenDNS"
1534 website = "http://www.opendns.com"
1535
1536 # Detailed information about the update request and possible
1537 # response codes can be obtained from here:
1538 # https://support.opendns.com/entries/23891440
1539
1540 url = "https://updates.opendns.com/nic/update"
1541
d45139f6 1542 def prepare_request_data(self, proto):
90663439
SS
1543 data = {
1544 "hostname" : self.hostname,
d45139f6 1545 "myip" : self.get_address(proto),
90663439
SS
1546 }
1547
1548 return data
1549
1550
5d4bec40
MT
1551class DDNSProviderOVH(DDNSProtocolDynDNS2, DDNSProvider):
1552 handle = "ovh.com"
1553 name = "OVH"
1554 website = "http://www.ovh.com/"
1555 protocols = ("ipv4",)
a508bda6
SS
1556
1557 # OVH only provides very limited information about how to
1558 # update a DynDNS host. They only provide the update url
1559 # on the their german subpage.
1560 #
1561 # http://hilfe.ovh.de/DomainDynHost
1562
1563 url = "https://www.ovh.com/nic/update"
1564
d45139f6
MT
1565 def prepare_request_data(self, proto):
1566 data = DDNSProtocolDynDNS2.prepare_request_data(self, proto)
54d3efc8
MT
1567 data.update({
1568 "system" : "dyndns",
1569 })
1570
1571 return data
a508bda6
SS
1572
1573
ef33455e 1574class DDNSProviderRegfish(DDNSProvider):
6a11646e
MT
1575 handle = "regfish.com"
1576 name = "Regfish GmbH"
1577 website = "http://www.regfish.com/"
ef33455e
SS
1578
1579 # A full documentation to the providers api can be found here
1580 # but is only available in german.
1581 # https://www.regfish.de/domains/dyndns/dokumentation
1582
1583 url = "https://dyndns.regfish.de/"
29c8c9c6 1584 can_remove_records = False
287b2bfe 1585 supports_token_auth = True
ef33455e
SS
1586
1587 def update(self):
1588 data = {
1589 "fqdn" : self.hostname,
1590 }
1591
1592 # Check if we update an IPv6 address.
1593 address6 = self.get_address("ipv6")
1594 if address6:
1595 data["ipv6"] = address6
1596
1597 # Check if we update an IPv4 address.
1598 address4 = self.get_address("ipv4")
1599 if address4:
1600 data["ipv4"] = address4
1601
1602 # Raise an error if none address is given.
91aead36 1603 if "ipv6" not in data and "ipv4" not in data:
ef33455e
SS
1604 raise DDNSConfigurationError
1605
1606 # Check if a token has been set.
1607 if self.token:
1608 data["token"] = self.token
1609
1610 # Raise an error if no token and no useranem and password
1611 # are given.
1612 elif not self.username and not self.password:
fb701db9 1613 raise DDNSConfigurationError(_("No Auth details specified"))
ef33455e
SS
1614
1615 # HTTP Basic Auth is only allowed if no token is used.
1616 if self.token:
1617 # Send update to the server.
1618 response = self.send_request(self.url, data=data)
1619 else:
1620 # Send update to the server.
91aead36 1621 response = self.send_request(self.url, username=self.username, password=self.password, data=data)
ef33455e
SS
1622
1623 # Get the full response message.
472b2408 1624 output = response.read().decode()
ef33455e
SS
1625
1626 # Handle success messages.
1627 if "100" in output or "101" in output:
1628 return
1629
1630 # Handle error codes.
1631 if "401" or "402" in output:
1632 raise DDNSAuthenticationError
1633 elif "408" in output:
fb701db9 1634 raise DDNSRequestError(_("Invalid IPv4 address has been sent"))
ef33455e 1635 elif "409" in output:
fb701db9 1636 raise DDNSRequestError(_("Invalid IPv6 address has been sent"))
ef33455e 1637 elif "412" in output:
fb701db9 1638 raise DDNSRequestError(_("No valid FQDN was given"))
ef33455e
SS
1639 elif "414" in output:
1640 raise DDNSInternalServerError
1641
1642 # If we got here, some other update error happened.
1643 raise DDNSUpdateError
1644
1645
f77e6bc9
SS
1646class DDNSProviderSchokokeksDNS(DDNSProtocolDynDNS2, DDNSProvider):
1647 handle = "schokokeks.org"
1648 name = "Schokokeks"
1649 website = "http://www.schokokeks.org/"
1650 protocols = ("ipv4",)
1651
1652 # Information about the format of the request is to be found
1653 # https://wiki.schokokeks.org/DynDNS
1654 url = "https://dyndns.schokokeks.org/nic/update"
1655
1656
5d4bec40 1657class DDNSProviderSelfhost(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
1658 handle = "selfhost.de"
1659 name = "Selfhost.de"
1660 website = "http://www.selfhost.de/"
5d4bec40 1661 protocols = ("ipv4",)
f22ab085 1662
04db1862 1663 url = "https://carol.selfhost.de/nic/update"
f22ab085 1664
d45139f6
MT
1665 def prepare_request_data(self, proto):
1666 data = DDNSProtocolDynDNS2.prepare_request_data(self, proto)
04db1862
MT
1667 data.update({
1668 "hostname" : "1",
1669 })
f22ab085 1670
04db1862 1671 return data
b09b1545
SS
1672
1673
52e8a969
JS
1674class DDNSProviderServercow(DDNSProvider):
1675 handle = "servercow.de"
1676 name = "servercow.de"
1677 website = "https://servercow.de/"
1678 protocols = ("ipv4", "ipv6")
1679
1680 url = "https://www.servercow.de/dnsupdate/update.php"
1681 can_remove_records = False
287b2bfe 1682 supports_token_auth = False
52e8a969
JS
1683
1684 def update_protocol(self, proto):
1685 data = {
1686 "ipaddr" : self.get_address(proto),
1687 "hostname" : self.hostname,
1688 "username" : self.username,
1689 "pass" : self.password,
1690 }
1691
1692 # Send request to provider
1693 response = self.send_request(self.url, data=data)
1694
1695 # Read response
472b2408 1696 output = response.read().decode()
52e8a969
JS
1697
1698 # Server responds with OK if update was successful
1699 if output.startswith("OK"):
1700 return
1701
1702 # Catch any errors
1703 elif output.startswith("FAILED - Authentication failed"):
1704 raise DDNSAuthenticationError
1705
1706 # If we got here, some other update error happened
1707 raise DDNSUpdateError(output)
1708
1709
5d4bec40
MT
1710class DDNSProviderSPDNS(DDNSProtocolDynDNS2, DDNSProvider):
1711 handle = "spdns.org"
74703885
MT
1712 name = "SPDYN"
1713 website = "https://www.spdyn.de/"
b09b1545
SS
1714
1715 # Detailed information about request and response codes are provided
1716 # by the vendor. They are using almost the same mechanism and status
1717 # codes as dyndns.org so we can inherit all those stuff.
1718 #
1719 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
1720 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
1721
74703885 1722 url = "https://update.spdyn.de/nic/update"
4ec90b93 1723
287b2bfe
SS
1724 supports_token_auth = True
1725
94ab4379
SS
1726 @property
1727 def username(self):
1728 return self.get("username") or self.hostname
1729
1730 @property
1731 def password(self):
25f39b4e 1732 return self.get("password") or self.token
94ab4379 1733
4ec90b93 1734
5d4bec40
MT
1735class DDNSProviderStrato(DDNSProtocolDynDNS2, DDNSProvider):
1736 handle = "strato.com"
1737 name = "Strato AG"
1738 website = "http:/www.strato.com/"
1739 protocols = ("ipv4",)
7488825c
SS
1740
1741 # Information about the request and response can be obtained here:
1742 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
1743
1744 url = "https://dyndns.strato.com/nic/update"
1745
34af066e
SS
1746 def prepare_request_data(self, proto):
1747 data = DDNSProtocolDynDNS2.prepare_request_data(self, proto)
1748 data.update({
1749 "mx" : "NOCHG",
1750 "backupmx" : "NOCHG"
1751 })
1752
1753 return data
1754
7488825c 1755
5d4bec40
MT
1756class DDNSProviderTwoDNS(DDNSProtocolDynDNS2, DDNSProvider):
1757 handle = "twodns.de"
1758 name = "TwoDNS"
1759 website = "http://www.twodns.de"
1760 protocols = ("ipv4",)
a6183090
SS
1761
1762 # Detailed information about the request can be found here
1763 # http://twodns.de/en/faqs
1764 # http://twodns.de/en/api
1765
1766 url = "https://update.twodns.de/update"
1767
d45139f6
MT
1768 def prepare_request_data(self, proto):
1769 assert proto == "ipv4"
1770
a6183090 1771 data = {
d45139f6 1772 "ip" : self.get_address(proto),
a6183090
SS
1773 "hostname" : self.hostname
1774 }
1775
1776 return data
1777
1778
5d4bec40
MT
1779class DDNSProviderUdmedia(DDNSProtocolDynDNS2, DDNSProvider):
1780 handle = "udmedia.de"
1781 name = "Udmedia GmbH"
1782 website = "http://www.udmedia.de"
1783 protocols = ("ipv4",)
03bdd188
SS
1784
1785 # Information about the request can be found here
1786 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1787
1788 url = "https://www.udmedia.de/nic/update"
1789
1790
5d4bec40 1791class DDNSProviderVariomedia(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
1792 handle = "variomedia.de"
1793 name = "Variomedia"
1794 website = "http://www.variomedia.de/"
1795 protocols = ("ipv6", "ipv4",)
c8c7ca8f
SS
1796
1797 # Detailed information about the request can be found here
1798 # https://dyndns.variomedia.de/
1799
1800 url = "https://dyndns.variomedia.de/nic/update"
1801
d45139f6 1802 def prepare_request_data(self, proto):
c8c7ca8f
SS
1803 data = {
1804 "hostname" : self.hostname,
d45139f6 1805 "myip" : self.get_address(proto),
c8c7ca8f 1806 }
54d3efc8
MT
1807
1808 return data
98fbe467
SS
1809
1810
6b81b43c
MB
1811class DDNSProviderXLhost(DDNSProtocolDynDNS2, DDNSProvider):
1812 handle = "xlhost.de"
1813 name = "XLhost"
1814 website = "http://xlhost.de/"
1815 protocols = ("ipv4",)
1816
1817 # Information about the format of the HTTP request is to be found
1818 # here: https://xlhost.de/faq/index_html?topicId=CQA2ELIPO4SQ
1819
1820 url = "https://nsupdate.xlhost.de/"
1821
1822
f0554226 1823class DDNSProviderZoneedit(DDNSProvider):
5d4bec40
MT
1824 handle = "zoneedit.com"
1825 name = "Zoneedit"
1826 website = "http://www.zoneedit.com"
1827 protocols = ("ipv4",)
98fbe467 1828
287b2bfe
SS
1829 supports_token_auth = False
1830
98fbe467
SS
1831 # Detailed information about the request and the response codes can be
1832 # obtained here:
1833 # http://www.zoneedit.com/doc/api/other.html
1834 # http://www.zoneedit.com/faq.html
1835
1836 url = "https://dynamic.zoneedit.com/auth/dynamic.html"
1837
d45139f6 1838 def update_protocol(self, proto):
98fbe467 1839 data = {
d45139f6 1840 "dnsto" : self.get_address(proto),
98fbe467
SS
1841 "host" : self.hostname
1842 }
1843
1844 # Send update to the server.
91aead36 1845 response = self.send_request(self.url, username=self.username, password=self.password, data=data)
98fbe467
SS
1846
1847 # Get the full response message.
472b2408 1848 output = response.read().decode()
98fbe467
SS
1849
1850 # Handle success messages.
1851 if output.startswith("<SUCCESS"):
1852 return
1853
1854 # Handle error codes.
1855 if output.startswith("invalid login"):
1856 raise DDNSAuthenticationError
1857 elif output.startswith("<ERROR CODE=\"704\""):
fb701db9 1858 raise DDNSRequestError(_("No valid FQDN was given"))
98fbe467 1859 elif output.startswith("<ERROR CODE=\"702\""):
fb701db9 1860 raise DDNSRequestError(_("Too frequent update requests have been sent"))
98fbe467
SS
1861
1862 # If we got here, some other update error happened.
1863 raise DDNSUpdateError
e53d3225
SS
1864
1865
d1cb1b54
MG
1866class DDNSProviderDNSmadeEasy(DDNSProvider):
1867 handle = "dnsmadeeasy.com"
1868 name = "DNSmadeEasy.com"
1869 website = "http://www.dnsmadeeasy.com/"
1870 protocols = ("ipv4",)
1871
1872 # DNS Made Easy Nameserver Provider also offering Dynamic DNS
1873 # Documentation can be found here:
1874 # http://www.dnsmadeeasy.com/dynamic-dns/
1875
1876 url = "https://cp.dnsmadeeasy.com/servlet/updateip?"
1877 can_remove_records = False
287b2bfe 1878 supports_token_auth = False
d1cb1b54
MG
1879
1880 def update_protocol(self, proto):
1881 data = {
1882 "ip" : self.get_address(proto),
1883 "id" : self.hostname,
1884 "username" : self.username,
1885 "password" : self.password,
1886 }
1887
1888 # Send update to the server.
1889 response = self.send_request(self.url, data=data)
1890
1891 # Get the full response message.
472b2408 1892 output = response.read().decode()
d1cb1b54
MG
1893
1894 # Handle success messages.
1895 if output.startswith("success") or output.startswith("error-record-ip-same"):
1896 return
1897
1898 # Handle error codes.
1899 if output.startswith("error-auth-suspend"):
fb701db9 1900 raise DDNSRequestError(_("Account has been suspended"))
d1cb1b54
MG
1901
1902 elif output.startswith("error-auth-voided"):
fb701db9 1903 raise DDNSRequestError(_("Account has been revoked"))
d1cb1b54
MG
1904
1905 elif output.startswith("error-record-invalid"):
fb701db9 1906 raise DDNSRequestError(_("Specified host does not exist"))
d1cb1b54
MG
1907
1908 elif output.startswith("error-auth"):
1909 raise DDNSAuthenticationError
1910
1911 # If we got here, some other update error happened.
1912 raise DDNSUpdateError(_("Server response: %s") % output)
1913
1914
e53d3225
SS
1915class DDNSProviderZZZZ(DDNSProvider):
1916 handle = "zzzz.io"
1917 name = "zzzz"
1918 website = "https://zzzz.io"
fbdff678 1919 protocols = ("ipv6", "ipv4",)
e53d3225
SS
1920
1921 # Detailed information about the update request can be found here:
1922 # https://zzzz.io/faq/
1923
1924 # Details about the possible response codes have been provided in the bugtracker:
1925 # https://bugzilla.ipfire.org/show_bug.cgi?id=10584#c2
1926
1927 url = "https://zzzz.io/api/v1/update"
29c8c9c6 1928 can_remove_records = False
287b2bfe 1929 supports_token_auth = True
e53d3225 1930
d45139f6 1931 def update_protocol(self, proto):
e53d3225 1932 data = {
d45139f6 1933 "ip" : self.get_address(proto),
e53d3225
SS
1934 "token" : self.token,
1935 }
1936
fbdff678
MT
1937 if proto == "ipv6":
1938 data["type"] = "aaaa"
1939
e53d3225
SS
1940 # zzzz uses the host from the full hostname as part
1941 # of the update url.
1942 host, domain = self.hostname.split(".", 1)
1943
1944 # Add host value to the update url.
1945 url = "%s/%s" % (self.url, host)
1946
1947 # Send update to the server.
1948 try:
1949 response = self.send_request(url, data=data)
1950
1951 # Handle error codes.
ff43fa70
MT
1952 except DDNSNotFound:
1953 raise DDNSRequestError(_("Invalid hostname specified"))
e53d3225
SS
1954
1955 # Handle success messages.
1956 if response.code == 200:
1957 return
1958
1959 # If we got here, some other update error happened.
1960 raise DDNSUpdateError