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