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