]> git.ipfire.org Git - oddments/ddns.git/blob - src/ddns/providers.py
Zoneedit: Provide better error message on error 702 responses.
[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 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
404 # If we got here, some other update error happened.
405 raise DDNSUpdateError(_("Server response: %s") % output)
406
407
408 class DDNSResponseParserXML(object):
409 """
410 This class provides a parser for XML responses which
411 will be sent by various providers. This class uses the python
412 shipped XML minidom module to walk through the XML tree and return
413 a requested element.
414 """
415
416 def get_xml_tag_value(self, document, content):
417 # Send input to the parser.
418 xmldoc = xml.dom.minidom.parseString(document)
419
420 # Get XML elements by the given content.
421 element = xmldoc.getElementsByTagName(content)
422
423 # If no element has been found, we directly can return None.
424 if not element:
425 return None
426
427 # Only get the first child from an element, even there are more than one.
428 firstchild = element[0].firstChild
429
430 # Get the value of the child.
431 value = firstchild.nodeValue
432
433 # Return the value.
434 return value
435
436
437 class DDNSProviderAllInkl(DDNSProvider):
438 handle = "all-inkl.com"
439 name = "All-inkl.com"
440 website = "http://all-inkl.com/"
441 protocols = ("ipv4",)
442
443 # There are only information provided by the vendor how to
444 # perform an update on a FRITZ Box. Grab requried informations
445 # from the net.
446 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
447
448 url = "http://dyndns.kasserver.com"
449 can_remove_records = False
450
451 def update(self):
452 # There is no additional data required so we directly can
453 # send our request.
454 response = self.send_request(self.url, username=self.username, password=self.password)
455
456 # Get the full response message.
457 output = response.read()
458
459 # Handle success messages.
460 if output.startswith("good") or output.startswith("nochg"):
461 return
462
463 # If we got here, some other update error happened.
464 raise DDNSUpdateError
465
466
467 class DDNSProviderBindNsupdate(DDNSProvider):
468 handle = "nsupdate"
469 name = "BIND nsupdate utility"
470 website = "http://en.wikipedia.org/wiki/Nsupdate"
471
472 DEFAULT_TTL = 60
473
474 @staticmethod
475 def supported():
476 # Search if the nsupdate utility is available
477 paths = os.environ.get("PATH")
478
479 for path in paths.split(":"):
480 executable = os.path.join(path, "nsupdate")
481
482 if os.path.exists(executable):
483 return True
484
485 return False
486
487 def update(self):
488 scriptlet = self.__make_scriptlet()
489
490 # -v enables TCP hence we transfer keys and other data that may
491 # exceed the size of one packet.
492 # -t sets the timeout
493 command = ["nsupdate", "-v", "-t", "60"]
494
495 p = subprocess.Popen(command, shell=True,
496 stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
497 )
498 stdout, stderr = p.communicate(scriptlet)
499
500 if p.returncode == 0:
501 return
502
503 raise DDNSError("nsupdate terminated with error code: %s\n %s" % (p.returncode, stderr))
504
505 def __make_scriptlet(self):
506 scriptlet = []
507
508 # Set a different server the update is sent to.
509 server = self.get("server", None)
510 if server:
511 scriptlet.append("server %s" % server)
512
513 # Set the DNS zone the host should be added to.
514 zone = self.get("zone", None)
515 if zone:
516 scriptlet.append("zone %s" % zone)
517
518 key = self.get("key", None)
519 if key:
520 secret = self.get("secret")
521
522 scriptlet.append("key %s %s" % (key, secret))
523
524 ttl = self.get("ttl", self.DEFAULT_TTL)
525
526 # Perform an update for each supported protocol.
527 for rrtype, proto in (("AAAA", "ipv6"), ("A", "ipv4")):
528 address = self.get_address(proto)
529 if not address:
530 continue
531
532 scriptlet.append("update delete %s. %s" % (self.hostname, rrtype))
533 scriptlet.append("update add %s. %s %s %s" % \
534 (self.hostname, ttl, rrtype, address))
535
536 # Send the actions to the server.
537 scriptlet.append("send")
538 scriptlet.append("quit")
539
540 logger.debug(_("Scriptlet:"))
541 for line in scriptlet:
542 # Masquerade the line with the secret key.
543 if line.startswith("key"):
544 line = "key **** ****"
545
546 logger.debug(" %s" % line)
547
548 return "\n".join(scriptlet)
549
550
551 class DDNSProviderChangeIP(DDNSProvider):
552 handle = "changeip.com"
553 name = "ChangeIP.com"
554 website = "https://changeip.com"
555 protocols = ("ipv4",)
556
557 # Detailed information about the update api can be found here.
558 # http://www.changeip.com/accounts/knowledgebase.php?action=displayarticle&id=34
559
560 url = "https://nic.changeip.com/nic/update"
561 can_remove_records = False
562
563 def update_protocol(self, proto):
564 data = {
565 "hostname" : self.hostname,
566 "myip" : self.get_address(proto),
567 }
568
569 # Send update to the server.
570 try:
571 response = self.send_request(self.url, username=self.username, password=self.password,
572 data=data)
573
574 # Handle error codes.
575 except urllib2.HTTPError, e:
576 if e.code == 422:
577 raise DDNSRequestError(_("Domain not found."))
578
579 raise
580
581 # Handle success message.
582 if response.code == 200:
583 return
584
585 # If we got here, some other update error happened.
586 raise DDNSUpdateError(_("Server response: %s") % output)
587
588
589 class DDNSProviderDesecIO(DDNSProtocolDynDNS2, DDNSProvider):
590 handle = "desec.io"
591 name = "desec.io"
592 website = "https://www.desec.io"
593 protocols = ("ipv6", "ipv4",)
594
595 # ipv4 / ipv6 records are automatically removed when the update
596 # request originates from the respectively other protocol and no
597 # address is explicitly provided for the unused protocol.
598
599 url = "https://update.dedyn.io"
600
601 # desec.io sends the IPv6 and IPv4 address in one request
602
603 def update(self):
604 data = DDNSProtocolDynDNS2.prepare_request_data(self, "ipv4")
605
606 # This one supports IPv6
607 myipv6 = self.get_address("ipv6")
608
609 # Add update information if we have an IPv6 address.
610 if myipv6:
611 data["myipv6"] = myipv6
612
613 self.send_request(data)
614
615
616 class DDNSProviderDDNSS(DDNSProvider):
617 handle = "ddnss.de"
618 name = "DDNSS"
619 website = "http://www.ddnss.de"
620 protocols = ("ipv4",)
621
622 # Detailed information about how to send the update request and possible response
623 # codes can be obtained from here.
624 # http://www.ddnss.de/info.php
625 # http://www.megacomputing.de/2014/08/dyndns-service-response-time/#more-919
626
627 url = "http://www.ddnss.de/upd.php"
628 can_remove_records = False
629
630 def update_protocol(self, proto):
631 data = {
632 "ip" : self.get_address(proto),
633 "host" : self.hostname,
634 }
635
636 # Check if a token has been set.
637 if self.token:
638 data["key"] = self.token
639
640 # Check if username and hostname are given.
641 elif self.username and self.password:
642 data.update({
643 "user" : self.username,
644 "pwd" : self.password,
645 })
646
647 # Raise an error if no auth details are given.
648 else:
649 raise DDNSConfigurationError
650
651 # Send update to the server.
652 response = self.send_request(self.url, data=data)
653
654 # This provider sends the response code as part of the header.
655 header = response.info()
656
657 # Get status information from the header.
658 output = header.getheader('ddnss-response')
659
660 # Handle success messages.
661 if output == "good" or output == "nochg":
662 return
663
664 # Handle error codes.
665 if output == "badauth":
666 raise DDNSAuthenticationError
667 elif output == "notfqdn":
668 raise DDNSRequestError(_("No valid FQDN was given."))
669 elif output == "nohost":
670 raise DDNSRequestError(_("Specified host does not exist."))
671 elif output == "911":
672 raise DDNSInternalServerError
673 elif output == "dnserr":
674 raise DDNSInternalServerError(_("DNS error encountered."))
675 elif output == "disabled":
676 raise DDNSRequestError(_("Account disabled or locked."))
677
678 # If we got here, some other update error happened.
679 raise DDNSUpdateError
680
681
682 class DDNSProviderDHS(DDNSProvider):
683 handle = "dhs.org"
684 name = "DHS International"
685 website = "http://dhs.org/"
686 protocols = ("ipv4",)
687
688 # No information about the used update api provided on webpage,
689 # grabed from source code of ez-ipudate.
690
691 url = "http://members.dhs.org/nic/hosts"
692 can_remove_records = False
693
694 def update_protocol(self, proto):
695 data = {
696 "domain" : self.hostname,
697 "ip" : self.get_address(proto),
698 "hostcmd" : "edit",
699 "hostcmdstage" : "2",
700 "type" : "4",
701 }
702
703 # Send update to the server.
704 response = self.send_request(self.url, username=self.username, password=self.password,
705 data=data)
706
707 # Handle success messages.
708 if response.code == 200:
709 return
710
711 # If we got here, some other update error happened.
712 raise DDNSUpdateError
713
714
715 class DDNSProviderDNSpark(DDNSProvider):
716 handle = "dnspark.com"
717 name = "DNS Park"
718 website = "http://dnspark.com/"
719 protocols = ("ipv4",)
720
721 # Informations to the used api can be found here:
722 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
723
724 url = "https://control.dnspark.com/api/dynamic/update.php"
725 can_remove_records = False
726
727 def update_protocol(self, proto):
728 data = {
729 "domain" : self.hostname,
730 "ip" : self.get_address(proto),
731 }
732
733 # Send update to the server.
734 response = self.send_request(self.url, username=self.username, password=self.password,
735 data=data)
736
737 # Get the full response message.
738 output = response.read()
739
740 # Handle success messages.
741 if output.startswith("ok") or output.startswith("nochange"):
742 return
743
744 # Handle error codes.
745 if output == "unauth":
746 raise DDNSAuthenticationError
747 elif output == "abuse":
748 raise DDNSAbuseError
749 elif output == "blocked":
750 raise DDNSBlockedError
751 elif output == "nofqdn":
752 raise DDNSRequestError(_("No valid FQDN was given."))
753 elif output == "nohost":
754 raise DDNSRequestError(_("Invalid hostname specified."))
755 elif output == "notdyn":
756 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
757 elif output == "invalid":
758 raise DDNSRequestError(_("Invalid IP address has been sent."))
759
760 # If we got here, some other update error happened.
761 raise DDNSUpdateError
762
763
764 class DDNSProviderDtDNS(DDNSProvider):
765 handle = "dtdns.com"
766 name = "DtDNS"
767 website = "http://dtdns.com/"
768 protocols = ("ipv4",)
769
770 # Information about the format of the HTTPS request is to be found
771 # http://www.dtdns.com/dtsite/updatespec
772
773 url = "https://www.dtdns.com/api/autodns.cfm"
774 can_remove_records = False
775
776 def update_protocol(self, proto):
777 data = {
778 "ip" : self.get_address(proto),
779 "id" : self.hostname,
780 "pw" : self.password
781 }
782
783 # Send update to the server.
784 response = self.send_request(self.url, data=data)
785
786 # Get the full response message.
787 output = response.read()
788
789 # Remove all leading and trailing whitespace.
790 output = output.strip()
791
792 # Handle success messages.
793 if "now points to" in output:
794 return
795
796 # Handle error codes.
797 if output == "No hostname to update was supplied.":
798 raise DDNSRequestError(_("No hostname specified."))
799
800 elif output == "The hostname you supplied is not valid.":
801 raise DDNSRequestError(_("Invalid hostname specified."))
802
803 elif output == "The password you supplied is not valid.":
804 raise DDNSAuthenticationError
805
806 elif output == "Administration has disabled this account.":
807 raise DDNSRequestError(_("Account has been disabled."))
808
809 elif output == "Illegal character in IP.":
810 raise DDNSRequestError(_("Invalid IP address has been sent."))
811
812 elif output == "Too many failed requests.":
813 raise DDNSRequestError(_("Too many failed requests."))
814
815 # If we got here, some other update error happened.
816 raise DDNSUpdateError
817
818
819 class DDNSProviderDynDNS(DDNSProtocolDynDNS2, DDNSProvider):
820 handle = "dyndns.org"
821 name = "Dyn"
822 website = "http://dyn.com/dns/"
823 protocols = ("ipv4",)
824
825 # Information about the format of the request is to be found
826 # http://http://dyn.com/support/developers/api/perform-update/
827 # http://dyn.com/support/developers/api/return-codes/
828
829 url = "https://members.dyndns.org/nic/update"
830
831
832 class DDNSProviderDynU(DDNSProtocolDynDNS2, DDNSProvider):
833 handle = "dynu.com"
834 name = "Dynu"
835 website = "http://dynu.com/"
836 protocols = ("ipv6", "ipv4",)
837
838 # Detailed information about the request and response codes
839 # are available on the providers webpage.
840 # http://dynu.com/Default.aspx?page=dnsapi
841
842 url = "https://api.dynu.com/nic/update"
843
844 # DynU sends the IPv6 and IPv4 address in one request
845
846 def update(self):
847 data = DDNSProtocolDynDNS2.prepare_request_data(self, "ipv4")
848
849 # This one supports IPv6
850 myipv6 = self.get_address("ipv6")
851
852 # Add update information if we have an IPv6 address.
853 if myipv6:
854 data["myipv6"] = myipv6
855
856 self.send_request(data)
857
858
859 class DDNSProviderEasyDNS(DDNSProvider):
860 handle = "easydns.com"
861 name = "EasyDNS"
862 website = "http://www.easydns.com/"
863 protocols = ("ipv4",)
864
865 # Detailed information about the request and response codes
866 # (API 1.3) are available on the providers webpage.
867 # https://fusion.easydns.com/index.php?/Knowledgebase/Article/View/102/7/dynamic-dns
868
869 url = "http://api.cp.easydns.com/dyn/tomato.php"
870
871 def update_protocol(self, proto):
872 data = {
873 "myip" : self.get_address(proto, "-"),
874 "hostname" : self.hostname,
875 }
876
877 # Send update to the server.
878 response = self.send_request(self.url, data=data,
879 username=self.username, password=self.password)
880
881 # Get the full response message.
882 output = response.read()
883
884 # Remove all leading and trailing whitespace.
885 output = output.strip()
886
887 # Handle success messages.
888 if output.startswith("NOERROR"):
889 return
890
891 # Handle error codes.
892 if output.startswith("NOACCESS"):
893 raise DDNSAuthenticationError
894
895 elif output.startswith("NOSERVICE"):
896 raise DDNSRequestError(_("Dynamic DNS is not turned on for this domain."))
897
898 elif output.startswith("ILLEGAL INPUT"):
899 raise DDNSRequestError(_("Invalid data has been sent."))
900
901 elif output.startswith("TOOSOON"):
902 raise DDNSRequestError(_("Too frequent update requests have been sent."))
903
904 # If we got here, some other update error happened.
905 raise DDNSUpdateError
906
907
908 class DDNSProviderDomopoli(DDNSProtocolDynDNS2, DDNSProvider):
909 handle = "domopoli.de"
910 name = "domopoli.de"
911 website = "http://domopoli.de/"
912 protocols = ("ipv4",)
913
914 # https://www.domopoli.de/?page=howto#DynDns_start
915
916 url = "http://dyndns.domopoli.de/nic/update"
917
918
919 class DDNSProviderDynsNet(DDNSProvider):
920 handle = "dyns.net"
921 name = "DyNS"
922 website = "http://www.dyns.net/"
923 protocols = ("ipv4",)
924 can_remove_records = False
925
926 # There is very detailed informatio about how to send the update request and
927 # the possible response codes. (Currently we are using the v1.1 proto)
928 # http://www.dyns.net/documentation/technical/protocol/
929
930 url = "http://www.dyns.net/postscript011.php"
931
932 def update_protocol(self, proto):
933 data = {
934 "ip" : self.get_address(proto),
935 "host" : self.hostname,
936 "username" : self.username,
937 "password" : self.password,
938 }
939
940 # Send update to the server.
941 response = self.send_request(self.url, data=data)
942
943 # Get the full response message.
944 output = response.read()
945
946 # Handle success messages.
947 if output.startswith("200"):
948 return
949
950 # Handle error codes.
951 if output.startswith("400"):
952 raise DDNSRequestError(_("Malformed request has been sent."))
953 elif output.startswith("401"):
954 raise DDNSAuthenticationError
955 elif output.startswith("402"):
956 raise DDNSRequestError(_("Too frequent update requests have been sent."))
957 elif output.startswith("403"):
958 raise DDNSInternalServerError
959
960 # If we got here, some other update error happened.
961 raise DDNSUpdateError(_("Server response: %s") % output)
962
963
964 class DDNSProviderEnomCom(DDNSResponseParserXML, DDNSProvider):
965 handle = "enom.com"
966 name = "eNom Inc."
967 website = "http://www.enom.com/"
968 protocols = ("ipv4",)
969
970 # There are very detailed information about how to send an update request and
971 # the respone codes.
972 # http://www.enom.com/APICommandCatalog/
973
974 url = "https://dynamic.name-services.com/interface.asp"
975 can_remove_records = False
976
977 def update_protocol(self, proto):
978 data = {
979 "command" : "setdnshost",
980 "responsetype" : "xml",
981 "address" : self.get_address(proto),
982 "domainpassword" : self.password,
983 "zone" : self.hostname
984 }
985
986 # Send update to the server.
987 response = self.send_request(self.url, data=data)
988
989 # Get the full response message.
990 output = response.read()
991
992 # Handle success messages.
993 if self.get_xml_tag_value(output, "ErrCount") == "0":
994 return
995
996 # Handle error codes.
997 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
998
999 if errorcode == "304155":
1000 raise DDNSAuthenticationError
1001 elif errorcode == "304153":
1002 raise DDNSRequestError(_("Domain not found."))
1003
1004 # If we got here, some other update error happened.
1005 raise DDNSUpdateError
1006
1007
1008 class DDNSProviderEntryDNS(DDNSProvider):
1009 handle = "entrydns.net"
1010 name = "EntryDNS"
1011 website = "http://entrydns.net/"
1012 protocols = ("ipv4",)
1013
1014 # Some very tiny details about their so called "Simple API" can be found
1015 # here: https://entrydns.net/help
1016 url = "https://entrydns.net/records/modify"
1017 can_remove_records = False
1018
1019 def update_protocol(self, proto):
1020 data = {
1021 "ip" : self.get_address(proto),
1022 }
1023
1024 # Add auth token to the update url.
1025 url = "%s/%s" % (self.url, self.token)
1026
1027 # Send update to the server.
1028 try:
1029 response = self.send_request(url, data=data)
1030
1031 # Handle error codes
1032 except urllib2.HTTPError, e:
1033 if e.code == 404:
1034 raise DDNSAuthenticationError
1035
1036 elif e.code == 422:
1037 raise DDNSRequestError(_("An invalid IP address was submitted"))
1038
1039 raise
1040
1041 # Handle success messages.
1042 if response.code == 200:
1043 return
1044
1045 # If we got here, some other update error happened.
1046 raise DDNSUpdateError
1047
1048
1049 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider):
1050 handle = "freedns.afraid.org"
1051 name = "freedns.afraid.org"
1052 website = "http://freedns.afraid.org/"
1053
1054 # No information about the request or response could be found on the vendor
1055 # page. All used values have been collected by testing.
1056 url = "https://freedns.afraid.org/dynamic/update.php"
1057 can_remove_records = False
1058
1059 def update_protocol(self, proto):
1060 data = {
1061 "address" : self.get_address(proto),
1062 }
1063
1064 # Add auth token to the update url.
1065 url = "%s?%s" % (self.url, self.token)
1066
1067 # Send update to the server.
1068 response = self.send_request(url, data=data)
1069
1070 # Get the full response message.
1071 output = response.read()
1072
1073 # Handle success messages.
1074 if output.startswith("Updated") or "has not changed" in output:
1075 return
1076
1077 # Handle error codes.
1078 if output == "ERROR: Unable to locate this record":
1079 raise DDNSAuthenticationError
1080 elif "is an invalid IP address" in output:
1081 raise DDNSRequestError(_("Invalid IP address has been sent."))
1082
1083 # If we got here, some other update error happened.
1084 raise DDNSUpdateError
1085
1086
1087 class DDNSProviderJoker(DDNSProtocolDynDNS2, DDNSProvider):
1088 handle = "joker.com"
1089 name = "Joker.com Dynamic DNS"
1090 website = "https://joker.com/"
1091 protocols = ("ipv4",)
1092
1093 # Information about the request can be found here:
1094 # https://joker.com/faq/content/11/427/en/what-is-dynamic-dns-dyndns.html
1095 # Using DynDNS V2 protocol over HTTPS here
1096
1097 url = "https://svc.joker.com/nic/update"
1098
1099
1100 class DDNSProviderGoogle(DDNSProtocolDynDNS2, DDNSProvider):
1101 handle = "domains.google.com"
1102 name = "Google Domains"
1103 website = "https://domains.google.com/"
1104 protocols = ("ipv4",)
1105
1106 # Information about the format of the HTTP request is to be found
1107 # here: https://support.google.com/domains/answer/6147083?hl=en
1108
1109 url = "https://domains.google.com/nic/update"
1110
1111
1112 class DDNSProviderLightningWireLabs(DDNSProvider):
1113 handle = "dns.lightningwirelabs.com"
1114 name = "Lightning Wire Labs DNS Service"
1115 website = "http://dns.lightningwirelabs.com/"
1116
1117 # Information about the format of the HTTPS request is to be found
1118 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
1119
1120 url = "https://dns.lightningwirelabs.com/update"
1121
1122 def update(self):
1123 data = {
1124 "hostname" : self.hostname,
1125 "address6" : self.get_address("ipv6", "-"),
1126 "address4" : self.get_address("ipv4", "-"),
1127 }
1128
1129 # Check if a token has been set.
1130 if self.token:
1131 data["token"] = self.token
1132
1133 # Check for username and password.
1134 elif self.username and self.password:
1135 data.update({
1136 "username" : self.username,
1137 "password" : self.password,
1138 })
1139
1140 # Raise an error if no auth details are given.
1141 else:
1142 raise DDNSConfigurationError
1143
1144 # Send update to the server.
1145 response = self.send_request(self.url, data=data)
1146
1147 # Handle success messages.
1148 if response.code == 200:
1149 return
1150
1151 # If we got here, some other update error happened.
1152 raise DDNSUpdateError
1153
1154
1155 class DDNSProviderLoopia(DDNSProtocolDynDNS2, DDNSProvider):
1156 handle = "loopia.se"
1157 name = "Loopia AB"
1158 website = "https://www.loopia.com"
1159 protocols = ("ipv4",)
1160
1161 # Information about the format of the HTTP request is to be found
1162 # here: https://support.loopia.com/wiki/About_the_DynDNS_support
1163
1164 url = "https://dns.loopia.se/XDynDNSServer/XDynDNS.php"
1165
1166
1167 class DDNSProviderMyOnlinePortal(DDNSProtocolDynDNS2, DDNSProvider):
1168 handle = "myonlineportal.net"
1169 name = "myonlineportal.net"
1170 website = "https:/myonlineportal.net/"
1171
1172 # Information about the request and response can be obtained here:
1173 # https://myonlineportal.net/howto_dyndns
1174
1175 url = "https://myonlineportal.net/updateddns"
1176
1177 def prepare_request_data(self, proto):
1178 data = {
1179 "hostname" : self.hostname,
1180 "ip" : self.get_address(proto),
1181 }
1182
1183 return data
1184
1185
1186 class DDNSProviderNamecheap(DDNSResponseParserXML, DDNSProvider):
1187 handle = "namecheap.com"
1188 name = "Namecheap"
1189 website = "http://namecheap.com"
1190 protocols = ("ipv4",)
1191
1192 # Information about the format of the HTTP request is to be found
1193 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
1194 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
1195
1196 url = "https://dynamicdns.park-your-domain.com/update"
1197 can_remove_records = False
1198
1199 def update_protocol(self, proto):
1200 # Namecheap requires the hostname splitted into a host and domain part.
1201 host, domain = self.hostname.split(".", 1)
1202
1203 # Get and store curent IP address.
1204 address = self.get_address(proto)
1205
1206 data = {
1207 "ip" : address,
1208 "password" : self.password,
1209 "host" : host,
1210 "domain" : domain
1211 }
1212
1213 # Send update to the server.
1214 response = self.send_request(self.url, data=data)
1215
1216 # Get the full response message.
1217 output = response.read()
1218
1219 # Handle success messages.
1220 if self.get_xml_tag_value(output, "IP") == address:
1221 return
1222
1223 # Handle error codes.
1224 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
1225
1226 if errorcode == "304156":
1227 raise DDNSAuthenticationError
1228 elif errorcode == "316153":
1229 raise DDNSRequestError(_("Domain not found."))
1230 elif errorcode == "316154":
1231 raise DDNSRequestError(_("Domain not active."))
1232 elif errorcode in ("380098", "380099"):
1233 raise DDNSInternalServerError
1234
1235 # If we got here, some other update error happened.
1236 raise DDNSUpdateError
1237
1238
1239 class DDNSProviderNOIP(DDNSProtocolDynDNS2, DDNSProvider):
1240 handle = "no-ip.com"
1241 name = "No-IP"
1242 website = "http://www.no-ip.com/"
1243 protocols = ("ipv4",)
1244
1245 # Information about the format of the HTTP request is to be found
1246 # here: http://www.no-ip.com/integrate/request and
1247 # here: http://www.no-ip.com/integrate/response
1248
1249 url = "http://dynupdate.no-ip.com/nic/update"
1250
1251 def prepare_request_data(self, proto):
1252 assert proto == "ipv4"
1253
1254 data = {
1255 "hostname" : self.hostname,
1256 "address" : self.get_address(proto),
1257 }
1258
1259 return data
1260
1261
1262 class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2, DDNSProvider):
1263 handle = "nsupdate.info"
1264 name = "nsupdate.info"
1265 website = "http://nsupdate.info/"
1266 protocols = ("ipv6", "ipv4",)
1267
1268 # Information about the format of the HTTP request can be found
1269 # after login on the provider user interface and here:
1270 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
1271
1272 url = "https://nsupdate.info/nic/update"
1273
1274 # TODO nsupdate.info can actually do this, but the functionality
1275 # has not been implemented here, yet.
1276 can_remove_records = False
1277
1278 # After a failed update, there will be no retries
1279 # https://bugzilla.ipfire.org/show_bug.cgi?id=10603
1280 holdoff_failure_days = None
1281
1282 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
1283 # and for the password a so called secret.
1284 @property
1285 def username(self):
1286 return self.get("hostname")
1287
1288 @property
1289 def password(self):
1290 return self.token or self.get("secret")
1291
1292 def prepare_request_data(self, proto):
1293 data = {
1294 "myip" : self.get_address(proto),
1295 }
1296
1297 return data
1298
1299
1300 class DDNSProviderOpenDNS(DDNSProtocolDynDNS2, DDNSProvider):
1301 handle = "opendns.com"
1302 name = "OpenDNS"
1303 website = "http://www.opendns.com"
1304
1305 # Detailed information about the update request and possible
1306 # response codes can be obtained from here:
1307 # https://support.opendns.com/entries/23891440
1308
1309 url = "https://updates.opendns.com/nic/update"
1310
1311 def prepare_request_data(self, proto):
1312 data = {
1313 "hostname" : self.hostname,
1314 "myip" : self.get_address(proto),
1315 }
1316
1317 return data
1318
1319
1320 class DDNSProviderOVH(DDNSProtocolDynDNS2, DDNSProvider):
1321 handle = "ovh.com"
1322 name = "OVH"
1323 website = "http://www.ovh.com/"
1324 protocols = ("ipv4",)
1325
1326 # OVH only provides very limited information about how to
1327 # update a DynDNS host. They only provide the update url
1328 # on the their german subpage.
1329 #
1330 # http://hilfe.ovh.de/DomainDynHost
1331
1332 url = "https://www.ovh.com/nic/update"
1333
1334 def prepare_request_data(self, proto):
1335 data = DDNSProtocolDynDNS2.prepare_request_data(self, proto)
1336 data.update({
1337 "system" : "dyndns",
1338 })
1339
1340 return data
1341
1342
1343 class DDNSProviderRegfish(DDNSProvider):
1344 handle = "regfish.com"
1345 name = "Regfish GmbH"
1346 website = "http://www.regfish.com/"
1347
1348 # A full documentation to the providers api can be found here
1349 # but is only available in german.
1350 # https://www.regfish.de/domains/dyndns/dokumentation
1351
1352 url = "https://dyndns.regfish.de/"
1353 can_remove_records = False
1354
1355 def update(self):
1356 data = {
1357 "fqdn" : self.hostname,
1358 }
1359
1360 # Check if we update an IPv6 address.
1361 address6 = self.get_address("ipv6")
1362 if address6:
1363 data["ipv6"] = address6
1364
1365 # Check if we update an IPv4 address.
1366 address4 = self.get_address("ipv4")
1367 if address4:
1368 data["ipv4"] = address4
1369
1370 # Raise an error if none address is given.
1371 if not data.has_key("ipv6") and not data.has_key("ipv4"):
1372 raise DDNSConfigurationError
1373
1374 # Check if a token has been set.
1375 if self.token:
1376 data["token"] = self.token
1377
1378 # Raise an error if no token and no useranem and password
1379 # are given.
1380 elif not self.username and not self.password:
1381 raise DDNSConfigurationError(_("No Auth details specified."))
1382
1383 # HTTP Basic Auth is only allowed if no token is used.
1384 if self.token:
1385 # Send update to the server.
1386 response = self.send_request(self.url, data=data)
1387 else:
1388 # Send update to the server.
1389 response = self.send_request(self.url, username=self.username, password=self.password,
1390 data=data)
1391
1392 # Get the full response message.
1393 output = response.read()
1394
1395 # Handle success messages.
1396 if "100" in output or "101" in output:
1397 return
1398
1399 # Handle error codes.
1400 if "401" or "402" in output:
1401 raise DDNSAuthenticationError
1402 elif "408" in output:
1403 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
1404 elif "409" in output:
1405 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
1406 elif "412" in output:
1407 raise DDNSRequestError(_("No valid FQDN was given."))
1408 elif "414" in output:
1409 raise DDNSInternalServerError
1410
1411 # If we got here, some other update error happened.
1412 raise DDNSUpdateError
1413
1414
1415 class DDNSProviderSelfhost(DDNSProtocolDynDNS2, DDNSProvider):
1416 handle = "selfhost.de"
1417 name = "Selfhost.de"
1418 website = "http://www.selfhost.de/"
1419 protocols = ("ipv4",)
1420
1421 url = "https://carol.selfhost.de/nic/update"
1422
1423 def prepare_request_data(self, proto):
1424 data = DDNSProtocolDynDNS2.prepare_request_data(self, proto)
1425 data.update({
1426 "hostname" : "1",
1427 })
1428
1429 return data
1430
1431
1432 class DDNSProviderSPDNS(DDNSProtocolDynDNS2, DDNSProvider):
1433 handle = "spdns.org"
1434 name = "SPDNS"
1435 website = "http://spdns.org/"
1436
1437 # Detailed information about request and response codes are provided
1438 # by the vendor. They are using almost the same mechanism and status
1439 # codes as dyndns.org so we can inherit all those stuff.
1440 #
1441 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
1442 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
1443
1444 url = "https://update.spdns.de/nic/update"
1445
1446 @property
1447 def username(self):
1448 return self.get("username") or self.hostname
1449
1450 @property
1451 def password(self):
1452 return self.get("password") or self.token
1453
1454
1455 class DDNSProviderStrato(DDNSProtocolDynDNS2, DDNSProvider):
1456 handle = "strato.com"
1457 name = "Strato AG"
1458 website = "http:/www.strato.com/"
1459 protocols = ("ipv4",)
1460
1461 # Information about the request and response can be obtained here:
1462 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
1463
1464 url = "https://dyndns.strato.com/nic/update"
1465
1466 def prepare_request_data(self, proto):
1467 data = DDNSProtocolDynDNS2.prepare_request_data(self, proto)
1468 data.update({
1469 "mx" : "NOCHG",
1470 "backupmx" : "NOCHG"
1471 })
1472
1473 return data
1474
1475
1476 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2, DDNSProvider):
1477 handle = "twodns.de"
1478 name = "TwoDNS"
1479 website = "http://www.twodns.de"
1480 protocols = ("ipv4",)
1481
1482 # Detailed information about the request can be found here
1483 # http://twodns.de/en/faqs
1484 # http://twodns.de/en/api
1485
1486 url = "https://update.twodns.de/update"
1487
1488 def prepare_request_data(self, proto):
1489 assert proto == "ipv4"
1490
1491 data = {
1492 "ip" : self.get_address(proto),
1493 "hostname" : self.hostname
1494 }
1495
1496 return data
1497
1498
1499 class DDNSProviderUdmedia(DDNSProtocolDynDNS2, DDNSProvider):
1500 handle = "udmedia.de"
1501 name = "Udmedia GmbH"
1502 website = "http://www.udmedia.de"
1503 protocols = ("ipv4",)
1504
1505 # Information about the request can be found here
1506 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1507
1508 url = "https://www.udmedia.de/nic/update"
1509
1510
1511 class DDNSProviderVariomedia(DDNSProtocolDynDNS2, DDNSProvider):
1512 handle = "variomedia.de"
1513 name = "Variomedia"
1514 website = "http://www.variomedia.de/"
1515 protocols = ("ipv6", "ipv4",)
1516
1517 # Detailed information about the request can be found here
1518 # https://dyndns.variomedia.de/
1519
1520 url = "https://dyndns.variomedia.de/nic/update"
1521
1522 def prepare_request_data(self, proto):
1523 data = {
1524 "hostname" : self.hostname,
1525 "myip" : self.get_address(proto),
1526 }
1527
1528 return data
1529
1530
1531 class DDNSProviderXLhost(DDNSProtocolDynDNS2, DDNSProvider):
1532 handle = "xlhost.de"
1533 name = "XLhost"
1534 website = "http://xlhost.de/"
1535 protocols = ("ipv4",)
1536
1537 # Information about the format of the HTTP request is to be found
1538 # here: https://xlhost.de/faq/index_html?topicId=CQA2ELIPO4SQ
1539
1540 url = "https://nsupdate.xlhost.de/"
1541
1542
1543 class DDNSProviderZoneedit(DDNSProvider):
1544 handle = "zoneedit.com"
1545 name = "Zoneedit"
1546 website = "http://www.zoneedit.com"
1547 protocols = ("ipv4",)
1548
1549 # Detailed information about the request and the response codes can be
1550 # obtained here:
1551 # http://www.zoneedit.com/doc/api/other.html
1552 # http://www.zoneedit.com/faq.html
1553
1554 url = "https://dynamic.zoneedit.com/auth/dynamic.html"
1555
1556 def update_protocol(self, proto):
1557 data = {
1558 "dnsto" : self.get_address(proto),
1559 "host" : self.hostname
1560 }
1561
1562 # Send update to the server.
1563 response = self.send_request(self.url, username=self.username, password=self.password,
1564 data=data)
1565
1566 # Get the full response message.
1567 output = response.read()
1568
1569 # Handle success messages.
1570 if output.startswith("<SUCCESS"):
1571 return
1572
1573 # Handle error codes.
1574 if output.startswith("invalid login"):
1575 raise DDNSAuthenticationError
1576 elif output.startswith("<ERROR CODE=\"704\""):
1577 raise DDNSRequestError(_("No valid FQDN was given."))
1578 elif output.startswith("<ERROR CODE=\"702\""):
1579 raise DDNSRequestError(_("Too frequent update requests have been sent."))
1580
1581 # If we got here, some other update error happened.
1582 raise DDNSUpdateError
1583
1584
1585 class DDNSProviderDNSmadeEasy(DDNSProvider):
1586 handle = "dnsmadeeasy.com"
1587 name = "DNSmadeEasy.com"
1588 website = "http://www.dnsmadeeasy.com/"
1589 protocols = ("ipv4",)
1590
1591 # DNS Made Easy Nameserver Provider also offering Dynamic DNS
1592 # Documentation can be found here:
1593 # http://www.dnsmadeeasy.com/dynamic-dns/
1594
1595 url = "https://cp.dnsmadeeasy.com/servlet/updateip?"
1596 can_remove_records = False
1597
1598 def update_protocol(self, proto):
1599 data = {
1600 "ip" : self.get_address(proto),
1601 "id" : self.hostname,
1602 "username" : self.username,
1603 "password" : self.password,
1604 }
1605
1606 # Send update to the server.
1607 response = self.send_request(self.url, data=data)
1608
1609 # Get the full response message.
1610 output = response.read()
1611
1612 # Handle success messages.
1613 if output.startswith("success") or output.startswith("error-record-ip-same"):
1614 return
1615
1616 # Handle error codes.
1617 if output.startswith("error-auth-suspend"):
1618 raise DDNSRequestError(_("Account has been suspended."))
1619
1620 elif output.startswith("error-auth-voided"):
1621 raise DDNSRequestError(_("Account has been revoked."))
1622
1623 elif output.startswith("error-record-invalid"):
1624 raise DDNSRequestError(_("Specified host does not exist."))
1625
1626 elif output.startswith("error-auth"):
1627 raise DDNSAuthenticationError
1628
1629 # If we got here, some other update error happened.
1630 raise DDNSUpdateError(_("Server response: %s") % output)
1631
1632
1633 class DDNSProviderZZZZ(DDNSProvider):
1634 handle = "zzzz.io"
1635 name = "zzzz"
1636 website = "https://zzzz.io"
1637 protocols = ("ipv6", "ipv4",)
1638
1639 # Detailed information about the update request can be found here:
1640 # https://zzzz.io/faq/
1641
1642 # Details about the possible response codes have been provided in the bugtracker:
1643 # https://bugzilla.ipfire.org/show_bug.cgi?id=10584#c2
1644
1645 url = "https://zzzz.io/api/v1/update"
1646 can_remove_records = False
1647
1648 def update_protocol(self, proto):
1649 data = {
1650 "ip" : self.get_address(proto),
1651 "token" : self.token,
1652 }
1653
1654 if proto == "ipv6":
1655 data["type"] = "aaaa"
1656
1657 # zzzz uses the host from the full hostname as part
1658 # of the update url.
1659 host, domain = self.hostname.split(".", 1)
1660
1661 # Add host value to the update url.
1662 url = "%s/%s" % (self.url, host)
1663
1664 # Send update to the server.
1665 try:
1666 response = self.send_request(url, data=data)
1667
1668 # Handle error codes.
1669 except DDNSNotFound:
1670 raise DDNSRequestError(_("Invalid hostname specified"))
1671
1672 # Handle success messages.
1673 if response.code == 200:
1674 return
1675
1676 # If we got here, some other update error happened.
1677 raise DDNSUpdateError