]> git.ipfire.org Git - ddns.git/blob - src/ddns/providers.py
661fbcc57a5aecba0d958ec05c26296b3cef0d70
[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 class DDNSProviderDynUp(DDNSProvider):
877 handle = "dynup.de"
878 name = "DynUp.DE"
879 website = "http://dynup.de/"
880 protocols = ("ipv4",)
881
882 # Information about the format of the HTTPS request is to be found
883 # https://dyndnsfree.de/user/hilfe.php
884
885 url = "https://dynup.de/dyn.php"
886 can_remove_records = False
887
888 def update_protocol(self, proto):
889 data = {
890 "username" : self.username,
891 "password" : self.password,
892 "hostname" : self.hostname,
893 "print" : '1',
894 }
895
896 # Send update to the server.
897 response = self.send_request(self.url, data=data)
898
899 # Get the full response message.
900 output = response.read()
901
902 # Remove all leading and trailing whitespace.
903 output = output.strip()
904
905 # Handle success messages.
906 if output.startswith("I:OK") :
907 return
908
909 # If we got here, some other update error happened.
910 raise DDNSUpdateError
911
912
913
914 class DDNSProviderDynU(DDNSProtocolDynDNS2, DDNSProvider):
915 handle = "dynu.com"
916 name = "Dynu"
917 website = "http://dynu.com/"
918 protocols = ("ipv6", "ipv4",)
919
920 # Detailed information about the request and response codes
921 # are available on the providers webpage.
922 # http://dynu.com/Default.aspx?page=dnsapi
923
924 url = "https://api.dynu.com/nic/update"
925
926 # DynU sends the IPv6 and IPv4 address in one request
927
928 def update(self):
929 data = DDNSProtocolDynDNS2.prepare_request_data(self, "ipv4")
930
931 # This one supports IPv6
932 myipv6 = self.get_address("ipv6")
933
934 # Add update information if we have an IPv6 address.
935 if myipv6:
936 data["myipv6"] = myipv6
937
938 self.send_request(data)
939
940
941 class DDNSProviderEasyDNS(DDNSProvider):
942 handle = "easydns.com"
943 name = "EasyDNS"
944 website = "http://www.easydns.com/"
945 protocols = ("ipv4",)
946
947 # Detailed information about the request and response codes
948 # (API 1.3) are available on the providers webpage.
949 # https://fusion.easydns.com/index.php?/Knowledgebase/Article/View/102/7/dynamic-dns
950
951 url = "http://api.cp.easydns.com/dyn/tomato.php"
952
953 def update_protocol(self, proto):
954 data = {
955 "myip" : self.get_address(proto, "-"),
956 "hostname" : self.hostname,
957 }
958
959 # Send update to the server.
960 response = self.send_request(self.url, data=data,
961 username=self.username, password=self.password)
962
963 # Get the full response message.
964 output = response.read()
965
966 # Remove all leading and trailing whitespace.
967 output = output.strip()
968
969 # Handle success messages.
970 if output.startswith("NOERROR"):
971 return
972
973 # Handle error codes.
974 if output.startswith("NOACCESS"):
975 raise DDNSAuthenticationError
976
977 elif output.startswith("NOSERVICE"):
978 raise DDNSRequestError(_("Dynamic DNS is not turned on for this domain"))
979
980 elif output.startswith("ILLEGAL INPUT"):
981 raise DDNSRequestError(_("Invalid data has been sent"))
982
983 elif output.startswith("TOOSOON"):
984 raise DDNSRequestError(_("Too frequent update requests have been sent"))
985
986 # If we got here, some other update error happened.
987 raise DDNSUpdateError
988
989
990 class DDNSProviderDomopoli(DDNSProtocolDynDNS2, DDNSProvider):
991 handle = "domopoli.de"
992 name = "domopoli.de"
993 website = "http://domopoli.de/"
994 protocols = ("ipv4",)
995
996 # https://www.domopoli.de/?page=howto#DynDns_start
997
998 url = "http://dyndns.domopoli.de/nic/update"
999
1000
1001 class DDNSProviderDynsNet(DDNSProvider):
1002 handle = "dyns.net"
1003 name = "DyNS"
1004 website = "http://www.dyns.net/"
1005 protocols = ("ipv4",)
1006 can_remove_records = False
1007
1008 # There is very detailed informatio about how to send the update request and
1009 # the possible response codes. (Currently we are using the v1.1 proto)
1010 # http://www.dyns.net/documentation/technical/protocol/
1011
1012 url = "http://www.dyns.net/postscript011.php"
1013
1014 def update_protocol(self, proto):
1015 data = {
1016 "ip" : self.get_address(proto),
1017 "host" : self.hostname,
1018 "username" : self.username,
1019 "password" : self.password,
1020 }
1021
1022 # Send update to the server.
1023 response = self.send_request(self.url, data=data)
1024
1025 # Get the full response message.
1026 output = response.read()
1027
1028 # Handle success messages.
1029 if output.startswith("200"):
1030 return
1031
1032 # Handle error codes.
1033 if output.startswith("400"):
1034 raise DDNSRequestError(_("Malformed request has been sent"))
1035 elif output.startswith("401"):
1036 raise DDNSAuthenticationError
1037 elif output.startswith("402"):
1038 raise DDNSRequestError(_("Too frequent update requests have been sent"))
1039 elif output.startswith("403"):
1040 raise DDNSInternalServerError
1041
1042 # If we got here, some other update error happened.
1043 raise DDNSUpdateError(_("Server response: %s") % output)
1044
1045
1046 class DDNSProviderEnomCom(DDNSResponseParserXML, DDNSProvider):
1047 handle = "enom.com"
1048 name = "eNom Inc."
1049 website = "http://www.enom.com/"
1050 protocols = ("ipv4",)
1051
1052 # There are very detailed information about how to send an update request and
1053 # the respone codes.
1054 # http://www.enom.com/APICommandCatalog/
1055
1056 url = "https://dynamic.name-services.com/interface.asp"
1057 can_remove_records = False
1058
1059 def update_protocol(self, proto):
1060 data = {
1061 "command" : "setdnshost",
1062 "responsetype" : "xml",
1063 "address" : self.get_address(proto),
1064 "domainpassword" : self.password,
1065 "zone" : self.hostname
1066 }
1067
1068 # Send update to the server.
1069 response = self.send_request(self.url, data=data)
1070
1071 # Get the full response message.
1072 output = response.read()
1073
1074 # Handle success messages.
1075 if self.get_xml_tag_value(output, "ErrCount") == "0":
1076 return
1077
1078 # Handle error codes.
1079 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
1080
1081 if errorcode == "304155":
1082 raise DDNSAuthenticationError
1083 elif errorcode == "304153":
1084 raise DDNSRequestError(_("Domain not found"))
1085
1086 # If we got here, some other update error happened.
1087 raise DDNSUpdateError
1088
1089
1090 class DDNSProviderEntryDNS(DDNSProvider):
1091 handle = "entrydns.net"
1092 name = "EntryDNS"
1093 website = "http://entrydns.net/"
1094 protocols = ("ipv4",)
1095
1096 # Some very tiny details about their so called "Simple API" can be found
1097 # here: https://entrydns.net/help
1098 url = "https://entrydns.net/records/modify"
1099 can_remove_records = False
1100
1101 def update_protocol(self, proto):
1102 data = {
1103 "ip" : self.get_address(proto),
1104 }
1105
1106 # Add auth token to the update url.
1107 url = "%s/%s" % (self.url, self.token)
1108
1109 # Send update to the server.
1110 try:
1111 response = self.send_request(url, data=data)
1112
1113 # Handle error codes
1114 except urllib2.HTTPError, e:
1115 if e.code == 404:
1116 raise DDNSAuthenticationError
1117
1118 elif e.code == 422:
1119 raise DDNSRequestError(_("An invalid IP address was submitted"))
1120
1121 raise
1122
1123 # Handle success messages.
1124 if response.code == 200:
1125 return
1126
1127 # If we got here, some other update error happened.
1128 raise DDNSUpdateError
1129
1130
1131 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider):
1132 handle = "freedns.afraid.org"
1133 name = "freedns.afraid.org"
1134 website = "http://freedns.afraid.org/"
1135
1136 # No information about the request or response could be found on the vendor
1137 # page. All used values have been collected by testing.
1138 url = "https://freedns.afraid.org/dynamic/update.php"
1139 can_remove_records = False
1140
1141 def update_protocol(self, proto):
1142 data = {
1143 "address" : self.get_address(proto),
1144 }
1145
1146 # Add auth token to the update url.
1147 url = "%s?%s" % (self.url, self.token)
1148
1149 # Send update to the server.
1150 response = self.send_request(url, data=data)
1151
1152 # Get the full response message.
1153 output = response.read()
1154
1155 # Handle success messages.
1156 if output.startswith("Updated") or "has not changed" in output:
1157 return
1158
1159 # Handle error codes.
1160 if output == "ERROR: Unable to locate this record":
1161 raise DDNSAuthenticationError
1162 elif "is an invalid IP address" in output:
1163 raise DDNSRequestError(_("Invalid IP address has been sent"))
1164
1165 # If we got here, some other update error happened.
1166 raise DDNSUpdateError
1167
1168
1169 class DDNSProviderItsdns(DDNSProtocolDynDNS2, DDNSProvider):
1170 handle = "inwx.com"
1171 name = "INWX"
1172 website = "https://www.inwx.com"
1173 protocols = ("ipv6", "ipv4")
1174
1175 # Information about the format of the HTTP request is to be found
1176 # here: https://www.inwx.com/en/nameserver2/dyndns (requires login)
1177 # Notice: The URL is the same for: inwx.com|de|at|ch|es
1178
1179 url = "https://dyndns.inwx.com/nic/update"
1180
1181
1182 class DDNSProviderItsdns(DDNSProtocolDynDNS2, DDNSProvider):
1183 handle = "itsdns.de"
1184 name = "it's DNS"
1185 website = "http://www.itsdns.de/"
1186 protocols = ("ipv6", "ipv4")
1187
1188 # Information about the format of the HTTP request is to be found
1189 # here: https://www.itsdns.de/dynupdatehelp.htm
1190
1191 url = "https://www.itsdns.de/update.php"
1192
1193
1194 class DDNSProviderJoker(DDNSProtocolDynDNS2, DDNSProvider):
1195 handle = "joker.com"
1196 name = "Joker.com Dynamic DNS"
1197 website = "https://joker.com/"
1198 protocols = ("ipv4",)
1199
1200 # Information about the request can be found here:
1201 # https://joker.com/faq/content/11/427/en/what-is-dynamic-dns-dyndns.html
1202 # Using DynDNS V2 protocol over HTTPS here
1203
1204 url = "https://svc.joker.com/nic/update"
1205
1206
1207 class DDNSProviderGoogle(DDNSProtocolDynDNS2, DDNSProvider):
1208 handle = "domains.google.com"
1209 name = "Google Domains"
1210 website = "https://domains.google.com/"
1211 protocols = ("ipv4",)
1212
1213 # Information about the format of the HTTP request is to be found
1214 # here: https://support.google.com/domains/answer/6147083?hl=en
1215
1216 url = "https://domains.google.com/nic/update"
1217
1218
1219 class DDNSProviderLightningWireLabs(DDNSProvider):
1220 handle = "dns.lightningwirelabs.com"
1221 name = "Lightning Wire Labs DNS Service"
1222 website = "http://dns.lightningwirelabs.com/"
1223
1224 # Information about the format of the HTTPS request is to be found
1225 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
1226
1227 url = "https://dns.lightningwirelabs.com/update"
1228
1229 def update(self):
1230 data = {
1231 "hostname" : self.hostname,
1232 "address6" : self.get_address("ipv6", "-"),
1233 "address4" : self.get_address("ipv4", "-"),
1234 }
1235
1236 # Check if a token has been set.
1237 if self.token:
1238 data["token"] = self.token
1239
1240 # Check for username and password.
1241 elif self.username and self.password:
1242 data.update({
1243 "username" : self.username,
1244 "password" : self.password,
1245 })
1246
1247 # Raise an error if no auth details are given.
1248 else:
1249 raise DDNSConfigurationError
1250
1251 # Send update to the server.
1252 response = self.send_request(self.url, data=data)
1253
1254 # Handle success messages.
1255 if response.code == 200:
1256 return
1257
1258 # If we got here, some other update error happened.
1259 raise DDNSUpdateError
1260
1261
1262 class DDNSProviderLoopia(DDNSProtocolDynDNS2, DDNSProvider):
1263 handle = "loopia.se"
1264 name = "Loopia AB"
1265 website = "https://www.loopia.com"
1266 protocols = ("ipv4",)
1267
1268 # Information about the format of the HTTP request is to be found
1269 # here: https://support.loopia.com/wiki/About_the_DynDNS_support
1270
1271 url = "https://dns.loopia.se/XDynDNSServer/XDynDNS.php"
1272
1273
1274 class DDNSProviderMyOnlinePortal(DDNSProtocolDynDNS2, DDNSProvider):
1275 handle = "myonlineportal.net"
1276 name = "myonlineportal.net"
1277 website = "https:/myonlineportal.net/"
1278
1279 # Information about the request and response can be obtained here:
1280 # https://myonlineportal.net/howto_dyndns
1281
1282 url = "https://myonlineportal.net/updateddns"
1283
1284 def prepare_request_data(self, proto):
1285 data = {
1286 "hostname" : self.hostname,
1287 "ip" : self.get_address(proto),
1288 }
1289
1290 return data
1291
1292
1293 class DDNSProviderNamecheap(DDNSResponseParserXML, DDNSProvider):
1294 handle = "namecheap.com"
1295 name = "Namecheap"
1296 website = "http://namecheap.com"
1297 protocols = ("ipv4",)
1298
1299 # Information about the format of the HTTP request is to be found
1300 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
1301 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
1302
1303 url = "https://dynamicdns.park-your-domain.com/update"
1304 can_remove_records = False
1305
1306 def update_protocol(self, proto):
1307 # Namecheap requires the hostname splitted into a host and domain part.
1308 host, domain = self.hostname.split(".", 1)
1309
1310 # Get and store curent IP address.
1311 address = self.get_address(proto)
1312
1313 data = {
1314 "ip" : address,
1315 "password" : self.password,
1316 "host" : host,
1317 "domain" : domain
1318 }
1319
1320 # Send update to the server.
1321 response = self.send_request(self.url, data=data)
1322
1323 # Get the full response message.
1324 output = response.read()
1325
1326 # Handle success messages.
1327 if self.get_xml_tag_value(output, "IP") == address:
1328 return
1329
1330 # Handle error codes.
1331 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
1332
1333 if errorcode == "304156":
1334 raise DDNSAuthenticationError
1335 elif errorcode == "316153":
1336 raise DDNSRequestError(_("Domain not found"))
1337 elif errorcode == "316154":
1338 raise DDNSRequestError(_("Domain not active"))
1339 elif errorcode in ("380098", "380099"):
1340 raise DDNSInternalServerError
1341
1342 # If we got here, some other update error happened.
1343 raise DDNSUpdateError
1344
1345
1346 class DDNSProviderNOIP(DDNSProtocolDynDNS2, DDNSProvider):
1347 handle = "no-ip.com"
1348 name = "NoIP"
1349 website = "http://www.noip.com/"
1350 protocols = ("ipv4",)
1351
1352 # Information about the format of the HTTP request is to be found
1353 # here: http://www.noip.com/integrate/request and
1354 # here: http://www.noip.com/integrate/response
1355
1356 url = "http://dynupdate.noip.com/nic/update"
1357
1358 def prepare_request_data(self, proto):
1359 assert proto == "ipv4"
1360
1361 data = {
1362 "hostname" : self.hostname,
1363 "address" : self.get_address(proto),
1364 }
1365
1366 return data
1367
1368
1369 class DDNSProviderNowDNS(DDNSProtocolDynDNS2, DDNSProvider):
1370 handle = "now-dns.com"
1371 name = "NOW-DNS"
1372 website = "http://now-dns.com/"
1373 protocols = ("ipv6", "ipv4")
1374
1375 # Information about the format of the request is to be found
1376 # but only can be accessed by register an account and login
1377 # https://now-dns.com/?m=api
1378
1379 url = "https://now-dns.com/update"
1380
1381
1382 class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2, DDNSProvider):
1383 handle = "nsupdate.info"
1384 name = "nsupdate.info"
1385 website = "http://nsupdate.info/"
1386 protocols = ("ipv6", "ipv4",)
1387
1388 # Information about the format of the HTTP request can be found
1389 # after login on the provider user interface and here:
1390 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
1391
1392 url = "https://nsupdate.info/nic/update"
1393
1394 # TODO nsupdate.info can actually do this, but the functionality
1395 # has not been implemented here, yet.
1396 can_remove_records = False
1397
1398 # After a failed update, there will be no retries
1399 # https://bugzilla.ipfire.org/show_bug.cgi?id=10603
1400 holdoff_failure_days = None
1401
1402 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
1403 # and for the password a so called secret.
1404 @property
1405 def username(self):
1406 return self.get("hostname")
1407
1408 @property
1409 def password(self):
1410 return self.token or self.get("secret")
1411
1412 def prepare_request_data(self, proto):
1413 data = {
1414 "myip" : self.get_address(proto),
1415 }
1416
1417 return data
1418
1419
1420 class DDNSProviderOpenDNS(DDNSProtocolDynDNS2, DDNSProvider):
1421 handle = "opendns.com"
1422 name = "OpenDNS"
1423 website = "http://www.opendns.com"
1424
1425 # Detailed information about the update request and possible
1426 # response codes can be obtained from here:
1427 # https://support.opendns.com/entries/23891440
1428
1429 url = "https://updates.opendns.com/nic/update"
1430
1431 def prepare_request_data(self, proto):
1432 data = {
1433 "hostname" : self.hostname,
1434 "myip" : self.get_address(proto),
1435 }
1436
1437 return data
1438
1439
1440 class DDNSProviderOVH(DDNSProtocolDynDNS2, DDNSProvider):
1441 handle = "ovh.com"
1442 name = "OVH"
1443 website = "http://www.ovh.com/"
1444 protocols = ("ipv4",)
1445
1446 # OVH only provides very limited information about how to
1447 # update a DynDNS host. They only provide the update url
1448 # on the their german subpage.
1449 #
1450 # http://hilfe.ovh.de/DomainDynHost
1451
1452 url = "https://www.ovh.com/nic/update"
1453
1454 def prepare_request_data(self, proto):
1455 data = DDNSProtocolDynDNS2.prepare_request_data(self, proto)
1456 data.update({
1457 "system" : "dyndns",
1458 })
1459
1460 return data
1461
1462
1463 class DDNSProviderRegfish(DDNSProvider):
1464 handle = "regfish.com"
1465 name = "Regfish GmbH"
1466 website = "http://www.regfish.com/"
1467
1468 # A full documentation to the providers api can be found here
1469 # but is only available in german.
1470 # https://www.regfish.de/domains/dyndns/dokumentation
1471
1472 url = "https://dyndns.regfish.de/"
1473 can_remove_records = False
1474
1475 def update(self):
1476 data = {
1477 "fqdn" : self.hostname,
1478 }
1479
1480 # Check if we update an IPv6 address.
1481 address6 = self.get_address("ipv6")
1482 if address6:
1483 data["ipv6"] = address6
1484
1485 # Check if we update an IPv4 address.
1486 address4 = self.get_address("ipv4")
1487 if address4:
1488 data["ipv4"] = address4
1489
1490 # Raise an error if none address is given.
1491 if not data.has_key("ipv6") and not data.has_key("ipv4"):
1492 raise DDNSConfigurationError
1493
1494 # Check if a token has been set.
1495 if self.token:
1496 data["token"] = self.token
1497
1498 # Raise an error if no token and no useranem and password
1499 # are given.
1500 elif not self.username and not self.password:
1501 raise DDNSConfigurationError(_("No Auth details specified"))
1502
1503 # HTTP Basic Auth is only allowed if no token is used.
1504 if self.token:
1505 # Send update to the server.
1506 response = self.send_request(self.url, data=data)
1507 else:
1508 # Send update to the server.
1509 response = self.send_request(self.url, username=self.username, password=self.password,
1510 data=data)
1511
1512 # Get the full response message.
1513 output = response.read()
1514
1515 # Handle success messages.
1516 if "100" in output or "101" in output:
1517 return
1518
1519 # Handle error codes.
1520 if "401" or "402" in output:
1521 raise DDNSAuthenticationError
1522 elif "408" in output:
1523 raise DDNSRequestError(_("Invalid IPv4 address has been sent"))
1524 elif "409" in output:
1525 raise DDNSRequestError(_("Invalid IPv6 address has been sent"))
1526 elif "412" in output:
1527 raise DDNSRequestError(_("No valid FQDN was given"))
1528 elif "414" in output:
1529 raise DDNSInternalServerError
1530
1531 # If we got here, some other update error happened.
1532 raise DDNSUpdateError
1533
1534
1535 class DDNSProviderSchokokeksDNS(DDNSProtocolDynDNS2, DDNSProvider):
1536 handle = "schokokeks.org"
1537 name = "Schokokeks"
1538 website = "http://www.schokokeks.org/"
1539 protocols = ("ipv4",)
1540
1541 # Information about the format of the request is to be found
1542 # https://wiki.schokokeks.org/DynDNS
1543 url = "https://dyndns.schokokeks.org/nic/update"
1544
1545
1546 class DDNSProviderSelfhost(DDNSProtocolDynDNS2, DDNSProvider):
1547 handle = "selfhost.de"
1548 name = "Selfhost.de"
1549 website = "http://www.selfhost.de/"
1550 protocols = ("ipv4",)
1551
1552 url = "https://carol.selfhost.de/nic/update"
1553
1554 def prepare_request_data(self, proto):
1555 data = DDNSProtocolDynDNS2.prepare_request_data(self, proto)
1556 data.update({
1557 "hostname" : "1",
1558 })
1559
1560 return data
1561
1562
1563 class DDNSProviderServercow(DDNSProvider):
1564 handle = "servercow.de"
1565 name = "servercow.de"
1566 website = "https://servercow.de/"
1567 protocols = ("ipv4", "ipv6")
1568
1569 url = "https://www.servercow.de/dnsupdate/update.php"
1570 can_remove_records = False
1571
1572 def update_protocol(self, proto):
1573 data = {
1574 "ipaddr" : self.get_address(proto),
1575 "hostname" : self.hostname,
1576 "username" : self.username,
1577 "pass" : self.password,
1578 }
1579
1580 # Send request to provider
1581 response = self.send_request(self.url, data=data)
1582
1583 # Read response
1584 output = response.read()
1585
1586 # Server responds with OK if update was successful
1587 if output.startswith("OK"):
1588 return
1589
1590 # Catch any errors
1591 elif output.startswith("FAILED - Authentication failed"):
1592 raise DDNSAuthenticationError
1593
1594 # If we got here, some other update error happened
1595 raise DDNSUpdateError(output)
1596
1597
1598 class DDNSProviderSPDNS(DDNSProtocolDynDNS2, DDNSProvider):
1599 handle = "spdns.org"
1600 name = "SPDYN"
1601 website = "https://www.spdyn.de/"
1602
1603 # Detailed information about request and response codes are provided
1604 # by the vendor. They are using almost the same mechanism and status
1605 # codes as dyndns.org so we can inherit all those stuff.
1606 #
1607 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
1608 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
1609
1610 url = "https://update.spdyn.de/nic/update"
1611
1612 @property
1613 def username(self):
1614 return self.get("username") or self.hostname
1615
1616 @property
1617 def password(self):
1618 return self.get("password") or self.token
1619
1620
1621 class DDNSProviderStrato(DDNSProtocolDynDNS2, DDNSProvider):
1622 handle = "strato.com"
1623 name = "Strato AG"
1624 website = "http:/www.strato.com/"
1625 protocols = ("ipv4",)
1626
1627 # Information about the request and response can be obtained here:
1628 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
1629
1630 url = "https://dyndns.strato.com/nic/update"
1631
1632 def prepare_request_data(self, proto):
1633 data = DDNSProtocolDynDNS2.prepare_request_data(self, proto)
1634 data.update({
1635 "mx" : "NOCHG",
1636 "backupmx" : "NOCHG"
1637 })
1638
1639 return data
1640
1641
1642 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2, DDNSProvider):
1643 handle = "twodns.de"
1644 name = "TwoDNS"
1645 website = "http://www.twodns.de"
1646 protocols = ("ipv4",)
1647
1648 # Detailed information about the request can be found here
1649 # http://twodns.de/en/faqs
1650 # http://twodns.de/en/api
1651
1652 url = "https://update.twodns.de/update"
1653
1654 def prepare_request_data(self, proto):
1655 assert proto == "ipv4"
1656
1657 data = {
1658 "ip" : self.get_address(proto),
1659 "hostname" : self.hostname
1660 }
1661
1662 return data
1663
1664
1665 class DDNSProviderUdmedia(DDNSProtocolDynDNS2, DDNSProvider):
1666 handle = "udmedia.de"
1667 name = "Udmedia GmbH"
1668 website = "http://www.udmedia.de"
1669 protocols = ("ipv4",)
1670
1671 # Information about the request can be found here
1672 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1673
1674 url = "https://www.udmedia.de/nic/update"
1675
1676
1677 class DDNSProviderVariomedia(DDNSProtocolDynDNS2, DDNSProvider):
1678 handle = "variomedia.de"
1679 name = "Variomedia"
1680 website = "http://www.variomedia.de/"
1681 protocols = ("ipv6", "ipv4",)
1682
1683 # Detailed information about the request can be found here
1684 # https://dyndns.variomedia.de/
1685
1686 url = "https://dyndns.variomedia.de/nic/update"
1687
1688 def prepare_request_data(self, proto):
1689 data = {
1690 "hostname" : self.hostname,
1691 "myip" : self.get_address(proto),
1692 }
1693
1694 return data
1695
1696
1697 class DDNSProviderXLhost(DDNSProtocolDynDNS2, DDNSProvider):
1698 handle = "xlhost.de"
1699 name = "XLhost"
1700 website = "http://xlhost.de/"
1701 protocols = ("ipv4",)
1702
1703 # Information about the format of the HTTP request is to be found
1704 # here: https://xlhost.de/faq/index_html?topicId=CQA2ELIPO4SQ
1705
1706 url = "https://nsupdate.xlhost.de/"
1707
1708
1709 class DDNSProviderZoneedit(DDNSProvider):
1710 handle = "zoneedit.com"
1711 name = "Zoneedit"
1712 website = "http://www.zoneedit.com"
1713 protocols = ("ipv4",)
1714
1715 # Detailed information about the request and the response codes can be
1716 # obtained here:
1717 # http://www.zoneedit.com/doc/api/other.html
1718 # http://www.zoneedit.com/faq.html
1719
1720 url = "https://dynamic.zoneedit.com/auth/dynamic.html"
1721
1722 def update_protocol(self, proto):
1723 data = {
1724 "dnsto" : self.get_address(proto),
1725 "host" : self.hostname
1726 }
1727
1728 # Send update to the server.
1729 response = self.send_request(self.url, username=self.username, password=self.password,
1730 data=data)
1731
1732 # Get the full response message.
1733 output = response.read()
1734
1735 # Handle success messages.
1736 if output.startswith("<SUCCESS"):
1737 return
1738
1739 # Handle error codes.
1740 if output.startswith("invalid login"):
1741 raise DDNSAuthenticationError
1742 elif output.startswith("<ERROR CODE=\"704\""):
1743 raise DDNSRequestError(_("No valid FQDN was given"))
1744 elif output.startswith("<ERROR CODE=\"702\""):
1745 raise DDNSRequestError(_("Too frequent update requests have been sent"))
1746
1747 # If we got here, some other update error happened.
1748 raise DDNSUpdateError
1749
1750
1751 class DDNSProviderDNSmadeEasy(DDNSProvider):
1752 handle = "dnsmadeeasy.com"
1753 name = "DNSmadeEasy.com"
1754 website = "http://www.dnsmadeeasy.com/"
1755 protocols = ("ipv4",)
1756
1757 # DNS Made Easy Nameserver Provider also offering Dynamic DNS
1758 # Documentation can be found here:
1759 # http://www.dnsmadeeasy.com/dynamic-dns/
1760
1761 url = "https://cp.dnsmadeeasy.com/servlet/updateip?"
1762 can_remove_records = False
1763
1764 def update_protocol(self, proto):
1765 data = {
1766 "ip" : self.get_address(proto),
1767 "id" : self.hostname,
1768 "username" : self.username,
1769 "password" : self.password,
1770 }
1771
1772 # Send update to the server.
1773 response = self.send_request(self.url, data=data)
1774
1775 # Get the full response message.
1776 output = response.read()
1777
1778 # Handle success messages.
1779 if output.startswith("success") or output.startswith("error-record-ip-same"):
1780 return
1781
1782 # Handle error codes.
1783 if output.startswith("error-auth-suspend"):
1784 raise DDNSRequestError(_("Account has been suspended"))
1785
1786 elif output.startswith("error-auth-voided"):
1787 raise DDNSRequestError(_("Account has been revoked"))
1788
1789 elif output.startswith("error-record-invalid"):
1790 raise DDNSRequestError(_("Specified host does not exist"))
1791
1792 elif output.startswith("error-auth"):
1793 raise DDNSAuthenticationError
1794
1795 # If we got here, some other update error happened.
1796 raise DDNSUpdateError(_("Server response: %s") % output)
1797
1798
1799 class DDNSProviderZZZZ(DDNSProvider):
1800 handle = "zzzz.io"
1801 name = "zzzz"
1802 website = "https://zzzz.io"
1803 protocols = ("ipv6", "ipv4",)
1804
1805 # Detailed information about the update request can be found here:
1806 # https://zzzz.io/faq/
1807
1808 # Details about the possible response codes have been provided in the bugtracker:
1809 # https://bugzilla.ipfire.org/show_bug.cgi?id=10584#c2
1810
1811 url = "https://zzzz.io/api/v1/update"
1812 can_remove_records = False
1813
1814 def update_protocol(self, proto):
1815 data = {
1816 "ip" : self.get_address(proto),
1817 "token" : self.token,
1818 }
1819
1820 if proto == "ipv6":
1821 data["type"] = "aaaa"
1822
1823 # zzzz uses the host from the full hostname as part
1824 # of the update url.
1825 host, domain = self.hostname.split(".", 1)
1826
1827 # Add host value to the update url.
1828 url = "%s/%s" % (self.url, host)
1829
1830 # Send update to the server.
1831 try:
1832 response = self.send_request(url, data=data)
1833
1834 # Handle error codes.
1835 except DDNSNotFound:
1836 raise DDNSRequestError(_("Invalid hostname specified"))
1837
1838 # Handle success messages.
1839 if response.code == 200:
1840 return
1841
1842 # If we got here, some other update error happened.
1843 raise DDNSUpdateError