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