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