]> git.ipfire.org Git - oddments/ddns.git/blame - src/ddns/providers.py
Bump version to 006
[oddments/ddns.git] / src / ddns / providers.py
CommitLineData
f22ab085 1#!/usr/bin/python
3fdcb9d1
MT
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###############################################################################
f22ab085 21
37e24fbf 22import datetime
7399fc5b 23import logging
64d3fad4 24import os
a892c594 25import subprocess
3b16fdb1 26import urllib2
d1cd57eb 27import xml.dom.minidom
7399fc5b
MT
28
29from i18n import _
30
f22ab085
MT
31# Import all possible exception types.
32from .errors import *
33
7399fc5b
MT
34logger = logging.getLogger("ddns.providers")
35logger.propagate = 1
36
adfe6272
MT
37_providers = {}
38
39def get():
40 """
41 Returns a dict with all automatically registered providers.
42 """
43 return _providers.copy()
44
f22ab085 45class DDNSProvider(object):
6a11646e
MT
46 # A short string that uniquely identifies
47 # this provider.
48 handle = None
f22ab085 49
6a11646e
MT
50 # The full name of the provider.
51 name = None
f22ab085 52
6a11646e
MT
53 # A weburl to the homepage of the provider.
54 # (Where to register a new account?)
55 website = None
f22ab085 56
6a11646e
MT
57 # A list of supported protocols.
58 protocols = ("ipv6", "ipv4")
f22ab085
MT
59
60 DEFAULT_SETTINGS = {}
61
37e24fbf
MT
62 # holdoff time - Number of days no update is performed unless
63 # the IP address has changed.
64 holdoff_days = 30
65
112d3fb8
MT
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
29c8c9c6
MT
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
adfe6272
MT
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
64d3fad4
MT
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
f22ab085
MT
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
37e24fbf
MT
114 @property
115 def db(self):
116 return self.core.db
117
f22ab085
MT
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
46687828
SS
145 @property
146 def token(self):
147 """
148 Fast access to the token.
149 """
150 return self.get("token")
151
9da3e685
MT
152 def __call__(self, force=False):
153 if force:
c3888f15 154 logger.debug(_("Updating %s forced") % self.hostname)
9da3e685 155
112d3fb8
MT
156 # Do nothing if the last update has failed or no update is required
157 elif self.has_failure or not self.requires_update:
7399fc5b
MT
158 return
159
160 # Execute the update.
37e24fbf
MT
161 try:
162 self.update()
163
164 # In case of any errors, log the failed request and
165 # raise the exception.
166 except DDNSError as e:
167 self.core.db.log_failure(self.hostname, e)
168 raise
5f402f36 169
12b3818b
MT
170 logger.info(_("Dynamic DNS update for %(hostname)s (%(provider)s) successful") % \
171 { "hostname" : self.hostname, "provider" : self.name })
37e24fbf 172 self.core.db.log_success(self.hostname)
12b3818b 173
5f402f36 174 def update(self):
d45139f6
MT
175 for protocol in self.protocols:
176 if self.have_address(protocol):
177 self.update_protocol(protocol)
29c8c9c6 178 elif self.can_remove_records:
d45139f6
MT
179 self.remove_protocol(protocol)
180
181 def update_protocol(self, proto):
f22ab085
MT
182 raise NotImplementedError
183
d45139f6 184 def remove_protocol(self, proto):
29c8c9c6
MT
185 if not self.can_remove_records:
186 raise RuntimeError, "can_remove_records is enabled, but remove_protocol() not implemented"
d45139f6 187
29c8c9c6 188 raise NotImplementedError
d45139f6 189
37e24fbf
MT
190 @property
191 def requires_update(self):
192 # If the IP addresses have changed, an update is required
193 if self.ip_address_changed(self.protocols):
194 logger.debug(_("An update for %(hostname)s (%(provider)s)"
195 " is performed because of an IP address change") % \
196 { "hostname" : self.hostname, "provider" : self.name })
197
198 return True
199
200 # If the holdoff time has expired, an update is required, too
201 if self.holdoff_time_expired():
202 logger.debug(_("An update for %(hostname)s (%(provider)s)"
203 " is performed because the holdoff time has expired") % \
204 { "hostname" : self.hostname, "provider" : self.name })
205
206 return True
207
208 # Otherwise, we don't need to perform an update
209 logger.debug(_("No update required for %(hostname)s (%(provider)s)") % \
210 { "hostname" : self.hostname, "provider" : self.name })
211
212 return False
213
112d3fb8
MT
214 @property
215 def has_failure(self):
216 """
217 Returns True when the last update has failed and no retry
218 should be performed, yet.
219 """
220 last_status = self.db.last_update_status(self.hostname)
221
222 # Return False if the last update has not failed.
223 if not last_status == "failure":
224 return False
225
64018439
MT
226 # If there is no holdoff time, we won't update ever again.
227 if self.holdoff_failure_days is None:
228 logger.warning(_("An update has not been performed because earlier updates failed for %s") \
229 % self.hostname)
230 logger.warning(_("There will be no retries"))
231
232 return True
233
112d3fb8
MT
234 # Determine when the holdoff time ends
235 last_update = self.db.last_update(self.hostname, status=last_status)
236 holdoff_end = last_update + datetime.timedelta(days=self.holdoff_failure_days)
237
238 now = datetime.datetime.utcnow()
239 if now < holdoff_end:
240 failure_message = self.db.last_update_failure_message(self.hostname)
241
242 logger.warning(_("An update has not been performed because earlier updates failed for %s") \
243 % self.hostname)
244
245 if failure_message:
246 logger.warning(_("Last failure message:"))
247
248 for line in failure_message.splitlines():
249 logger.warning(" %s" % line)
250
251 logger.warning(_("Further updates will be withheld until %s") % holdoff_end)
252
253 return True
254
255 return False
256
37e24fbf 257 def ip_address_changed(self, protos):
7399fc5b
MT
258 """
259 Returns True if this host is already up to date
260 and does not need to change the IP address on the
261 name server.
262 """
263 for proto in protos:
264 addresses = self.core.system.resolve(self.hostname, proto)
7399fc5b
MT
265 current_address = self.get_address(proto)
266
29c8c9c6
MT
267 # Handle if the system has not got any IP address from a protocol
268 # (i.e. had full dual-stack connectivity which it has not any more)
269 if current_address is None:
270 # If addresses still exists in the DNS system and if this provider
271 # is able to remove records, we will do that.
272 if addresses and self.can_remove_records:
273 return True
274
275 # Otherwise, we cannot go on...
38d81db4
MT
276 continue
277
7399fc5b 278 if not current_address in addresses:
37e24fbf
MT
279 return True
280
281 return False
7399fc5b 282
37e24fbf
MT
283 def holdoff_time_expired(self):
284 """
285 Returns true if the holdoff time has expired
286 and the host requires an update
287 """
288 # If no holdoff days is defined, we cannot go on
289 if not self.holdoff_days:
290 return False
291
292 # Get the timestamp of the last successfull update
112d3fb8 293 last_update = self.db.last_update(self.hostname, status="success")
37e24fbf
MT
294
295 # If no timestamp has been recorded, no update has been
296 # performed. An update should be performed now.
297 if not last_update:
298 return True
7399fc5b 299
37e24fbf
MT
300 # Determine when the holdoff time ends
301 holdoff_end = last_update + datetime.timedelta(days=self.holdoff_days)
302
303 now = datetime.datetime.utcnow()
304
305 if now >= holdoff_end:
306 logger.debug("The holdoff time has expired for %s" % self.hostname)
307 return True
308 else:
309 logger.debug("Updates for %s are held off until %s" % \
310 (self.hostname, holdoff_end))
311 return False
7399fc5b 312
f22ab085
MT
313 def send_request(self, *args, **kwargs):
314 """
315 Proxy connection to the send request
316 method.
317 """
318 return self.core.system.send_request(*args, **kwargs)
319
e3c70807 320 def get_address(self, proto, default=None):
f22ab085
MT
321 """
322 Proxy method to get the current IP address.
323 """
e3c70807 324 return self.core.system.get_address(proto) or default
f22ab085 325
d45139f6
MT
326 def have_address(self, proto):
327 """
328 Returns True if an IP address for the given protocol
329 is known and usable.
330 """
331 address = self.get_address(proto)
332
333 if address:
334 return True
335
336 return False
337
f22ab085 338
5d4bec40
MT
339class DDNSProtocolDynDNS2(object):
340 """
341 This is an abstract class that implements the DynDNS updater
342 protocol version 2. As this is a popular way to update dynamic
343 DNS records, this class is supposed make the provider classes
344 shorter and simpler.
345 """
346
347 # Information about the format of the request is to be found
486c1b9d 348 # http://dyn.com/support/developers/api/perform-update/
5d4bec40
MT
349 # http://dyn.com/support/developers/api/return-codes/
350
29c8c9c6
MT
351 # The DynDNS protocol version 2 does not allow to remove records
352 can_remove_records = False
353
d45139f6 354 def prepare_request_data(self, proto):
5d4bec40
MT
355 data = {
356 "hostname" : self.hostname,
d45139f6 357 "myip" : self.get_address(proto),
5d4bec40
MT
358 }
359
360 return data
361
d45139f6
MT
362 def update_protocol(self, proto):
363 data = self.prepare_request_data(proto)
364
365 return self.send_request(data)
5d4bec40 366
d45139f6 367 def send_request(self, data):
5d4bec40 368 # Send update to the server.
d45139f6 369 response = DDNSProvider.send_request(self, self.url, data=data,
5d4bec40
MT
370 username=self.username, password=self.password)
371
372 # Get the full response message.
373 output = response.read()
374
375 # Handle success messages.
376 if output.startswith("good") or output.startswith("nochg"):
377 return
378
379 # Handle error codes.
380 if output == "badauth":
381 raise DDNSAuthenticationError
af97e369 382 elif output == "abuse":
5d4bec40
MT
383 raise DDNSAbuseError
384 elif output == "notfqdn":
385 raise DDNSRequestError(_("No valid FQDN was given."))
386 elif output == "nohost":
387 raise DDNSRequestError(_("Specified host does not exist."))
388 elif output == "911":
389 raise DDNSInternalServerError
390 elif output == "dnserr":
391 raise DDNSInternalServerError(_("DNS error encountered."))
6ddfd5c7
SS
392 elif output == "badagent":
393 raise DDNSBlockedError
5d4bec40
MT
394
395 # If we got here, some other update error happened.
396 raise DDNSUpdateError(_("Server response: %s") % output)
397
398
78c9780b
SS
399class DDNSResponseParserXML(object):
400 """
401 This class provides a parser for XML responses which
402 will be sent by various providers. This class uses the python
403 shipped XML minidom module to walk through the XML tree and return
404 a requested element.
405 """
406
407 def get_xml_tag_value(self, document, content):
408 # Send input to the parser.
409 xmldoc = xml.dom.minidom.parseString(document)
410
411 # Get XML elements by the given content.
412 element = xmldoc.getElementsByTagName(content)
413
414 # If no element has been found, we directly can return None.
415 if not element:
416 return None
417
418 # Only get the first child from an element, even there are more than one.
419 firstchild = element[0].firstChild
420
421 # Get the value of the child.
422 value = firstchild.nodeValue
423
424 # Return the value.
425 return value
426
427
3b16fdb1 428class DDNSProviderAllInkl(DDNSProvider):
6a11646e
MT
429 handle = "all-inkl.com"
430 name = "All-inkl.com"
431 website = "http://all-inkl.com/"
432 protocols = ("ipv4",)
3b16fdb1
SS
433
434 # There are only information provided by the vendor how to
435 # perform an update on a FRITZ Box. Grab requried informations
436 # from the net.
437 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
438
439 url = "http://dyndns.kasserver.com"
29c8c9c6 440 can_remove_records = False
3b16fdb1
SS
441
442 def update(self):
3b16fdb1
SS
443 # There is no additional data required so we directly can
444 # send our request.
536e87d1 445 response = self.send_request(self.url, username=self.username, password=self.password)
3b16fdb1
SS
446
447 # Get the full response message.
448 output = response.read()
449
450 # Handle success messages.
451 if output.startswith("good") or output.startswith("nochg"):
452 return
453
454 # If we got here, some other update error happened.
455 raise DDNSUpdateError
456
457
a892c594
MT
458class DDNSProviderBindNsupdate(DDNSProvider):
459 handle = "nsupdate"
460 name = "BIND nsupdate utility"
461 website = "http://en.wikipedia.org/wiki/Nsupdate"
462
463 DEFAULT_TTL = 60
464
64d3fad4
MT
465 @staticmethod
466 def supported():
467 # Search if the nsupdate utility is available
468 paths = os.environ.get("PATH")
469
470 for path in paths.split(":"):
471 executable = os.path.join(path, "nsupdate")
472
473 if os.path.exists(executable):
474 return True
475
476 return False
477
a892c594
MT
478 def update(self):
479 scriptlet = self.__make_scriptlet()
480
481 # -v enables TCP hence we transfer keys and other data that may
482 # exceed the size of one packet.
483 # -t sets the timeout
484 command = ["nsupdate", "-v", "-t", "60"]
485
486 p = subprocess.Popen(command, shell=True,
487 stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
488 )
489 stdout, stderr = p.communicate(scriptlet)
490
491 if p.returncode == 0:
492 return
493
494 raise DDNSError("nsupdate terminated with error code: %s\n %s" % (p.returncode, stderr))
495
496 def __make_scriptlet(self):
497 scriptlet = []
498
499 # Set a different server the update is sent to.
500 server = self.get("server", None)
501 if server:
502 scriptlet.append("server %s" % server)
503
97998aac
MW
504 # Set the DNS zone the host should be added to.
505 zone = self.get("zone", None)
506 if zone:
507 scriptlet.append("zone %s" % zone)
508
a892c594
MT
509 key = self.get("key", None)
510 if key:
511 secret = self.get("secret")
512
513 scriptlet.append("key %s %s" % (key, secret))
514
515 ttl = self.get("ttl", self.DEFAULT_TTL)
516
517 # Perform an update for each supported protocol.
518 for rrtype, proto in (("AAAA", "ipv6"), ("A", "ipv4")):
519 address = self.get_address(proto)
520 if not address:
521 continue
522
523 scriptlet.append("update delete %s. %s" % (self.hostname, rrtype))
524 scriptlet.append("update add %s. %s %s %s" % \
525 (self.hostname, ttl, rrtype, address))
526
527 # Send the actions to the server.
528 scriptlet.append("send")
529 scriptlet.append("quit")
530
531 logger.debug(_("Scriptlet:"))
532 for line in scriptlet:
533 # Masquerade the line with the secret key.
534 if line.startswith("key"):
535 line = "key **** ****"
536
537 logger.debug(" %s" % line)
538
539 return "\n".join(scriptlet)
540
541
78046ffe
SS
542class DDNSProviderChangeIP(DDNSProvider):
543 handle = "changeip.com"
544 name = "ChangeIP.com"
545 website = "https://changeip.com"
546 protocols = ("ipv4",)
547
548 # Detailed information about the update api can be found here.
549 # http://www.changeip.com/accounts/knowledgebase.php?action=displayarticle&id=34
550
551 url = "https://nic.changeip.com/nic/update"
552 can_remove_records = False
553
554 def update_protocol(self, proto):
555 data = {
556 "hostname" : self.hostname,
557 "myip" : self.get_address(proto),
558 }
559
560 # Send update to the server.
561 try:
562 response = self.send_request(self.url, username=self.username, password=self.password,
563 data=data)
564
565 # Handle error codes.
566 except urllib2.HTTPError, e:
567 if e.code == 422:
568 raise DDNSRequestError(_("Domain not found."))
569
570 raise
571
572 # Handle success message.
573 if response.code == 200:
574 return
575
576 # If we got here, some other update error happened.
577 raise DDNSUpdateError(_("Server response: %s") % output)
578
579
f1332a16
SS
580class DDNSProviderDDNSS(DDNSProvider):
581 handle = "ddnss.de"
582 name = "DDNSS"
583 website = "http://www.ddnss.de"
584 protocols = ("ipv4",)
585
586 # Detailed information about how to send the update request and possible response
587 # codes can be obtained from here.
588 # http://www.ddnss.de/info.php
589 # http://www.megacomputing.de/2014/08/dyndns-service-response-time/#more-919
590
591 url = "http://www.ddnss.de/upd.php"
592 can_remove_records = False
593
594 def update_protocol(self, proto):
595 data = {
596 "ip" : self.get_address(proto),
597 "host" : self.hostname,
598 }
599
600 # Check if a token has been set.
601 if self.token:
602 data["key"] = self.token
603
604 # Check if username and hostname are given.
605 elif self.username and self.password:
606 data.update({
607 "user" : self.username,
608 "pwd" : self.password,
609 })
610
611 # Raise an error if no auth details are given.
612 else:
613 raise DDNSConfigurationError
614
615 # Send update to the server.
616 response = self.send_request(self.url, data=data)
617
618 # This provider sends the response code as part of the header.
619 header = response.info()
620
621 # Get status information from the header.
622 output = header.getheader('ddnss-response')
623
624 # Handle success messages.
625 if output == "good" or output == "nochg":
626 return
627
628 # Handle error codes.
629 if output == "badauth":
630 raise DDNSAuthenticationError
631 elif output == "notfqdn":
632 raise DDNSRequestError(_("No valid FQDN was given."))
633 elif output == "nohost":
634 raise DDNSRequestError(_("Specified host does not exist."))
635 elif output == "911":
636 raise DDNSInternalServerError
637 elif output == "dnserr":
638 raise DDNSInternalServerError(_("DNS error encountered."))
639 elif output == "disabled":
640 raise DDNSRequestError(_("Account disabled or locked."))
641
642 # If we got here, some other update error happened.
643 raise DDNSUpdateError
644
645
f3cf1f70 646class DDNSProviderDHS(DDNSProvider):
6a11646e
MT
647 handle = "dhs.org"
648 name = "DHS International"
649 website = "http://dhs.org/"
650 protocols = ("ipv4",)
f3cf1f70
SS
651
652 # No information about the used update api provided on webpage,
653 # grabed from source code of ez-ipudate.
b2b05ef3 654
f3cf1f70 655 url = "http://members.dhs.org/nic/hosts"
29c8c9c6 656 can_remove_records = False
f3cf1f70 657
d45139f6 658 def update_protocol(self, proto):
f3cf1f70
SS
659 data = {
660 "domain" : self.hostname,
d45139f6 661 "ip" : self.get_address(proto),
f3cf1f70
SS
662 "hostcmd" : "edit",
663 "hostcmdstage" : "2",
664 "type" : "4",
665 }
666
667 # Send update to the server.
175c9b80 668 response = self.send_request(self.url, username=self.username, password=self.password,
f3cf1f70
SS
669 data=data)
670
671 # Handle success messages.
672 if response.code == 200:
673 return
674
f3cf1f70
SS
675 # If we got here, some other update error happened.
676 raise DDNSUpdateError
677
678
39301272 679class DDNSProviderDNSpark(DDNSProvider):
6a11646e
MT
680 handle = "dnspark.com"
681 name = "DNS Park"
682 website = "http://dnspark.com/"
683 protocols = ("ipv4",)
39301272
SS
684
685 # Informations to the used api can be found here:
686 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
b2b05ef3 687
39301272 688 url = "https://control.dnspark.com/api/dynamic/update.php"
29c8c9c6 689 can_remove_records = False
39301272 690
d45139f6 691 def update_protocol(self, proto):
39301272
SS
692 data = {
693 "domain" : self.hostname,
d45139f6 694 "ip" : self.get_address(proto),
39301272
SS
695 }
696
697 # Send update to the server.
175c9b80 698 response = self.send_request(self.url, username=self.username, password=self.password,
39301272
SS
699 data=data)
700
701 # Get the full response message.
702 output = response.read()
703
704 # Handle success messages.
705 if output.startswith("ok") or output.startswith("nochange"):
706 return
707
708 # Handle error codes.
709 if output == "unauth":
710 raise DDNSAuthenticationError
711 elif output == "abuse":
712 raise DDNSAbuseError
713 elif output == "blocked":
714 raise DDNSBlockedError
715 elif output == "nofqdn":
716 raise DDNSRequestError(_("No valid FQDN was given."))
717 elif output == "nohost":
718 raise DDNSRequestError(_("Invalid hostname specified."))
719 elif output == "notdyn":
720 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
721 elif output == "invalid":
722 raise DDNSRequestError(_("Invalid IP address has been sent."))
723
724 # If we got here, some other update error happened.
725 raise DDNSUpdateError
726
43b2cd59
SS
727
728class DDNSProviderDtDNS(DDNSProvider):
6a11646e
MT
729 handle = "dtdns.com"
730 name = "DtDNS"
731 website = "http://dtdns.com/"
732 protocols = ("ipv4",)
43b2cd59
SS
733
734 # Information about the format of the HTTPS request is to be found
735 # http://www.dtdns.com/dtsite/updatespec
b2b05ef3 736
43b2cd59 737 url = "https://www.dtdns.com/api/autodns.cfm"
29c8c9c6 738 can_remove_records = False
43b2cd59 739
d45139f6 740 def update_protocol(self, proto):
43b2cd59 741 data = {
d45139f6 742 "ip" : self.get_address(proto),
43b2cd59
SS
743 "id" : self.hostname,
744 "pw" : self.password
745 }
746
747 # Send update to the server.
748 response = self.send_request(self.url, data=data)
749
750 # Get the full response message.
751 output = response.read()
752
753 # Remove all leading and trailing whitespace.
754 output = output.strip()
755
756 # Handle success messages.
757 if "now points to" in output:
758 return
759
760 # Handle error codes.
761 if output == "No hostname to update was supplied.":
762 raise DDNSRequestError(_("No hostname specified."))
763
764 elif output == "The hostname you supplied is not valid.":
765 raise DDNSRequestError(_("Invalid hostname specified."))
766
767 elif output == "The password you supplied is not valid.":
768 raise DDNSAuthenticationError
769
770 elif output == "Administration has disabled this account.":
771 raise DDNSRequestError(_("Account has been disabled."))
772
773 elif output == "Illegal character in IP.":
774 raise DDNSRequestError(_("Invalid IP address has been sent."))
775
776 elif output == "Too many failed requests.":
777 raise DDNSRequestError(_("Too many failed requests."))
778
779 # If we got here, some other update error happened.
780 raise DDNSUpdateError
781
782
5d4bec40 783class DDNSProviderDynDNS(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
784 handle = "dyndns.org"
785 name = "Dyn"
786 website = "http://dyn.com/dns/"
787 protocols = ("ipv4",)
bfed6701
SS
788
789 # Information about the format of the request is to be found
790 # http://http://dyn.com/support/developers/api/perform-update/
791 # http://dyn.com/support/developers/api/return-codes/
b2b05ef3 792
bfed6701
SS
793 url = "https://members.dyndns.org/nic/update"
794
bfed6701 795
5d4bec40 796class DDNSProviderDynU(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
797 handle = "dynu.com"
798 name = "Dynu"
799 website = "http://dynu.com/"
800 protocols = ("ipv6", "ipv4",)
3a8407fa
SS
801
802 # Detailed information about the request and response codes
803 # are available on the providers webpage.
804 # http://dynu.com/Default.aspx?page=dnsapi
805
806 url = "https://api.dynu.com/nic/update"
807
d45139f6
MT
808 # DynU sends the IPv6 and IPv4 address in one request
809
810 def update(self):
811 data = DDNSProtocolDynDNS2.prepare_request_data(self, "ipv4")
54d3efc8
MT
812
813 # This one supports IPv6
cdc078dc
SS
814 myipv6 = self.get_address("ipv6")
815
816 # Add update information if we have an IPv6 address.
817 if myipv6:
818 data["myipv6"] = myipv6
54d3efc8 819
304026a3 820 self.send_request(data)
3a8407fa
SS
821
822
5420343a 823class DDNSProviderEasyDNS(DDNSProvider):
5d4bec40
MT
824 handle = "easydns.com"
825 name = "EasyDNS"
826 website = "http://www.easydns.com/"
827 protocols = ("ipv4",)
ee071271 828
5420343a
SS
829 # Detailed information about the request and response codes
830 # (API 1.3) are available on the providers webpage.
831 # https://fusion.easydns.com/index.php?/Knowledgebase/Article/View/102/7/dynamic-dns
ee071271
SS
832
833 url = "http://api.cp.easydns.com/dyn/tomato.php"
834
5420343a
SS
835 def update_protocol(self, proto):
836 data = {
837 "myip" : self.get_address(proto, "-"),
838 "hostname" : self.hostname,
839 }
840
841 # Send update to the server.
842 response = self.send_request(self.url, data=data,
843 username=self.username, password=self.password)
844
845 # Get the full response message.
846 output = response.read()
847
848 # Remove all leading and trailing whitespace.
849 output = output.strip()
850
851 # Handle success messages.
852 if output.startswith("NOERROR"):
853 return
854
855 # Handle error codes.
856 if output.startswith("NOACCESS"):
857 raise DDNSAuthenticationError
858
859 elif output.startswith("NOSERVICE"):
860 raise DDNSRequestError(_("Dynamic DNS is not turned on for this domain."))
861
862 elif output.startswith("ILLEGAL INPUT"):
863 raise DDNSRequestError(_("Invalid data has been sent."))
864
865 elif output.startswith("TOOSOON"):
866 raise DDNSRequestError(_("Too frequent update requests have been sent."))
867
868 # If we got here, some other update error happened.
869 raise DDNSUpdateError
870
ee071271 871
90fe8843
CE
872class DDNSProviderDomopoli(DDNSProtocolDynDNS2, DDNSProvider):
873 handle = "domopoli.de"
874 name = "domopoli.de"
875 website = "http://domopoli.de/"
876 protocols = ("ipv4",)
877
878 # https://www.domopoli.de/?page=howto#DynDns_start
879
880 url = "http://dyndns.domopoli.de/nic/update"
881
882
a197d1a6
SS
883class DDNSProviderDynsNet(DDNSProvider):
884 handle = "dyns.net"
885 name = "DyNS"
886 website = "http://www.dyns.net/"
887 protocols = ("ipv4",)
29c8c9c6 888 can_remove_records = False
a197d1a6
SS
889
890 # There is very detailed informatio about how to send the update request and
891 # the possible response codes. (Currently we are using the v1.1 proto)
892 # http://www.dyns.net/documentation/technical/protocol/
893
894 url = "http://www.dyns.net/postscript011.php"
895
d45139f6 896 def update_protocol(self, proto):
a197d1a6 897 data = {
d45139f6 898 "ip" : self.get_address(proto),
a197d1a6
SS
899 "host" : self.hostname,
900 "username" : self.username,
901 "password" : self.password,
902 }
903
904 # Send update to the server.
905 response = self.send_request(self.url, data=data)
906
907 # Get the full response message.
908 output = response.read()
909
910 # Handle success messages.
911 if output.startswith("200"):
912 return
913
914 # Handle error codes.
915 if output.startswith("400"):
916 raise DDNSRequestError(_("Malformed request has been sent."))
917 elif output.startswith("401"):
918 raise DDNSAuthenticationError
919 elif output.startswith("402"):
920 raise DDNSRequestError(_("Too frequent update requests have been sent."))
921 elif output.startswith("403"):
922 raise DDNSInternalServerError
923
924 # If we got here, some other update error happened.
925 raise DDNSUpdateError(_("Server response: %s") % output)
926
927
35216523
SS
928class DDNSProviderEnomCom(DDNSResponseParserXML, DDNSProvider):
929 handle = "enom.com"
930 name = "eNom Inc."
931 website = "http://www.enom.com/"
d45139f6 932 protocols = ("ipv4",)
35216523
SS
933
934 # There are very detailed information about how to send an update request and
935 # the respone codes.
936 # http://www.enom.com/APICommandCatalog/
937
938 url = "https://dynamic.name-services.com/interface.asp"
29c8c9c6 939 can_remove_records = False
35216523 940
d45139f6 941 def update_protocol(self, proto):
35216523
SS
942 data = {
943 "command" : "setdnshost",
944 "responsetype" : "xml",
d45139f6 945 "address" : self.get_address(proto),
35216523
SS
946 "domainpassword" : self.password,
947 "zone" : self.hostname
948 }
949
950 # Send update to the server.
951 response = self.send_request(self.url, data=data)
952
953 # Get the full response message.
954 output = response.read()
955
956 # Handle success messages.
957 if self.get_xml_tag_value(output, "ErrCount") == "0":
958 return
959
960 # Handle error codes.
961 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
962
963 if errorcode == "304155":
964 raise DDNSAuthenticationError
965 elif errorcode == "304153":
966 raise DDNSRequestError(_("Domain not found."))
967
968 # If we got here, some other update error happened.
969 raise DDNSUpdateError
970
971
ab4e352e
SS
972class DDNSProviderEntryDNS(DDNSProvider):
973 handle = "entrydns.net"
974 name = "EntryDNS"
975 website = "http://entrydns.net/"
976 protocols = ("ipv4",)
977
978 # Some very tiny details about their so called "Simple API" can be found
979 # here: https://entrydns.net/help
980 url = "https://entrydns.net/records/modify"
29c8c9c6 981 can_remove_records = False
ab4e352e 982
d45139f6 983 def update_protocol(self, proto):
ab4e352e 984 data = {
d45139f6 985 "ip" : self.get_address(proto),
ab4e352e
SS
986 }
987
988 # Add auth token to the update url.
989 url = "%s/%s" % (self.url, self.token)
990
991 # Send update to the server.
992 try:
babc5e6d 993 response = self.send_request(url, data=data)
ab4e352e
SS
994
995 # Handle error codes
996 except urllib2.HTTPError, e:
997 if e.code == 404:
998 raise DDNSAuthenticationError
999
1000 elif e.code == 422:
1001 raise DDNSRequestError(_("An invalid IP address was submitted"))
1002
1003 raise
1004
1005 # Handle success messages.
1006 if response.code == 200:
1007 return
1008
1009 # If we got here, some other update error happened.
1010 raise DDNSUpdateError
1011
1012
aa21a4c6 1013class DDNSProviderFreeDNSAfraidOrg(DDNSProvider):
6a11646e
MT
1014 handle = "freedns.afraid.org"
1015 name = "freedns.afraid.org"
1016 website = "http://freedns.afraid.org/"
aa21a4c6
SS
1017
1018 # No information about the request or response could be found on the vendor
1019 # page. All used values have been collected by testing.
1020 url = "https://freedns.afraid.org/dynamic/update.php"
29c8c9c6 1021 can_remove_records = False
aa21a4c6 1022
d45139f6 1023 def update_protocol(self, proto):
aa21a4c6 1024 data = {
d45139f6 1025 "address" : self.get_address(proto),
aa21a4c6
SS
1026 }
1027
1028 # Add auth token to the update url.
1029 url = "%s?%s" % (self.url, self.token)
1030
1031 # Send update to the server.
1032 response = self.send_request(url, data=data)
1033
a204b107
SS
1034 # Get the full response message.
1035 output = response.read()
1036
1037 # Handle success messages.
aa21a4c6
SS
1038 if output.startswith("Updated") or "has not changed" in output:
1039 return
1040
1041 # Handle error codes.
1042 if output == "ERROR: Unable to locate this record":
1043 raise DDNSAuthenticationError
1044 elif "is an invalid IP address" in output:
1045 raise DDNSRequestError(_("Invalid IP address has been sent."))
1046
3b524cf2
SS
1047 # If we got here, some other update error happened.
1048 raise DDNSUpdateError
1049
aa21a4c6 1050
a08c1b72 1051class DDNSProviderLightningWireLabs(DDNSProvider):
6a11646e 1052 handle = "dns.lightningwirelabs.com"
fb115fdc 1053 name = "Lightning Wire Labs DNS Service"
6a11646e 1054 website = "http://dns.lightningwirelabs.com/"
a08c1b72
SS
1055
1056 # Information about the format of the HTTPS request is to be found
1057 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
b2b05ef3 1058
a08c1b72
SS
1059 url = "https://dns.lightningwirelabs.com/update"
1060
5f402f36 1061 def update(self):
a08c1b72
SS
1062 data = {
1063 "hostname" : self.hostname,
e3c70807
MT
1064 "address6" : self.get_address("ipv6", "-"),
1065 "address4" : self.get_address("ipv4", "-"),
a08c1b72
SS
1066 }
1067
a08c1b72
SS
1068 # Check if a token has been set.
1069 if self.token:
1070 data["token"] = self.token
1071
1072 # Check for username and password.
1073 elif self.username and self.password:
1074 data.update({
1075 "username" : self.username,
1076 "password" : self.password,
1077 })
1078
1079 # Raise an error if no auth details are given.
1080 else:
1081 raise DDNSConfigurationError
1082
1083 # Send update to the server.
cb455540 1084 response = self.send_request(self.url, data=data)
a08c1b72
SS
1085
1086 # Handle success messages.
1087 if response.code == 200:
1088 return
1089
a08c1b72
SS
1090 # If we got here, some other update error happened.
1091 raise DDNSUpdateError
1092
1093
446e42af
GH
1094class DDNSProviderMyOnlinePortal(DDNSProtocolDynDNS2, DDNSProvider):
1095 handle = "myonlineportal.net"
1096 name = "myonlineportal.net"
1097 website = "https:/myonlineportal.net/"
1098
1099 # Information about the request and response can be obtained here:
1100 # https://myonlineportal.net/howto_dyndns
1101
1102 url = "https://myonlineportal.net/updateddns"
1103
1104 def prepare_request_data(self, proto):
1105 data = {
1106 "hostname" : self.hostname,
1107 "ip" : self.get_address(proto),
1108 }
1109
1110 return data
1111
1112
78c9780b 1113class DDNSProviderNamecheap(DDNSResponseParserXML, DDNSProvider):
6a11646e
MT
1114 handle = "namecheap.com"
1115 name = "Namecheap"
1116 website = "http://namecheap.com"
1117 protocols = ("ipv4",)
d1cd57eb
SS
1118
1119 # Information about the format of the HTTP request is to be found
1120 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
1121 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
1122
1123 url = "https://dynamicdns.park-your-domain.com/update"
29c8c9c6 1124 can_remove_records = False
d1cd57eb 1125
d45139f6 1126 def update_protocol(self, proto):
d1cd57eb
SS
1127 # Namecheap requires the hostname splitted into a host and domain part.
1128 host, domain = self.hostname.split(".", 1)
1129
1130 data = {
d45139f6 1131 "ip" : self.get_address(proto),
d1cd57eb
SS
1132 "password" : self.password,
1133 "host" : host,
1134 "domain" : domain
1135 }
1136
1137 # Send update to the server.
1138 response = self.send_request(self.url, data=data)
1139
1140 # Get the full response message.
1141 output = response.read()
1142
1143 # Handle success messages.
d45139f6 1144 if self.get_xml_tag_value(output, "IP") == address:
d1cd57eb
SS
1145 return
1146
1147 # Handle error codes.
78c9780b 1148 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
d1cd57eb
SS
1149
1150 if errorcode == "304156":
1151 raise DDNSAuthenticationError
1152 elif errorcode == "316153":
1153 raise DDNSRequestError(_("Domain not found."))
1154 elif errorcode == "316154":
1155 raise DDNSRequestError(_("Domain not active."))
1156 elif errorcode in ("380098", "380099"):
1157 raise DDNSInternalServerError
1158
1159 # If we got here, some other update error happened.
1160 raise DDNSUpdateError
1161
1162
5d4bec40
MT
1163class DDNSProviderNOIP(DDNSProtocolDynDNS2, DDNSProvider):
1164 handle = "no-ip.com"
1165 name = "No-IP"
1166 website = "http://www.no-ip.com/"
1167 protocols = ("ipv4",)
f22ab085
MT
1168
1169 # Information about the format of the HTTP request is to be found
1170 # here: http://www.no-ip.com/integrate/request and
1171 # here: http://www.no-ip.com/integrate/response
1172
88f39629 1173 url = "http://dynupdate.no-ip.com/nic/update"
2de06f59 1174
d45139f6
MT
1175 def prepare_request_data(self, proto):
1176 assert proto == "ipv4"
1177
2de06f59
MT
1178 data = {
1179 "hostname" : self.hostname,
d45139f6 1180 "address" : self.get_address(proto),
f22ab085
MT
1181 }
1182
88f39629 1183 return data
f22ab085
MT
1184
1185
31c95e4b
SS
1186class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2, DDNSProvider):
1187 handle = "nsupdate.info"
1188 name = "nsupdate.info"
b9221322 1189 website = "http://nsupdate.info/"
31c95e4b
SS
1190 protocols = ("ipv6", "ipv4",)
1191
1192 # Information about the format of the HTTP request can be found
b9221322 1193 # after login on the provider user interface and here:
31c95e4b
SS
1194 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
1195
7b5d382e
SS
1196 url = "https://nsupdate.info/nic/update"
1197
29c8c9c6
MT
1198 # TODO nsupdate.info can actually do this, but the functionality
1199 # has not been implemented here, yet.
1200 can_remove_records = False
1201
64018439
MT
1202 # After a failed update, there will be no retries
1203 # https://bugzilla.ipfire.org/show_bug.cgi?id=10603
1204 holdoff_failure_days = None
1205
31c95e4b
SS
1206 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
1207 # and for the password a so called secret.
1208 @property
1209 def username(self):
1210 return self.get("hostname")
1211
1212 @property
1213 def password(self):
9c777232 1214 return self.token or self.get("secret")
31c95e4b 1215
d45139f6 1216 def prepare_request_data(self, proto):
31c95e4b 1217 data = {
d45139f6 1218 "myip" : self.get_address(proto),
31c95e4b
SS
1219 }
1220
1221 return data
1222
1223
90663439
SS
1224class DDNSProviderOpenDNS(DDNSProtocolDynDNS2, DDNSProvider):
1225 handle = "opendns.com"
1226 name = "OpenDNS"
1227 website = "http://www.opendns.com"
1228
1229 # Detailed information about the update request and possible
1230 # response codes can be obtained from here:
1231 # https://support.opendns.com/entries/23891440
1232
1233 url = "https://updates.opendns.com/nic/update"
1234
d45139f6 1235 def prepare_request_data(self, proto):
90663439
SS
1236 data = {
1237 "hostname" : self.hostname,
d45139f6 1238 "myip" : self.get_address(proto),
90663439
SS
1239 }
1240
1241 return data
1242
1243
5d4bec40
MT
1244class DDNSProviderOVH(DDNSProtocolDynDNS2, DDNSProvider):
1245 handle = "ovh.com"
1246 name = "OVH"
1247 website = "http://www.ovh.com/"
1248 protocols = ("ipv4",)
a508bda6
SS
1249
1250 # OVH only provides very limited information about how to
1251 # update a DynDNS host. They only provide the update url
1252 # on the their german subpage.
1253 #
1254 # http://hilfe.ovh.de/DomainDynHost
1255
1256 url = "https://www.ovh.com/nic/update"
1257
d45139f6
MT
1258 def prepare_request_data(self, proto):
1259 data = DDNSProtocolDynDNS2.prepare_request_data(self, proto)
54d3efc8
MT
1260 data.update({
1261 "system" : "dyndns",
1262 })
1263
1264 return data
a508bda6
SS
1265
1266
ef33455e 1267class DDNSProviderRegfish(DDNSProvider):
6a11646e
MT
1268 handle = "regfish.com"
1269 name = "Regfish GmbH"
1270 website = "http://www.regfish.com/"
ef33455e
SS
1271
1272 # A full documentation to the providers api can be found here
1273 # but is only available in german.
1274 # https://www.regfish.de/domains/dyndns/dokumentation
1275
1276 url = "https://dyndns.regfish.de/"
29c8c9c6 1277 can_remove_records = False
ef33455e
SS
1278
1279 def update(self):
1280 data = {
1281 "fqdn" : self.hostname,
1282 }
1283
1284 # Check if we update an IPv6 address.
1285 address6 = self.get_address("ipv6")
1286 if address6:
1287 data["ipv6"] = address6
1288
1289 # Check if we update an IPv4 address.
1290 address4 = self.get_address("ipv4")
1291 if address4:
1292 data["ipv4"] = address4
1293
1294 # Raise an error if none address is given.
1295 if not data.has_key("ipv6") and not data.has_key("ipv4"):
1296 raise DDNSConfigurationError
1297
1298 # Check if a token has been set.
1299 if self.token:
1300 data["token"] = self.token
1301
1302 # Raise an error if no token and no useranem and password
1303 # are given.
1304 elif not self.username and not self.password:
1305 raise DDNSConfigurationError(_("No Auth details specified."))
1306
1307 # HTTP Basic Auth is only allowed if no token is used.
1308 if self.token:
1309 # Send update to the server.
1310 response = self.send_request(self.url, data=data)
1311 else:
1312 # Send update to the server.
1313 response = self.send_request(self.url, username=self.username, password=self.password,
1314 data=data)
1315
1316 # Get the full response message.
1317 output = response.read()
1318
1319 # Handle success messages.
1320 if "100" in output or "101" in output:
1321 return
1322
1323 # Handle error codes.
1324 if "401" or "402" in output:
1325 raise DDNSAuthenticationError
1326 elif "408" in output:
1327 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
1328 elif "409" in output:
1329 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
1330 elif "412" in output:
1331 raise DDNSRequestError(_("No valid FQDN was given."))
1332 elif "414" in output:
1333 raise DDNSInternalServerError
1334
1335 # If we got here, some other update error happened.
1336 raise DDNSUpdateError
1337
1338
5d4bec40 1339class DDNSProviderSelfhost(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
1340 handle = "selfhost.de"
1341 name = "Selfhost.de"
1342 website = "http://www.selfhost.de/"
5d4bec40 1343 protocols = ("ipv4",)
f22ab085 1344
04db1862 1345 url = "https://carol.selfhost.de/nic/update"
f22ab085 1346
d45139f6
MT
1347 def prepare_request_data(self, proto):
1348 data = DDNSProtocolDynDNS2.prepare_request_data(self, proto)
04db1862
MT
1349 data.update({
1350 "hostname" : "1",
1351 })
f22ab085 1352
04db1862 1353 return data
b09b1545
SS
1354
1355
5d4bec40
MT
1356class DDNSProviderSPDNS(DDNSProtocolDynDNS2, DDNSProvider):
1357 handle = "spdns.org"
1358 name = "SPDNS"
1359 website = "http://spdns.org/"
b09b1545
SS
1360
1361 # Detailed information about request and response codes are provided
1362 # by the vendor. They are using almost the same mechanism and status
1363 # codes as dyndns.org so we can inherit all those stuff.
1364 #
1365 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
1366 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
1367
1368 url = "https://update.spdns.de/nic/update"
4ec90b93 1369
94ab4379
SS
1370 @property
1371 def username(self):
1372 return self.get("username") or self.hostname
1373
1374 @property
1375 def password(self):
25f39b4e 1376 return self.get("password") or self.token
94ab4379 1377
4ec90b93 1378
5d4bec40
MT
1379class DDNSProviderStrato(DDNSProtocolDynDNS2, DDNSProvider):
1380 handle = "strato.com"
1381 name = "Strato AG"
1382 website = "http:/www.strato.com/"
1383 protocols = ("ipv4",)
7488825c
SS
1384
1385 # Information about the request and response can be obtained here:
1386 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
1387
1388 url = "https://dyndns.strato.com/nic/update"
1389
1390
5d4bec40
MT
1391class DDNSProviderTwoDNS(DDNSProtocolDynDNS2, DDNSProvider):
1392 handle = "twodns.de"
1393 name = "TwoDNS"
1394 website = "http://www.twodns.de"
1395 protocols = ("ipv4",)
a6183090
SS
1396
1397 # Detailed information about the request can be found here
1398 # http://twodns.de/en/faqs
1399 # http://twodns.de/en/api
1400
1401 url = "https://update.twodns.de/update"
1402
d45139f6
MT
1403 def prepare_request_data(self, proto):
1404 assert proto == "ipv4"
1405
a6183090 1406 data = {
d45139f6 1407 "ip" : self.get_address(proto),
a6183090
SS
1408 "hostname" : self.hostname
1409 }
1410
1411 return data
1412
1413
5d4bec40
MT
1414class DDNSProviderUdmedia(DDNSProtocolDynDNS2, DDNSProvider):
1415 handle = "udmedia.de"
1416 name = "Udmedia GmbH"
1417 website = "http://www.udmedia.de"
1418 protocols = ("ipv4",)
03bdd188
SS
1419
1420 # Information about the request can be found here
1421 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1422
1423 url = "https://www.udmedia.de/nic/update"
1424
1425
5d4bec40 1426class DDNSProviderVariomedia(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
1427 handle = "variomedia.de"
1428 name = "Variomedia"
1429 website = "http://www.variomedia.de/"
1430 protocols = ("ipv6", "ipv4",)
c8c7ca8f
SS
1431
1432 # Detailed information about the request can be found here
1433 # https://dyndns.variomedia.de/
1434
1435 url = "https://dyndns.variomedia.de/nic/update"
1436
d45139f6 1437 def prepare_request_data(self, proto):
c8c7ca8f
SS
1438 data = {
1439 "hostname" : self.hostname,
d45139f6 1440 "myip" : self.get_address(proto),
c8c7ca8f 1441 }
54d3efc8
MT
1442
1443 return data
98fbe467
SS
1444
1445
6b81b43c
MB
1446class DDNSProviderXLhost(DDNSProtocolDynDNS2, DDNSProvider):
1447 handle = "xlhost.de"
1448 name = "XLhost"
1449 website = "http://xlhost.de/"
1450 protocols = ("ipv4",)
1451
1452 # Information about the format of the HTTP request is to be found
1453 # here: https://xlhost.de/faq/index_html?topicId=CQA2ELIPO4SQ
1454
1455 url = "https://nsupdate.xlhost.de/"
1456
1457
f0554226 1458class DDNSProviderZoneedit(DDNSProvider):
5d4bec40
MT
1459 handle = "zoneedit.com"
1460 name = "Zoneedit"
1461 website = "http://www.zoneedit.com"
1462 protocols = ("ipv4",)
98fbe467
SS
1463
1464 # Detailed information about the request and the response codes can be
1465 # obtained here:
1466 # http://www.zoneedit.com/doc/api/other.html
1467 # http://www.zoneedit.com/faq.html
1468
1469 url = "https://dynamic.zoneedit.com/auth/dynamic.html"
1470
d45139f6 1471 def update_protocol(self, proto):
98fbe467 1472 data = {
d45139f6 1473 "dnsto" : self.get_address(proto),
98fbe467
SS
1474 "host" : self.hostname
1475 }
1476
1477 # Send update to the server.
1478 response = self.send_request(self.url, username=self.username, password=self.password,
1479 data=data)
1480
1481 # Get the full response message.
1482 output = response.read()
1483
1484 # Handle success messages.
1485 if output.startswith("<SUCCESS"):
1486 return
1487
1488 # Handle error codes.
1489 if output.startswith("invalid login"):
1490 raise DDNSAuthenticationError
1491 elif output.startswith("<ERROR CODE=\"704\""):
1492 raise DDNSRequestError(_("No valid FQDN was given."))
1493 elif output.startswith("<ERROR CODE=\"702\""):
1494 raise DDNSInternalServerError
1495
1496 # If we got here, some other update error happened.
1497 raise DDNSUpdateError
e53d3225
SS
1498
1499
1500class DDNSProviderZZZZ(DDNSProvider):
1501 handle = "zzzz.io"
1502 name = "zzzz"
1503 website = "https://zzzz.io"
fbdff678 1504 protocols = ("ipv6", "ipv4",)
e53d3225
SS
1505
1506 # Detailed information about the update request can be found here:
1507 # https://zzzz.io/faq/
1508
1509 # Details about the possible response codes have been provided in the bugtracker:
1510 # https://bugzilla.ipfire.org/show_bug.cgi?id=10584#c2
1511
1512 url = "https://zzzz.io/api/v1/update"
29c8c9c6 1513 can_remove_records = False
e53d3225 1514
d45139f6 1515 def update_protocol(self, proto):
e53d3225 1516 data = {
d45139f6 1517 "ip" : self.get_address(proto),
e53d3225
SS
1518 "token" : self.token,
1519 }
1520
fbdff678
MT
1521 if proto == "ipv6":
1522 data["type"] = "aaaa"
1523
e53d3225
SS
1524 # zzzz uses the host from the full hostname as part
1525 # of the update url.
1526 host, domain = self.hostname.split(".", 1)
1527
1528 # Add host value to the update url.
1529 url = "%s/%s" % (self.url, host)
1530
1531 # Send update to the server.
1532 try:
1533 response = self.send_request(url, data=data)
1534
1535 # Handle error codes.
ff43fa70
MT
1536 except DDNSNotFound:
1537 raise DDNSRequestError(_("Invalid hostname specified"))
e53d3225
SS
1538
1539 # Handle success messages.
1540 if response.code == 200:
1541 return
1542
1543 # If we got here, some other update error happened.
1544 raise DDNSUpdateError