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