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