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