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