]> git.ipfire.org Git - people/stevee/ddns.git/blob - src/ddns/providers.py
a1ca3f3d58a1a50480316cd1240b581fbe01d824
[people/stevee/ddns.git] / src / ddns / providers.py
1 #!/usr/bin/python3
2 ###############################################################################
3 # #
4 # ddns - A dynamic DNS client for IPFire #
5 # Copyright (C) 2012-2017 IPFire development team #
6 # #
7 # This program is free software: you can redistribute it and/or modify #
8 # it under the terms of the GNU General Public License as published by #
9 # the Free Software Foundation, either version 3 of the License, or #
10 # (at your option) any later version. #
11 # #
12 # This program is distributed in the hope that it will be useful, #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15 # GNU General Public License for more details. #
16 # #
17 # You should have received a copy of the GNU General Public License #
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
19 # #
20 ###############################################################################
21
22 import datetime
23 import logging
24 import os
25 import subprocess
26 import urllib.request
27 import urllib.error
28 import urllib.parse
29 import xml.dom.minidom
30
31 from .i18n import _
32
33 # Import all possible exception types.
34 from .errors import *
35
36 logger = logging.getLogger("ddns.providers")
37 logger.propagate = 1
38
39 _providers = {}
40
41 def get():
42 """
43 Returns a dict with all automatically registered providers.
44 """
45 return _providers.copy()
46
47 class DDNSProvider(object):
48 # A short string that uniquely identifies
49 # this provider.
50 handle = None
51
52 # The full name of the provider.
53 name = None
54
55 # A weburl to the homepage of the provider.
56 # (Where to register a new account?)
57 website = None
58
59 # A list of supported protocols.
60 protocols = ("ipv6", "ipv4")
61
62 DEFAULT_SETTINGS = {}
63
64 # holdoff time - Number of days no update is performed unless
65 # the IP address has changed.
66 holdoff_days = 30
67
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
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
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
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
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
104 def __repr__(self):
105 return "<DDNS Provider %s (%s)>" % (self.name, self.handle)
106
107 def __cmp__(self, other):
108 return (lambda a, b: (a > b)-(a < b))(self.hostname, other.hostname)
109
110 @property
111 def db(self):
112 return self.core.db
113
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
141 @property
142 def token(self):
143 """
144 Fast access to the token.
145 """
146 return self.get("token")
147
148 def __call__(self, force=False):
149 if force:
150 logger.debug(_("Updating %s forced") % self.hostname)
151
152 # Do nothing if the last update has failed or no update is required
153 elif self.has_failure or not self.requires_update:
154 return
155
156 # Execute the update.
157 try:
158 self.update()
159
160 # 1) Catch network errors early, because we do not want to log
161 # them to the database. They are usually temporary and caused
162 # by the client side, so that we will retry quickly.
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):
167 raise
168
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
174
175 logger.info(_("Dynamic DNS update for %(hostname)s (%(provider)s) successful") %
176 {"hostname": self.hostname, "provider": self.name})
177 self.core.db.log_success(self.hostname)
178
179 def update(self):
180 for protocol in self.protocols:
181 if self.have_address(protocol):
182 self.update_protocol(protocol)
183 elif self.can_remove_records:
184 self.remove_protocol(protocol)
185
186 def update_protocol(self, proto):
187 raise NotImplementedError
188
189 def remove_protocol(self, proto):
190 if not self.can_remove_records:
191 raise RuntimeError("can_remove_records is enabled, but remove_protocol() not implemented")
192
193 raise NotImplementedError
194
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):
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})
201
202 return True
203
204 # If the holdoff time has expired, an update is required, too
205 if self.holdoff_time_expired():
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})
208
209 return True
210
211 # Otherwise, we don't need to perform an update
212 logger.debug(_("No update required for %(hostname)s (%(provider)s)") %
213 {"hostname": self.hostname, "provider": self.name})
214
215 return False
216
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
229 # If there is no holdoff time, we won't update ever again.
230 if self.holdoff_failure_days is None:
231 logger.warning(_("An update has not been performed because earlier updates failed for %s") % self.hostname)
232 logger.warning(_("There will be no retries"))
233
234 return True
235
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
244 logger.warning(_("An update has not been performed because earlier updates failed for %s") % self.hostname)
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
258 def ip_address_changed(self, protos):
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)
266 current_address = self.get_address(proto)
267
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...
277 continue
278
279 if not current_address in addresses:
280 return True
281
282 return False
283
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
294 last_update = self.db.last_update(self.hostname, status="success")
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
300
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:
310 logger.debug("Updates for %s are held off until %s" %
311 (self.hostname, holdoff_end))
312 return False
313
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
321 def get_address(self, proto, default=None):
322 """
323 Proxy method to get the current IP address.
324 """
325 return self.core.system.get_address(proto) or default
326
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
339
340 class 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
349 # http://dyn.com/support/developers/api/perform-update/
350 # http://dyn.com/support/developers/api/return-codes/
351
352 # The DynDNS protocol version 2 does not allow to remove records
353 can_remove_records = False
354
355 def prepare_request_data(self, proto):
356 data = {
357 "hostname" : self.hostname,
358 "myip" : self.get_address(proto),
359 }
360
361 return data
362
363 def update_protocol(self, proto):
364 data = self.prepare_request_data(proto)
365
366 return self.send_request(data)
367
368 def send_request(self, data):
369 # Send update to the server.
370 response = DDNSProvider.send_request(self, self.url, data=data, username=self.username, password=self.password)
371
372 # Get the full response message.
373 output = response.read().decode()
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
382 elif output == "abuse":
383 raise DDNSAbuseError
384 elif output == "notfqdn":
385 raise DDNSRequestError(_("No valid FQDN was given"))
386 elif output == "nohost":
387 raise DDNSRequestError(_("Specified host does not exist"))
388 elif output == "911":
389 raise DDNSInternalServerError
390 elif output == "dnserr":
391 raise DDNSInternalServerError(_("DNS error encountered"))
392 elif output == "badagent":
393 raise DDNSBlockedError
394 elif output == "badip":
395 raise DDNSBlockedError
396
397 # If we got here, some other update error happened.
398 raise DDNSUpdateError(_("Server response: %s") % output)
399
400
401 class 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.
407 """
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
430 class DDNSProviderAllInkl(DDNSProvider):
431 handle = "all-inkl.com"
432 name = "All-inkl.com"
433 website = "http://all-inkl.com/"
434 protocols = ("ipv4",)
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"
442 can_remove_records = False
443
444 def update(self):
445 # There is no additional data required so we directly can
446 # send our request.
447 response = self.send_request(self.url, username=self.username, password=self.password)
448
449 # Get the full response message.
450 output = response.read().decode()
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
460 class DDNSProviderBindNsupdate(DDNSProvider):
461 handle = "nsupdate"
462 name = "BIND nsupdate utility"
463 website = "http://en.wikipedia.org/wiki/Nsupdate"
464
465 DEFAULT_TTL = 60
466
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
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
488 p = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
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
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
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
542 class 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:
562 response = self.send_request(self.url, username=self.username, password=self.password, data=data)
563
564 # Handle error codes.
565 except urllib.error.HTTPError as e:
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
579 class 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
606 class 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 # Get status information from the header.
646 output = response.getheader('ddnss-response')
647
648 # Handle success messages.
649 if output == "good" or output == "nochg":
650 return
651
652 # Handle error codes.
653 if output == "badauth":
654 raise DDNSAuthenticationError
655 elif output == "notfqdn":
656 raise DDNSRequestError(_("No valid FQDN was given"))
657 elif output == "nohost":
658 raise DDNSRequestError(_("Specified host does not exist"))
659 elif output == "911":
660 raise DDNSInternalServerError
661 elif output == "dnserr":
662 raise DDNSInternalServerError(_("DNS error encountered"))
663 elif output == "disabled":
664 raise DDNSRequestError(_("Account disabled or locked"))
665
666 # If we got here, some other update error happened.
667 raise DDNSUpdateError
668
669
670 class DDNSProviderDHS(DDNSProvider):
671 handle = "dhs.org"
672 name = "DHS International"
673 website = "http://dhs.org/"
674 protocols = ("ipv4",)
675
676 # No information about the used update api provided on webpage,
677 # grabed from source code of ez-ipudate.
678
679 url = "http://members.dhs.org/nic/hosts"
680 can_remove_records = False
681
682 def update_protocol(self, proto):
683 data = {
684 "domain" : self.hostname,
685 "ip" : self.get_address(proto),
686 "hostcmd" : "edit",
687 "hostcmdstage" : "2",
688 "type" : "4",
689 }
690
691 # Send update to the server.
692 response = self.send_request(self.url, username=self.username, password=self.password, data=data)
693
694 # Handle success messages.
695 if response.code == 200:
696 return
697
698 # If we got here, some other update error happened.
699 raise DDNSUpdateError
700
701
702 class DDNSProviderDNSpark(DDNSProvider):
703 handle = "dnspark.com"
704 name = "DNS Park"
705 website = "http://dnspark.com/"
706 protocols = ("ipv4",)
707
708 # Informations to the used api can be found here:
709 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
710
711 url = "https://control.dnspark.com/api/dynamic/update.php"
712 can_remove_records = False
713
714 def update_protocol(self, proto):
715 data = {
716 "domain" : self.hostname,
717 "ip" : self.get_address(proto),
718 }
719
720 # Send update to the server.
721 response = self.send_request(self.url, username=self.username, password=self.password, data=data)
722
723 # Get the full response message.
724 output = response.read().decode()
725
726 # Handle success messages.
727 if output.startswith("ok") or output.startswith("nochange"):
728 return
729
730 # Handle error codes.
731 if output == "unauth":
732 raise DDNSAuthenticationError
733 elif output == "abuse":
734 raise DDNSAbuseError
735 elif output == "blocked":
736 raise DDNSBlockedError
737 elif output == "nofqdn":
738 raise DDNSRequestError(_("No valid FQDN was given"))
739 elif output == "nohost":
740 raise DDNSRequestError(_("Invalid hostname specified"))
741 elif output == "notdyn":
742 raise DDNSRequestError(_("Hostname not marked as a dynamic host"))
743 elif output == "invalid":
744 raise DDNSRequestError(_("Invalid IP address has been sent"))
745
746 # If we got here, some other update error happened.
747 raise DDNSUpdateError
748
749
750 class DDNSProviderDtDNS(DDNSProvider):
751 handle = "dtdns.com"
752 name = "DtDNS"
753 website = "http://dtdns.com/"
754 protocols = ("ipv4",)
755
756 # Information about the format of the HTTPS request is to be found
757 # http://www.dtdns.com/dtsite/updatespec
758
759 url = "https://www.dtdns.com/api/autodns.cfm"
760 can_remove_records = False
761
762 def update_protocol(self, proto):
763 data = {
764 "ip" : self.get_address(proto),
765 "id" : self.hostname,
766 "pw" : self.password
767 }
768
769 # Send update to the server.
770 response = self.send_request(self.url, data=data)
771
772 # Get the full response message.
773 output = response.read().decode()
774
775 # Remove all leading and trailing whitespace.
776 output = output.strip()
777
778 # Handle success messages.
779 if "now points to" in output:
780 return
781
782 # Handle error codes.
783 if output == "No hostname to update was supplied.":
784 raise DDNSRequestError(_("No hostname specified"))
785
786 elif output == "The hostname you supplied is not valid.":
787 raise DDNSRequestError(_("Invalid hostname specified"))
788
789 elif output == "The password you supplied is not valid.":
790 raise DDNSAuthenticationError
791
792 elif output == "Administration has disabled this account.":
793 raise DDNSRequestError(_("Account has been disabled"))
794
795 elif output == "Illegal character in IP.":
796 raise DDNSRequestError(_("Invalid IP address has been sent"))
797
798 elif output == "Too many failed requests.":
799 raise DDNSRequestError(_("Too many failed requests"))
800
801 # If we got here, some other update error happened.
802 raise DDNSUpdateError
803
804
805 class DDNSProviderDuckDNS(DDNSProvider):
806 handle = "duckdns.org"
807 name = "Duck DNS"
808 website = "http://www.duckdns.org/"
809 protocols = ("ipv6", "ipv4",)
810
811 # Information about the format of the request is to be found
812 # https://www.duckdns.org/spec.jsp
813
814 url = "https://www.duckdns.org/update"
815 can_remove_records = False
816
817 def update(self):
818 # Raise an error if no auth details are given.
819 if not self.token:
820 raise DDNSConfigurationError
821
822 data = {
823 "domains" : self.hostname,
824 "token" : self.token,
825 }
826
827 # Check if we update an IPv4 address.
828 address4 = self.get_address("ipv4")
829 if address4:
830 data["ip"] = address4
831
832 # Check if we update an IPv6 address.
833 address6 = self.get_address("ipv6")
834 if address6:
835 data["ipv6"] = address6
836
837 # Raise an error if no address is given.
838 if "ip" not in data and "ipv6" not in data:
839 raise DDNSConfigurationError
840
841 # Send update to the server.
842 response = self.send_request(self.url, data=data)
843
844 # Get the full response message.
845 output = response.read().decode()
846
847 # Remove all leading and trailing whitespace.
848 output = output.strip()
849
850 # Handle success messages.
851 if output == "OK":
852 return
853
854 # The provider does not give detailed information
855 # if the update fails. Only a "KO" will be sent back.
856 if output == "KO":
857 raise DDNSUpdateError
858
859 # If we got here, some other update error happened.
860 raise DDNSUpdateError
861
862
863 class DDNSProviderDyFi(DDNSProtocolDynDNS2, DDNSProvider):
864 handle = "dy.fi"
865 name = "dy.fi"
866 website = "https://www.dy.fi/"
867 protocols = ("ipv4",)
868
869 # Information about the format of the request is to be found
870 # https://www.dy.fi/page/clients?lang=en
871 # https://www.dy.fi/page/specification?lang=en
872
873 url = "https://www.dy.fi/nic/update"
874
875 # Please only send automatic updates when your IP address changes,
876 # or once per 5 to 6 days to refresh the address mapping (they will
877 # expire if not refreshed within 7 days).
878 holdoff_days = 6
879
880
881 class DDNSProviderDynDNS(DDNSProtocolDynDNS2, DDNSProvider):
882 handle = "dyndns.org"
883 name = "Dyn"
884 website = "http://dyn.com/dns/"
885 protocols = ("ipv4",)
886
887 # Information about the format of the request is to be found
888 # http://http://dyn.com/support/developers/api/perform-update/
889 # http://dyn.com/support/developers/api/return-codes/
890
891 url = "https://members.dyndns.org/nic/update"
892
893
894 class DDNSProviderDomainOffensive(DDNSProtocolDynDNS2, DDNSProvider):
895 handle = "do.de"
896 name = "Domain-Offensive"
897 website = "https://www.do.de/"
898 protocols = ("ipv6", "ipv4")
899
900 # Detailed information about the request and response codes
901 # are available on the providers webpage.
902 # https://www.do.de/wiki/FlexDNS_-_Entwickler
903
904 url = "https://ddns.do.de/"
905
906 class DDNSProviderDynUp(DDNSProvider):
907 handle = "dynup.de"
908 name = "DynUp.DE"
909 website = "http://dynup.de/"
910 protocols = ("ipv4",)
911
912 # Information about the format of the HTTPS request is to be found
913 # https://dyndnsfree.de/user/hilfe.php
914
915 url = "https://dynup.de/dyn.php"
916 can_remove_records = False
917
918 def update_protocol(self, proto):
919 data = {
920 "username" : self.username,
921 "password" : self.password,
922 "hostname" : self.hostname,
923 "print" : '1',
924 }
925
926 # Send update to the server.
927 response = self.send_request(self.url, data=data)
928
929 # Get the full response message.
930 output = response.read().decode()
931
932 # Remove all leading and trailing whitespace.
933 output = output.strip()
934
935 # Handle success messages.
936 if output.startswith("I:OK"):
937 return
938
939 # If we got here, some other update error happened.
940 raise DDNSUpdateError
941
942
943 class DDNSProviderDynU(DDNSProtocolDynDNS2, DDNSProvider):
944 handle = "dynu.com"
945 name = "Dynu"
946 website = "http://dynu.com/"
947 protocols = ("ipv6", "ipv4",)
948
949 # Detailed information about the request and response codes
950 # are available on the providers webpage.
951 # http://dynu.com/Default.aspx?page=dnsapi
952
953 url = "https://api.dynu.com/nic/update"
954
955 # DynU sends the IPv6 and IPv4 address in one request
956
957 def update(self):
958 data = DDNSProtocolDynDNS2.prepare_request_data(self, "ipv4")
959
960 # This one supports IPv6
961 myipv6 = self.get_address("ipv6")
962
963 # Add update information if we have an IPv6 address.
964 if myipv6:
965 data["myipv6"] = myipv6
966
967 self.send_request(data)
968
969
970 class DDNSProviderEasyDNS(DDNSProvider):
971 handle = "easydns.com"
972 name = "EasyDNS"
973 website = "http://www.easydns.com/"
974 protocols = ("ipv4",)
975
976 # Detailed information about the request and response codes
977 # (API 1.3) are available on the providers webpage.
978 # https://fusion.easydns.com/index.php?/Knowledgebase/Article/View/102/7/dynamic-dns
979
980 url = "http://api.cp.easydns.com/dyn/tomato.php"
981
982 def update_protocol(self, proto):
983 data = {
984 "myip" : self.get_address(proto, "-"),
985 "hostname" : self.hostname,
986 }
987
988 # Send update to the server.
989 response = self.send_request(self.url, data=data, username=self.username, password=self.password)
990
991 # Get the full response message.
992 output = response.read().decode()
993
994 # Remove all leading and trailing whitespace.
995 output = output.strip()
996
997 # Handle success messages.
998 if output.startswith("NOERROR"):
999 return
1000
1001 # Handle error codes.
1002 if output.startswith("NOACCESS"):
1003 raise DDNSAuthenticationError
1004
1005 elif output.startswith("NOSERVICE"):
1006 raise DDNSRequestError(_("Dynamic DNS is not turned on for this domain"))
1007
1008 elif output.startswith("ILLEGAL INPUT"):
1009 raise DDNSRequestError(_("Invalid data has been sent"))
1010
1011 elif output.startswith("TOOSOON"):
1012 raise DDNSRequestError(_("Too frequent update requests have been sent"))
1013
1014 # If we got here, some other update error happened.
1015 raise DDNSUpdateError
1016
1017
1018 class DDNSProviderDomopoli(DDNSProtocolDynDNS2, DDNSProvider):
1019 handle = "domopoli.de"
1020 name = "domopoli.de"
1021 website = "http://domopoli.de/"
1022 protocols = ("ipv4",)
1023
1024 # https://www.domopoli.de/?page=howto#DynDns_start
1025
1026 url = "http://dyndns.domopoli.de/nic/update"
1027
1028
1029 class DDNSProviderDynsNet(DDNSProvider):
1030 handle = "dyns.net"
1031 name = "DyNS"
1032 website = "http://www.dyns.net/"
1033 protocols = ("ipv4",)
1034 can_remove_records = False
1035
1036 # There is very detailed informatio about how to send the update request and
1037 # the possible response codes. (Currently we are using the v1.1 proto)
1038 # http://www.dyns.net/documentation/technical/protocol/
1039
1040 url = "http://www.dyns.net/postscript011.php"
1041
1042 def update_protocol(self, proto):
1043 data = {
1044 "ip" : self.get_address(proto),
1045 "host" : self.hostname,
1046 "username" : self.username,
1047 "password" : self.password,
1048 }
1049
1050 # Send update to the server.
1051 response = self.send_request(self.url, data=data)
1052
1053 # Get the full response message.
1054 output = response.read().decode()
1055
1056 # Handle success messages.
1057 if output.startswith("200"):
1058 return
1059
1060 # Handle error codes.
1061 if output.startswith("400"):
1062 raise DDNSRequestError(_("Malformed request has been sent"))
1063 elif output.startswith("401"):
1064 raise DDNSAuthenticationError
1065 elif output.startswith("402"):
1066 raise DDNSRequestError(_("Too frequent update requests have been sent"))
1067 elif output.startswith("403"):
1068 raise DDNSInternalServerError
1069
1070 # If we got here, some other update error happened.
1071 raise DDNSUpdateError(_("Server response: %s") % output)
1072
1073
1074 class DDNSProviderEnomCom(DDNSResponseParserXML, DDNSProvider):
1075 handle = "enom.com"
1076 name = "eNom Inc."
1077 website = "http://www.enom.com/"
1078 protocols = ("ipv4",)
1079
1080 # There are very detailed information about how to send an update request and
1081 # the respone codes.
1082 # http://www.enom.com/APICommandCatalog/
1083
1084 url = "https://dynamic.name-services.com/interface.asp"
1085 can_remove_records = False
1086
1087 def update_protocol(self, proto):
1088 data = {
1089 "command" : "setdnshost",
1090 "responsetype" : "xml",
1091 "address" : self.get_address(proto),
1092 "domainpassword" : self.password,
1093 "zone" : self.hostname
1094 }
1095
1096 # Send update to the server.
1097 response = self.send_request(self.url, data=data)
1098
1099 # Get the full response message.
1100 output = response.read().decode()
1101
1102 # Handle success messages.
1103 if self.get_xml_tag_value(output, "ErrCount") == "0":
1104 return
1105
1106 # Handle error codes.
1107 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
1108
1109 if errorcode == "304155":
1110 raise DDNSAuthenticationError
1111 elif errorcode == "304153":
1112 raise DDNSRequestError(_("Domain not found"))
1113
1114 # If we got here, some other update error happened.
1115 raise DDNSUpdateError
1116
1117
1118 class DDNSProviderEntryDNS(DDNSProvider):
1119 handle = "entrydns.net"
1120 name = "EntryDNS"
1121 website = "http://entrydns.net/"
1122 protocols = ("ipv4",)
1123
1124 # Some very tiny details about their so called "Simple API" can be found
1125 # here: https://entrydns.net/help
1126 url = "https://entrydns.net/records/modify"
1127 can_remove_records = False
1128
1129 def update_protocol(self, proto):
1130 data = {
1131 "ip" : self.get_address(proto),
1132 }
1133
1134 # Add auth token to the update url.
1135 url = "%s/%s" % (self.url, self.token)
1136
1137 # Send update to the server.
1138 try:
1139 response = self.send_request(url, data=data)
1140
1141 # Handle error codes
1142 except urllib.error.HTTPError as e:
1143 if e.code == 404:
1144 raise DDNSAuthenticationError
1145
1146 elif e.code == 422:
1147 raise DDNSRequestError(_("An invalid IP address was submitted"))
1148
1149 raise
1150
1151 # Handle success messages.
1152 if response.code == 200:
1153 return
1154
1155 # If we got here, some other update error happened.
1156 raise DDNSUpdateError
1157
1158
1159 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider):
1160 handle = "freedns.afraid.org"
1161 name = "freedns.afraid.org"
1162 website = "http://freedns.afraid.org/"
1163
1164 # No information about the request or response could be found on the vendor
1165 # page. All used values have been collected by testing.
1166 url = "https://freedns.afraid.org/dynamic/update.php"
1167 can_remove_records = False
1168
1169 def update_protocol(self, proto):
1170 data = {
1171 "address" : self.get_address(proto),
1172 }
1173
1174 # Add auth token to the update url.
1175 url = "%s?%s" % (self.url, self.token)
1176
1177 # Send update to the server.
1178 response = self.send_request(url, data=data)
1179
1180 # Get the full response message.
1181 output = response.read().decode()
1182
1183 # Handle success messages.
1184 if output.startswith("Updated") or "has not changed" in output:
1185 return
1186
1187 # Handle error codes.
1188 if output == "ERROR: Unable to locate this record":
1189 raise DDNSAuthenticationError
1190 elif "is an invalid IP address" in output:
1191 raise DDNSRequestError(_("Invalid IP address has been sent"))
1192
1193 # If we got here, some other update error happened.
1194 raise DDNSUpdateError
1195
1196
1197 class DDNSProviderItsdns(DDNSProtocolDynDNS2, DDNSProvider):
1198 handle = "inwx.com"
1199 name = "INWX"
1200 website = "https://www.inwx.com"
1201 protocols = ("ipv6", "ipv4")
1202
1203 # Information about the format of the HTTP request is to be found
1204 # here: https://www.inwx.com/en/nameserver2/dyndns (requires login)
1205 # Notice: The URL is the same for: inwx.com|de|at|ch|es
1206
1207 url = "https://dyndns.inwx.com/nic/update"
1208
1209
1210 class DDNSProviderItsdns(DDNSProtocolDynDNS2, DDNSProvider):
1211 handle = "itsdns.de"
1212 name = "it's DNS"
1213 website = "http://www.itsdns.de/"
1214 protocols = ("ipv6", "ipv4")
1215
1216 # Information about the format of the HTTP request is to be found
1217 # here: https://www.itsdns.de/dynupdatehelp.htm
1218
1219 url = "https://www.itsdns.de/update.php"
1220
1221
1222 class DDNSProviderJoker(DDNSProtocolDynDNS2, DDNSProvider):
1223 handle = "joker.com"
1224 name = "Joker.com Dynamic DNS"
1225 website = "https://joker.com/"
1226 protocols = ("ipv4",)
1227
1228 # Information about the request can be found here:
1229 # https://joker.com/faq/content/11/427/en/what-is-dynamic-dns-dyndns.html
1230 # Using DynDNS V2 protocol over HTTPS here
1231
1232 url = "https://svc.joker.com/nic/update"
1233
1234
1235 class DDNSProviderKEYSYSTEMS(DDNSProvider):
1236 handle = "key-systems.net"
1237 name = "dynamicdns.key-systems.net"
1238 website = "https://domaindiscount24.com/"
1239 protocols = ("ipv4",)
1240
1241 # There are only information provided by the domaindiscount24 how to
1242 # perform an update with HTTP APIs
1243 # https://www.domaindiscount24.com/faq/dynamic-dns
1244 # examples: https://dynamicdns.key-systems.net/update.php?hostname=hostname&password=password&ip=auto
1245 # https://dynamicdns.key-systems.net/update.php?hostname=hostname&password=password&ip=213.x.x.x&mx=213.x.x.x
1246
1247 url = "https://dynamicdns.key-systems.net/update.php"
1248 can_remove_records = False
1249
1250 def update_protocol(self, proto):
1251 address = self.get_address(proto)
1252 data = {
1253 "hostname" : self.hostname,
1254 "password" : self.password,
1255 "ip" : address,
1256 }
1257
1258 # Send update to the server.
1259 response = self.send_request(self.url, data=data)
1260
1261 # Get the full response message.
1262 output = response.read().decode()
1263
1264 # Handle success messages.
1265 if "code = 200" in output:
1266 return
1267
1268 # Handle error messages.
1269 if "abuse prevention triggered" in output:
1270 raise DDNSAbuseError
1271 elif "invalid password" in output:
1272 raise DDNSAuthenticationError
1273 elif "Authorization failed" in output:
1274 raise DDNSRequestError(_("Invalid hostname specified"))
1275
1276 # If we got here, some other update error happened.
1277 raise DDNSUpdateError
1278
1279
1280 class DDNSProviderGoogle(DDNSProtocolDynDNS2, DDNSProvider):
1281 handle = "domains.google.com"
1282 name = "Google Domains"
1283 website = "https://domains.google.com/"
1284 protocols = ("ipv4",)
1285
1286 # Information about the format of the HTTP request is to be found
1287 # here: https://support.google.com/domains/answer/6147083?hl=en
1288
1289 url = "https://domains.google.com/nic/update"
1290
1291
1292 class DDNSProviderLightningWireLabs(DDNSProvider):
1293 handle = "dns.lightningwirelabs.com"
1294 name = "Lightning Wire Labs DNS Service"
1295 website = "https://dns.lightningwirelabs.com/"
1296
1297 # Information about the format of the HTTPS request is to be found
1298 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
1299
1300 url = "https://dns.lightningwirelabs.com/update"
1301
1302 def update(self):
1303 # Raise an error if no auth details are given.
1304 if not self.token:
1305 raise DDNSConfigurationError
1306
1307 data = {
1308 "hostname" : self.hostname,
1309 "token" : self.token,
1310 "address6" : self.get_address("ipv6", "-"),
1311 "address4" : self.get_address("ipv4", "-"),
1312 }
1313
1314 # Send update to the server.
1315 response = self.send_request(self.url, data=data)
1316
1317 # Handle success messages.
1318 if response.code == 200:
1319 return
1320
1321 # If we got here, some other update error happened.
1322 raise DDNSUpdateError
1323
1324
1325 class DDNSProviderLoopia(DDNSProtocolDynDNS2, DDNSProvider):
1326 handle = "loopia.se"
1327 name = "Loopia AB"
1328 website = "https://www.loopia.com"
1329 protocols = ("ipv4",)
1330
1331 # Information about the format of the HTTP request is to be found
1332 # here: https://support.loopia.com/wiki/About_the_DynDNS_support
1333
1334 url = "https://dns.loopia.se/XDynDNSServer/XDynDNS.php"
1335
1336
1337 class DDNSProviderMyOnlinePortal(DDNSProtocolDynDNS2, DDNSProvider):
1338 handle = "myonlineportal.net"
1339 name = "myonlineportal.net"
1340 website = "https:/myonlineportal.net/"
1341
1342 # Information about the request and response can be obtained here:
1343 # https://myonlineportal.net/howto_dyndns
1344
1345 url = "https://myonlineportal.net/updateddns"
1346
1347 def prepare_request_data(self, proto):
1348 data = {
1349 "hostname" : self.hostname,
1350 "ip" : self.get_address(proto),
1351 }
1352
1353 return data
1354
1355
1356 class DDNSProviderNamecheap(DDNSResponseParserXML, DDNSProvider):
1357 handle = "namecheap.com"
1358 name = "Namecheap"
1359 website = "http://namecheap.com"
1360 protocols = ("ipv4",)
1361
1362 # Information about the format of the HTTP request is to be found
1363 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
1364 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
1365
1366 url = "https://dynamicdns.park-your-domain.com/update"
1367 can_remove_records = False
1368
1369 def update_protocol(self, proto):
1370 # Namecheap requires the hostname splitted into a host and domain part.
1371 host, domain = self.hostname.split(".", 1)
1372
1373 # Get and store curent IP address.
1374 address = self.get_address(proto)
1375
1376 data = {
1377 "ip" : address,
1378 "password" : self.password,
1379 "host" : host,
1380 "domain" : domain
1381 }
1382
1383 # Send update to the server.
1384 response = self.send_request(self.url, data=data)
1385
1386 # Get the full response message.
1387 output = response.read().decode()
1388
1389 # Handle success messages.
1390 if self.get_xml_tag_value(output, "IP") == address:
1391 return
1392
1393 # Handle error codes.
1394 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
1395
1396 if errorcode == "304156":
1397 raise DDNSAuthenticationError
1398 elif errorcode == "316153":
1399 raise DDNSRequestError(_("Domain not found"))
1400 elif errorcode == "316154":
1401 raise DDNSRequestError(_("Domain not active"))
1402 elif errorcode in ("380098", "380099"):
1403 raise DDNSInternalServerError
1404
1405 # If we got here, some other update error happened.
1406 raise DDNSUpdateError
1407
1408
1409 class DDNSProviderNOIP(DDNSProtocolDynDNS2, DDNSProvider):
1410 handle = "no-ip.com"
1411 name = "NoIP"
1412 website = "http://www.noip.com/"
1413 protocols = ("ipv4",)
1414
1415 # Information about the format of the HTTP request is to be found
1416 # here: http://www.noip.com/integrate/request and
1417 # here: http://www.noip.com/integrate/response
1418
1419 url = "http://dynupdate.noip.com/nic/update"
1420
1421 def prepare_request_data(self, proto):
1422 assert proto == "ipv4"
1423
1424 data = {
1425 "hostname" : self.hostname,
1426 "address" : self.get_address(proto),
1427 }
1428
1429 return data
1430
1431
1432 class DDNSProviderNowDNS(DDNSProtocolDynDNS2, DDNSProvider):
1433 handle = "now-dns.com"
1434 name = "NOW-DNS"
1435 website = "http://now-dns.com/"
1436 protocols = ("ipv6", "ipv4")
1437
1438 # Information about the format of the request is to be found
1439 # but only can be accessed by register an account and login
1440 # https://now-dns.com/?m=api
1441
1442 url = "https://now-dns.com/update"
1443
1444
1445 class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2, DDNSProvider):
1446 handle = "nsupdate.info"
1447 name = "nsupdate.info"
1448 website = "http://nsupdate.info/"
1449 protocols = ("ipv6", "ipv4",)
1450
1451 # Information about the format of the HTTP request can be found
1452 # after login on the provider user interface and here:
1453 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
1454
1455 url = "https://nsupdate.info/nic/update"
1456
1457 # TODO nsupdate.info can actually do this, but the functionality
1458 # has not been implemented here, yet.
1459 can_remove_records = False
1460
1461 # After a failed update, there will be no retries
1462 # https://bugzilla.ipfire.org/show_bug.cgi?id=10603
1463 holdoff_failure_days = None
1464
1465 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
1466 # and for the password a so called secret.
1467 @property
1468 def username(self):
1469 return self.get("hostname")
1470
1471 @property
1472 def password(self):
1473 return self.token or self.get("secret")
1474
1475 def prepare_request_data(self, proto):
1476 data = {
1477 "myip" : self.get_address(proto),
1478 }
1479
1480 return data
1481
1482
1483 class DDNSProviderOpenDNS(DDNSProtocolDynDNS2, DDNSProvider):
1484 handle = "opendns.com"
1485 name = "OpenDNS"
1486 website = "http://www.opendns.com"
1487
1488 # Detailed information about the update request and possible
1489 # response codes can be obtained from here:
1490 # https://support.opendns.com/entries/23891440
1491
1492 url = "https://updates.opendns.com/nic/update"
1493
1494 def prepare_request_data(self, proto):
1495 data = {
1496 "hostname" : self.hostname,
1497 "myip" : self.get_address(proto),
1498 }
1499
1500 return data
1501
1502
1503 class DDNSProviderOVH(DDNSProtocolDynDNS2, DDNSProvider):
1504 handle = "ovh.com"
1505 name = "OVH"
1506 website = "http://www.ovh.com/"
1507 protocols = ("ipv4",)
1508
1509 # OVH only provides very limited information about how to
1510 # update a DynDNS host. They only provide the update url
1511 # on the their german subpage.
1512 #
1513 # http://hilfe.ovh.de/DomainDynHost
1514
1515 url = "https://www.ovh.com/nic/update"
1516
1517 def prepare_request_data(self, proto):
1518 data = DDNSProtocolDynDNS2.prepare_request_data(self, proto)
1519 data.update({
1520 "system" : "dyndns",
1521 })
1522
1523 return data
1524
1525
1526 class DDNSProviderRegfish(DDNSProvider):
1527 handle = "regfish.com"
1528 name = "Regfish GmbH"
1529 website = "http://www.regfish.com/"
1530
1531 # A full documentation to the providers api can be found here
1532 # but is only available in german.
1533 # https://www.regfish.de/domains/dyndns/dokumentation
1534
1535 url = "https://dyndns.regfish.de/"
1536 can_remove_records = False
1537
1538 def update(self):
1539 data = {
1540 "fqdn" : self.hostname,
1541 }
1542
1543 # Check if we update an IPv6 address.
1544 address6 = self.get_address("ipv6")
1545 if address6:
1546 data["ipv6"] = address6
1547
1548 # Check if we update an IPv4 address.
1549 address4 = self.get_address("ipv4")
1550 if address4:
1551 data["ipv4"] = address4
1552
1553 # Raise an error if none address is given.
1554 if "ipv6" not in data and "ipv4" not in data:
1555 raise DDNSConfigurationError
1556
1557 # Check if a token has been set.
1558 if self.token:
1559 data["token"] = self.token
1560
1561 # Raise an error if no token and no useranem and password
1562 # are given.
1563 elif not self.username and not self.password:
1564 raise DDNSConfigurationError(_("No Auth details specified"))
1565
1566 # HTTP Basic Auth is only allowed if no token is used.
1567 if self.token:
1568 # Send update to the server.
1569 response = self.send_request(self.url, data=data)
1570 else:
1571 # Send update to the server.
1572 response = self.send_request(self.url, username=self.username, password=self.password, data=data)
1573
1574 # Get the full response message.
1575 output = response.read().decode()
1576
1577 # Handle success messages.
1578 if "100" in output or "101" in output:
1579 return
1580
1581 # Handle error codes.
1582 if "401" or "402" in output:
1583 raise DDNSAuthenticationError
1584 elif "408" in output:
1585 raise DDNSRequestError(_("Invalid IPv4 address has been sent"))
1586 elif "409" in output:
1587 raise DDNSRequestError(_("Invalid IPv6 address has been sent"))
1588 elif "412" in output:
1589 raise DDNSRequestError(_("No valid FQDN was given"))
1590 elif "414" in output:
1591 raise DDNSInternalServerError
1592
1593 # If we got here, some other update error happened.
1594 raise DDNSUpdateError
1595
1596
1597 class DDNSProviderSchokokeksDNS(DDNSProtocolDynDNS2, DDNSProvider):
1598 handle = "schokokeks.org"
1599 name = "Schokokeks"
1600 website = "http://www.schokokeks.org/"
1601 protocols = ("ipv4",)
1602
1603 # Information about the format of the request is to be found
1604 # https://wiki.schokokeks.org/DynDNS
1605 url = "https://dyndns.schokokeks.org/nic/update"
1606
1607
1608 class DDNSProviderSelfhost(DDNSProtocolDynDNS2, DDNSProvider):
1609 handle = "selfhost.de"
1610 name = "Selfhost.de"
1611 website = "http://www.selfhost.de/"
1612 protocols = ("ipv4",)
1613
1614 url = "https://carol.selfhost.de/nic/update"
1615
1616 def prepare_request_data(self, proto):
1617 data = DDNSProtocolDynDNS2.prepare_request_data(self, proto)
1618 data.update({
1619 "hostname" : "1",
1620 })
1621
1622 return data
1623
1624
1625 class DDNSProviderServercow(DDNSProvider):
1626 handle = "servercow.de"
1627 name = "servercow.de"
1628 website = "https://servercow.de/"
1629 protocols = ("ipv4", "ipv6")
1630
1631 url = "https://www.servercow.de/dnsupdate/update.php"
1632 can_remove_records = False
1633
1634 def update_protocol(self, proto):
1635 data = {
1636 "ipaddr" : self.get_address(proto),
1637 "hostname" : self.hostname,
1638 "username" : self.username,
1639 "pass" : self.password,
1640 }
1641
1642 # Send request to provider
1643 response = self.send_request(self.url, data=data)
1644
1645 # Read response
1646 output = response.read().decode()
1647
1648 # Server responds with OK if update was successful
1649 if output.startswith("OK"):
1650 return
1651
1652 # Catch any errors
1653 elif output.startswith("FAILED - Authentication failed"):
1654 raise DDNSAuthenticationError
1655
1656 # If we got here, some other update error happened
1657 raise DDNSUpdateError(output)
1658
1659
1660 class DDNSProviderSPDNS(DDNSProtocolDynDNS2, DDNSProvider):
1661 handle = "spdns.org"
1662 name = "SPDYN"
1663 website = "https://www.spdyn.de/"
1664
1665 # Detailed information about request and response codes are provided
1666 # by the vendor. They are using almost the same mechanism and status
1667 # codes as dyndns.org so we can inherit all those stuff.
1668 #
1669 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
1670 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
1671
1672 url = "https://update.spdyn.de/nic/update"
1673
1674 @property
1675 def username(self):
1676 return self.get("username") or self.hostname
1677
1678 @property
1679 def password(self):
1680 return self.get("password") or self.token
1681
1682
1683 class DDNSProviderStrato(DDNSProtocolDynDNS2, DDNSProvider):
1684 handle = "strato.com"
1685 name = "Strato AG"
1686 website = "http:/www.strato.com/"
1687 protocols = ("ipv4",)
1688
1689 # Information about the request and response can be obtained here:
1690 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
1691
1692 url = "https://dyndns.strato.com/nic/update"
1693
1694 def prepare_request_data(self, proto):
1695 data = DDNSProtocolDynDNS2.prepare_request_data(self, proto)
1696 data.update({
1697 "mx" : "NOCHG",
1698 "backupmx" : "NOCHG"
1699 })
1700
1701 return data
1702
1703
1704 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2, DDNSProvider):
1705 handle = "twodns.de"
1706 name = "TwoDNS"
1707 website = "http://www.twodns.de"
1708 protocols = ("ipv4",)
1709
1710 # Detailed information about the request can be found here
1711 # http://twodns.de/en/faqs
1712 # http://twodns.de/en/api
1713
1714 url = "https://update.twodns.de/update"
1715
1716 def prepare_request_data(self, proto):
1717 assert proto == "ipv4"
1718
1719 data = {
1720 "ip" : self.get_address(proto),
1721 "hostname" : self.hostname
1722 }
1723
1724 return data
1725
1726
1727 class DDNSProviderUdmedia(DDNSProtocolDynDNS2, DDNSProvider):
1728 handle = "udmedia.de"
1729 name = "Udmedia GmbH"
1730 website = "http://www.udmedia.de"
1731 protocols = ("ipv4",)
1732
1733 # Information about the request can be found here
1734 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1735
1736 url = "https://www.udmedia.de/nic/update"
1737
1738
1739 class DDNSProviderVariomedia(DDNSProtocolDynDNS2, DDNSProvider):
1740 handle = "variomedia.de"
1741 name = "Variomedia"
1742 website = "http://www.variomedia.de/"
1743 protocols = ("ipv6", "ipv4",)
1744
1745 # Detailed information about the request can be found here
1746 # https://dyndns.variomedia.de/
1747
1748 url = "https://dyndns.variomedia.de/nic/update"
1749
1750 def prepare_request_data(self, proto):
1751 data = {
1752 "hostname" : self.hostname,
1753 "myip" : self.get_address(proto),
1754 }
1755
1756 return data
1757
1758
1759 class DDNSProviderXLhost(DDNSProtocolDynDNS2, DDNSProvider):
1760 handle = "xlhost.de"
1761 name = "XLhost"
1762 website = "http://xlhost.de/"
1763 protocols = ("ipv4",)
1764
1765 # Information about the format of the HTTP request is to be found
1766 # here: https://xlhost.de/faq/index_html?topicId=CQA2ELIPO4SQ
1767
1768 url = "https://nsupdate.xlhost.de/"
1769
1770
1771 class DDNSProviderZoneedit(DDNSProvider):
1772 handle = "zoneedit.com"
1773 name = "Zoneedit"
1774 website = "http://www.zoneedit.com"
1775 protocols = ("ipv4",)
1776
1777 # Detailed information about the request and the response codes can be
1778 # obtained here:
1779 # http://www.zoneedit.com/doc/api/other.html
1780 # http://www.zoneedit.com/faq.html
1781
1782 url = "https://dynamic.zoneedit.com/auth/dynamic.html"
1783
1784 def update_protocol(self, proto):
1785 data = {
1786 "dnsto" : self.get_address(proto),
1787 "host" : self.hostname
1788 }
1789
1790 # Send update to the server.
1791 response = self.send_request(self.url, username=self.username, password=self.password, data=data)
1792
1793 # Get the full response message.
1794 output = response.read().decode()
1795
1796 # Handle success messages.
1797 if output.startswith("<SUCCESS"):
1798 return
1799
1800 # Handle error codes.
1801 if output.startswith("invalid login"):
1802 raise DDNSAuthenticationError
1803 elif output.startswith("<ERROR CODE=\"704\""):
1804 raise DDNSRequestError(_("No valid FQDN was given"))
1805 elif output.startswith("<ERROR CODE=\"702\""):
1806 raise DDNSRequestError(_("Too frequent update requests have been sent"))
1807
1808 # If we got here, some other update error happened.
1809 raise DDNSUpdateError
1810
1811
1812 class DDNSProviderDNSmadeEasy(DDNSProvider):
1813 handle = "dnsmadeeasy.com"
1814 name = "DNSmadeEasy.com"
1815 website = "http://www.dnsmadeeasy.com/"
1816 protocols = ("ipv4",)
1817
1818 # DNS Made Easy Nameserver Provider also offering Dynamic DNS
1819 # Documentation can be found here:
1820 # http://www.dnsmadeeasy.com/dynamic-dns/
1821
1822 url = "https://cp.dnsmadeeasy.com/servlet/updateip?"
1823 can_remove_records = False
1824
1825 def update_protocol(self, proto):
1826 data = {
1827 "ip" : self.get_address(proto),
1828 "id" : self.hostname,
1829 "username" : self.username,
1830 "password" : self.password,
1831 }
1832
1833 # Send update to the server.
1834 response = self.send_request(self.url, data=data)
1835
1836 # Get the full response message.
1837 output = response.read().decode()
1838
1839 # Handle success messages.
1840 if output.startswith("success") or output.startswith("error-record-ip-same"):
1841 return
1842
1843 # Handle error codes.
1844 if output.startswith("error-auth-suspend"):
1845 raise DDNSRequestError(_("Account has been suspended"))
1846
1847 elif output.startswith("error-auth-voided"):
1848 raise DDNSRequestError(_("Account has been revoked"))
1849
1850 elif output.startswith("error-record-invalid"):
1851 raise DDNSRequestError(_("Specified host does not exist"))
1852
1853 elif output.startswith("error-auth"):
1854 raise DDNSAuthenticationError
1855
1856 # If we got here, some other update error happened.
1857 raise DDNSUpdateError(_("Server response: %s") % output)
1858
1859
1860 class DDNSProviderZZZZ(DDNSProvider):
1861 handle = "zzzz.io"
1862 name = "zzzz"
1863 website = "https://zzzz.io"
1864 protocols = ("ipv6", "ipv4",)
1865
1866 # Detailed information about the update request can be found here:
1867 # https://zzzz.io/faq/
1868
1869 # Details about the possible response codes have been provided in the bugtracker:
1870 # https://bugzilla.ipfire.org/show_bug.cgi?id=10584#c2
1871
1872 url = "https://zzzz.io/api/v1/update"
1873 can_remove_records = False
1874
1875 def update_protocol(self, proto):
1876 data = {
1877 "ip" : self.get_address(proto),
1878 "token" : self.token,
1879 }
1880
1881 if proto == "ipv6":
1882 data["type"] = "aaaa"
1883
1884 # zzzz uses the host from the full hostname as part
1885 # of the update url.
1886 host, domain = self.hostname.split(".", 1)
1887
1888 # Add host value to the update url.
1889 url = "%s/%s" % (self.url, host)
1890
1891 # Send update to the server.
1892 try:
1893 response = self.send_request(url, data=data)
1894
1895 # Handle error codes.
1896 except DDNSNotFound:
1897 raise DDNSRequestError(_("Invalid hostname specified"))
1898
1899 # Handle success messages.
1900 if response.code == 200:
1901 return
1902
1903 # If we got here, some other update error happened.
1904 raise DDNSUpdateError