b0443a1a0d77f99a13de4da4851d0339e12fe0ac
[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 logging
23 import subprocess
24 import urllib2
25 import xml.dom.minidom
26
27 from i18n import _
28
29 # Import all possible exception types.
30 from .errors import *
31
32 logger = logging.getLogger("ddns.providers")
33 logger.propagate = 1
34
35 _providers = {}
36
37 def get():
38         """
39                 Returns a dict with all automatically registered providers.
40         """
41         return _providers.copy()
42
43 class DDNSProvider(object):
44         # A short string that uniquely identifies
45         # this provider.
46         handle = None
47
48         # The full name of the provider.
49         name = None
50
51         # A weburl to the homepage of the provider.
52         # (Where to register a new account?)
53         website = None
54
55         # A list of supported protocols.
56         protocols = ("ipv6", "ipv4")
57
58         DEFAULT_SETTINGS = {}
59
60         # Automatically register all providers.
61         class __metaclass__(type):
62                 def __init__(provider, name, bases, dict):
63                         type.__init__(provider, name, bases, dict)
64
65                         # The main class from which is inherited is not registered
66                         # as a provider.
67                         if name == "DDNSProvider":
68                                 return
69
70                         if not all((provider.handle, provider.name, provider.website)):
71                                 raise DDNSError(_("Provider is not properly configured"))
72
73                         assert not _providers.has_key(provider.handle), \
74                                 "Provider '%s' has already been registered" % provider.handle
75
76                         _providers[provider.handle] = provider
77
78         def __init__(self, core, **settings):
79                 self.core = core
80
81                 # Copy a set of default settings and
82                 # update them by those from the configuration file.
83                 self.settings = self.DEFAULT_SETTINGS.copy()
84                 self.settings.update(settings)
85
86         def __repr__(self):
87                 return "<DDNS Provider %s (%s)>" % (self.name, self.handle)
88
89         def __cmp__(self, other):
90                 return cmp(self.hostname, other.hostname)
91
92         def get(self, key, default=None):
93                 """
94                         Get a setting from the settings dictionary.
95                 """
96                 return self.settings.get(key, default)
97
98         @property
99         def hostname(self):
100                 """
101                         Fast access to the hostname.
102                 """
103                 return self.get("hostname")
104
105         @property
106         def username(self):
107                 """
108                         Fast access to the username.
109                 """
110                 return self.get("username")
111
112         @property
113         def password(self):
114                 """
115                         Fast access to the password.
116                 """
117                 return self.get("password")
118
119         @property
120         def token(self):
121                 """
122                         Fast access to the token.
123                 """
124                 return self.get("token")
125
126         def __call__(self, force=False):
127                 if force:
128                         logger.debug(_("Updating %s forced") % self.hostname)
129
130                 # Check if we actually need to update this host.
131                 elif self.is_uptodate(self.protocols):
132                         logger.debug(_("The dynamic host %(hostname)s (%(provider)s) is already up to date") % \
133                                 { "hostname" : self.hostname, "provider" : self.name })
134                         return
135
136                 # Execute the update.
137                 self.update()
138
139                 logger.info(_("Dynamic DNS update for %(hostname)s (%(provider)s) successful") % \
140                         { "hostname" : self.hostname, "provider" : self.name })
141
142         def update(self):
143                 raise NotImplementedError
144
145         def is_uptodate(self, protos):
146                 """
147                         Returns True if this host is already up to date
148                         and does not need to change the IP address on the
149                         name server.
150                 """
151                 for proto in protos:
152                         addresses = self.core.system.resolve(self.hostname, proto)
153
154                         current_address = self.get_address(proto)
155
156                         # If no addresses for the given protocol exist, we
157                         # are fine...
158                         if current_address is None and not addresses:
159                                 continue
160
161                         if not current_address in addresses:
162                                 return False
163
164                 return True
165
166         def send_request(self, *args, **kwargs):
167                 """
168                         Proxy connection to the send request
169                         method.
170                 """
171                 return self.core.system.send_request(*args, **kwargs)
172
173         def get_address(self, proto, default=None):
174                 """
175                         Proxy method to get the current IP address.
176                 """
177                 return self.core.system.get_address(proto) or default
178
179
180 class DDNSProtocolDynDNS2(object):
181         """
182                 This is an abstract class that implements the DynDNS updater
183                 protocol version 2. As this is a popular way to update dynamic
184                 DNS records, this class is supposed make the provider classes
185                 shorter and simpler.
186         """
187
188         # Information about the format of the request is to be found
189         # http://dyn.com/support/developers/api/perform-update/
190         # http://dyn.com/support/developers/api/return-codes/
191
192         def _prepare_request_data(self):
193                 data = {
194                         "hostname" : self.hostname,
195                         "myip"     : self.get_address("ipv4"),
196                 }
197
198                 return data
199
200         def update(self):
201                 data = self._prepare_request_data()
202
203                 # Send update to the server.
204                 response = self.send_request(self.url, data=data,
205                         username=self.username, password=self.password)
206
207                 # Get the full response message.
208                 output = response.read()
209
210                 # Handle success messages.
211                 if output.startswith("good") or output.startswith("nochg"):
212                         return
213
214                 # Handle error codes.
215                 if output == "badauth":
216                         raise DDNSAuthenticationError
217                 elif output == "aduse":
218                         raise DDNSAbuseError
219                 elif output == "notfqdn":
220                         raise DDNSRequestError(_("No valid FQDN was given."))
221                 elif output == "nohost":
222                         raise DDNSRequestError(_("Specified host does not exist."))
223                 elif output == "911":
224                         raise DDNSInternalServerError
225                 elif output == "dnserr":
226                         raise DDNSInternalServerError(_("DNS error encountered."))
227
228                 # If we got here, some other update error happened.
229                 raise DDNSUpdateError(_("Server response: %s") % output)
230
231
232 class DDNSResponseParserXML(object):
233         """
234                 This class provides a parser for XML responses which
235                 will be sent by various providers. This class uses the python
236                 shipped XML minidom module to walk through the XML tree and return
237                 a requested element.
238         """
239
240         def get_xml_tag_value(self, document, content):
241                 # Send input to the parser.
242                 xmldoc = xml.dom.minidom.parseString(document)
243
244                 # Get XML elements by the given content.
245                 element = xmldoc.getElementsByTagName(content)
246
247                 # If no element has been found, we directly can return None.
248                 if not element:
249                         return None
250
251                 # Only get the first child from an element, even there are more than one.
252                 firstchild = element[0].firstChild
253
254                 # Get the value of the child.
255                 value = firstchild.nodeValue
256
257                 # Return the value.
258                 return value
259
260
261 class DDNSProviderAllInkl(DDNSProvider):
262         handle    = "all-inkl.com"
263         name      = "All-inkl.com"
264         website   = "http://all-inkl.com/"
265         protocols = ("ipv4",)
266
267         # There are only information provided by the vendor how to
268         # perform an update on a FRITZ Box. Grab requried informations
269         # from the net.
270         # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
271
272         url = "http://dyndns.kasserver.com"
273
274         def update(self):
275                 # There is no additional data required so we directly can
276                 # send our request.
277                 response = self.send_request(self.url, username=self.username, password=self.password)
278
279                 # Get the full response message.
280                 output = response.read()
281
282                 # Handle success messages.
283                 if output.startswith("good") or output.startswith("nochg"):
284                         return
285
286                 # If we got here, some other update error happened.
287                 raise DDNSUpdateError
288
289
290 class DDNSProviderBindNsupdate(DDNSProvider):
291         handle  = "nsupdate"
292         name    = "BIND nsupdate utility"
293         website = "http://en.wikipedia.org/wiki/Nsupdate"
294
295         DEFAULT_TTL = 60
296
297         def update(self):
298                 scriptlet = self.__make_scriptlet()
299
300                 # -v enables TCP hence we transfer keys and other data that may
301                 # exceed the size of one packet.
302                 # -t sets the timeout
303                 command = ["nsupdate", "-v", "-t", "60"]
304
305                 p = subprocess.Popen(command, shell=True,
306                         stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
307                 )
308                 stdout, stderr = p.communicate(scriptlet)
309
310                 if p.returncode == 0:
311                         return
312
313                 raise DDNSError("nsupdate terminated with error code: %s\n  %s" % (p.returncode, stderr))
314
315         def __make_scriptlet(self):
316                 scriptlet = []
317
318                 # Set a different server the update is sent to.
319                 server = self.get("server", None)
320                 if server:
321                         scriptlet.append("server %s" % server)
322
323                 # Set the DNS zone the host should be added to.
324                 zone = self.get("zone", None)
325                 if zone:
326                         scriptlet.append("zone %s" % zone)
327
328                 key = self.get("key", None)
329                 if key:
330                         secret = self.get("secret")
331
332                         scriptlet.append("key %s %s" % (key, secret))
333
334                 ttl = self.get("ttl", self.DEFAULT_TTL)
335
336                 # Perform an update for each supported protocol.
337                 for rrtype, proto in (("AAAA", "ipv6"), ("A", "ipv4")):
338                         address = self.get_address(proto)
339                         if not address:
340                                 continue
341
342                         scriptlet.append("update delete %s. %s" % (self.hostname, rrtype))
343                         scriptlet.append("update add %s. %s %s %s" % \
344                                 (self.hostname, ttl, rrtype, address))
345
346                 # Send the actions to the server.
347                 scriptlet.append("send")
348                 scriptlet.append("quit")
349
350                 logger.debug(_("Scriptlet:"))
351                 for line in scriptlet:
352                         # Masquerade the line with the secret key.
353                         if line.startswith("key"):
354                                 line = "key **** ****"
355
356                         logger.debug("  %s" % line)
357
358                 return "\n".join(scriptlet)
359
360
361 class DDNSProviderDHS(DDNSProvider):
362         handle    = "dhs.org"
363         name      = "DHS International"
364         website   = "http://dhs.org/"
365         protocols = ("ipv4",)
366
367         # No information about the used update api provided on webpage,
368         # grabed from source code of ez-ipudate.
369
370         url = "http://members.dhs.org/nic/hosts"
371
372         def update(self):
373                 data = {
374                         "domain"       : self.hostname,
375                         "ip"           : self.get_address("ipv4"),
376                         "hostcmd"      : "edit",
377                         "hostcmdstage" : "2",
378                         "type"         : "4",
379                 }
380
381                 # Send update to the server.
382                 response = self.send_request(self.url, username=self.username, password=self.password,
383                         data=data)
384
385                 # Handle success messages.
386                 if response.code == 200:
387                         return
388
389                 # If we got here, some other update error happened.
390                 raise DDNSUpdateError
391
392
393 class DDNSProviderDNSpark(DDNSProvider):
394         handle    = "dnspark.com"
395         name      = "DNS Park"
396         website   = "http://dnspark.com/"
397         protocols = ("ipv4",)
398
399         # Informations to the used api can be found here:
400         # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
401
402         url = "https://control.dnspark.com/api/dynamic/update.php"
403
404         def update(self):
405                 data = {
406                         "domain" : self.hostname,
407                         "ip"     : self.get_address("ipv4"),
408                 }
409
410                 # Send update to the server.
411                 response = self.send_request(self.url, username=self.username, password=self.password,
412                         data=data)
413
414                 # Get the full response message.
415                 output = response.read()
416
417                 # Handle success messages.
418                 if output.startswith("ok") or output.startswith("nochange"):
419                         return
420
421                 # Handle error codes.
422                 if output == "unauth":
423                         raise DDNSAuthenticationError
424                 elif output == "abuse":
425                         raise DDNSAbuseError
426                 elif output == "blocked":
427                         raise DDNSBlockedError
428                 elif output == "nofqdn":
429                         raise DDNSRequestError(_("No valid FQDN was given."))
430                 elif output == "nohost":
431                         raise DDNSRequestError(_("Invalid hostname specified."))
432                 elif output == "notdyn":
433                         raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
434                 elif output == "invalid":
435                         raise DDNSRequestError(_("Invalid IP address has been sent."))
436
437                 # If we got here, some other update error happened.
438                 raise DDNSUpdateError
439
440
441 class DDNSProviderDtDNS(DDNSProvider):
442         handle    = "dtdns.com"
443         name      = "DtDNS"
444         website   = "http://dtdns.com/"
445         protocols = ("ipv4",)
446
447         # Information about the format of the HTTPS request is to be found
448         # http://www.dtdns.com/dtsite/updatespec
449
450         url = "https://www.dtdns.com/api/autodns.cfm"
451
452         def update(self):
453                 data = {
454                         "ip" : self.get_address("ipv4"),
455                         "id" : self.hostname,
456                         "pw" : self.password
457                 }
458
459                 # Send update to the server.
460                 response = self.send_request(self.url, data=data)
461
462                 # Get the full response message.
463                 output = response.read()
464
465                 # Remove all leading and trailing whitespace.
466                 output = output.strip()
467
468                 # Handle success messages.
469                 if "now points to" in output:
470                         return
471
472                 # Handle error codes.
473                 if output == "No hostname to update was supplied.":
474                         raise DDNSRequestError(_("No hostname specified."))
475
476                 elif output == "The hostname you supplied is not valid.":
477                         raise DDNSRequestError(_("Invalid hostname specified."))
478
479                 elif output == "The password you supplied is not valid.":
480                         raise DDNSAuthenticationError
481
482                 elif output == "Administration has disabled this account.":
483                         raise DDNSRequestError(_("Account has been disabled."))
484
485                 elif output == "Illegal character in IP.":
486                         raise DDNSRequestError(_("Invalid IP address has been sent."))
487
488                 elif output == "Too many failed requests.":
489                         raise DDNSRequestError(_("Too many failed requests."))
490
491                 # If we got here, some other update error happened.
492                 raise DDNSUpdateError
493
494
495 class DDNSProviderDynDNS(DDNSProtocolDynDNS2, DDNSProvider):
496         handle    = "dyndns.org"
497         name      = "Dyn"
498         website   = "http://dyn.com/dns/"
499         protocols = ("ipv4",)
500
501         # Information about the format of the request is to be found
502         # http://http://dyn.com/support/developers/api/perform-update/
503         # http://dyn.com/support/developers/api/return-codes/
504
505         url = "https://members.dyndns.org/nic/update"
506
507
508 class DDNSProviderDynU(DDNSProtocolDynDNS2, DDNSProvider):
509         handle    = "dynu.com"
510         name      = "Dynu"
511         website   = "http://dynu.com/"
512         protocols = ("ipv6", "ipv4",)
513
514         # Detailed information about the request and response codes
515         # are available on the providers webpage.
516         # http://dynu.com/Default.aspx?page=dnsapi
517
518         url = "https://api.dynu.com/nic/update"
519
520         def _prepare_request_data(self):
521                 data = DDNSProtocolDynDNS2._prepare_request_data(self)
522
523                 # This one supports IPv6
524                 myipv6 = self.get_address("ipv6")
525
526                 # Add update information if we have an IPv6 address.
527                 if myipv6:
528                         data["myipv6"] = myipv6
529
530                 return data
531
532
533 class DDNSProviderEasyDNS(DDNSProtocolDynDNS2, DDNSProvider):
534         handle    = "easydns.com"
535         name      = "EasyDNS"
536         website   = "http://www.easydns.com/"
537         protocols = ("ipv4",)
538
539         # There is only some basic documentation provided by the vendor,
540         # also searching the web gain very poor results.
541         # http://mediawiki.easydns.com/index.php/Dynamic_DNS
542
543         url = "http://api.cp.easydns.com/dyn/tomato.php"
544
545
546 class DDNSProviderDomopoli(DDNSProtocolDynDNS2, DDNSProvider):
547         handle    = "domopoli.de"
548         name      = "domopoli.de"
549         website   = "http://domopoli.de/"
550         protocols = ("ipv4",)
551
552         # https://www.domopoli.de/?page=howto#DynDns_start
553
554         url = "http://dyndns.domopoli.de/nic/update"
555
556
557 class DDNSProviderEnomCom(DDNSResponseParserXML, DDNSProvider):
558         handle    = "enom.com"
559         name      = "eNom Inc."
560         website   = "http://www.enom.com/"
561
562         # There are very detailed information about how to send an update request and
563         # the respone codes.
564         # http://www.enom.com/APICommandCatalog/
565
566         url = "https://dynamic.name-services.com/interface.asp"
567
568         def update(self):
569                 data = {
570                         "command"        : "setdnshost",
571                         "responsetype"   : "xml",
572                         "address"        : self.get_address("ipv4"),
573                         "domainpassword" : self.password,
574                         "zone"           : self.hostname
575                 }
576
577                 # Send update to the server.
578                 response = self.send_request(self.url, data=data)
579
580                 # Get the full response message.
581                 output = response.read()
582
583                 # Handle success messages.
584                 if self.get_xml_tag_value(output, "ErrCount") == "0":
585                         return
586
587                 # Handle error codes.
588                 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
589
590                 if errorcode == "304155":
591                         raise DDNSAuthenticationError
592                 elif errorcode == "304153":
593                         raise DDNSRequestError(_("Domain not found."))
594
595                 # If we got here, some other update error happened.
596                 raise DDNSUpdateError
597
598
599 class DDNSProviderEntryDNS(DDNSProvider):
600         handle    = "entrydns.net"
601         name      = "EntryDNS"
602         website   = "http://entrydns.net/"
603         protocols = ("ipv4",)
604
605         # Some very tiny details about their so called "Simple API" can be found
606         # here: https://entrydns.net/help
607         url = "https://entrydns.net/records/modify"
608
609         def update(self):
610                 data = {
611                         "ip" : self.get_address("ipv4")
612                 }
613
614                 # Add auth token to the update url.
615                 url = "%s/%s" % (self.url, self.token)
616
617                 # Send update to the server.
618                 try:
619                         response = self.send_request(url, data=data)
620
621                 # Handle error codes
622                 except urllib2.HTTPError, e:
623                         if e.code == 404:
624                                 raise DDNSAuthenticationError
625
626                         elif e.code == 422:
627                                 raise DDNSRequestError(_("An invalid IP address was submitted"))
628
629                         raise
630
631                 # Handle success messages.
632                 if response.code == 200:
633                         return
634
635                 # If we got here, some other update error happened.
636                 raise DDNSUpdateError
637
638
639 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider):
640         handle    = "freedns.afraid.org"
641         name      = "freedns.afraid.org"
642         website   = "http://freedns.afraid.org/"
643
644         # No information about the request or response could be found on the vendor
645         # page. All used values have been collected by testing.
646         url = "https://freedns.afraid.org/dynamic/update.php"
647
648         @property
649         def proto(self):
650                 return self.get("proto")
651
652         def update(self):
653                 address = self.get_address(self.proto)
654
655                 data = {
656                         "address" : address,
657                 }
658
659                 # Add auth token to the update url.
660                 url = "%s?%s" % (self.url, self.token)
661
662                 # Send update to the server.
663                 response = self.send_request(url, data=data)
664
665                 # Get the full response message.
666                 output = response.read()
667
668                 # Handle success messages.
669                 if output.startswith("Updated") or "has not changed" in output:
670                         return
671
672                 # Handle error codes.
673                 if output == "ERROR: Unable to locate this record":
674                         raise DDNSAuthenticationError
675                 elif "is an invalid IP address" in output:
676                         raise DDNSRequestError(_("Invalid IP address has been sent."))
677
678                 # If we got here, some other update error happened.
679                 raise DDNSUpdateError
680
681
682 class DDNSProviderLightningWireLabs(DDNSProvider):
683         handle    = "dns.lightningwirelabs.com"
684         name      = "Lightning Wire Labs DNS Service"
685         website   = "http://dns.lightningwirelabs.com/"
686
687         # Information about the format of the HTTPS request is to be found
688         # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
689
690         url = "https://dns.lightningwirelabs.com/update"
691
692         def update(self):
693                 data =  {
694                         "hostname" : self.hostname,
695                         "address6" : self.get_address("ipv6", "-"),
696                         "address4" : self.get_address("ipv4", "-"),
697                 }
698
699                 # Check if a token has been set.
700                 if self.token:
701                         data["token"] = self.token
702
703                 # Check for username and password.
704                 elif self.username and self.password:
705                         data.update({
706                                 "username" : self.username,
707                                 "password" : self.password,
708                         })
709
710                 # Raise an error if no auth details are given.
711                 else:
712                         raise DDNSConfigurationError
713
714                 # Send update to the server.
715                 response = self.send_request(self.url, data=data)
716
717                 # Handle success messages.
718                 if response.code == 200:
719                         return
720
721                 # If we got here, some other update error happened.
722                 raise DDNSUpdateError
723
724
725 class DDNSProviderNamecheap(DDNSResponseParserXML, DDNSProvider):
726         handle    = "namecheap.com"
727         name      = "Namecheap"
728         website   = "http://namecheap.com"
729         protocols = ("ipv4",)
730
731         # Information about the format of the HTTP request is to be found
732         # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
733         # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
734
735         url = "https://dynamicdns.park-your-domain.com/update"
736
737         def update(self):
738                 # Namecheap requires the hostname splitted into a host and domain part.
739                 host, domain = self.hostname.split(".", 1)
740
741                 data = {
742                         "ip"       : self.get_address("ipv4"),
743                         "password" : self.password,
744                         "host"     : host,
745                         "domain"   : domain
746                 }
747
748                 # Send update to the server.
749                 response = self.send_request(self.url, data=data)
750
751                 # Get the full response message.
752                 output = response.read()
753
754                 # Handle success messages.
755                 if self.get_xml_tag_value(output, "IP") == self.get_address("ipv4"):
756                         return
757
758                 # Handle error codes.
759                 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
760
761                 if errorcode == "304156":
762                         raise DDNSAuthenticationError
763                 elif errorcode == "316153":
764                         raise DDNSRequestError(_("Domain not found."))
765                 elif errorcode == "316154":
766                         raise DDNSRequestError(_("Domain not active."))
767                 elif errorcode in ("380098", "380099"):
768                         raise DDNSInternalServerError
769
770                 # If we got here, some other update error happened.
771                 raise DDNSUpdateError
772
773
774 class DDNSProviderNOIP(DDNSProtocolDynDNS2, DDNSProvider):
775         handle    = "no-ip.com"
776         name      = "No-IP"
777         website   = "http://www.no-ip.com/"
778         protocols = ("ipv4",)
779
780         # Information about the format of the HTTP request is to be found
781         # here: http://www.no-ip.com/integrate/request and
782         # here: http://www.no-ip.com/integrate/response
783
784         url = "http://dynupdate.no-ip.com/nic/update"
785
786         def _prepare_request_data(self):
787                 data = {
788                         "hostname" : self.hostname,
789                         "address"  : self.get_address("ipv4"),
790                 }
791
792                 return data
793
794
795 class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2, DDNSProvider):
796         handle    = "nsupdate.info"
797         name      = "nsupdate.info"
798         website   = "http://www.nsupdate.info/"
799         protocols = ("ipv6", "ipv4",)
800
801         # Information about the format of the HTTP request can be found
802         # after login on the provider user intrface and here:
803         # http://nsupdateinfo.readthedocs.org/en/latest/user.html
804
805         # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
806         # and for the password a so called secret.
807         @property
808         def username(self):
809                 return self.get("hostname")
810
811         @property
812         def password(self):
813                 return self.get("secret")
814
815         @property
816         def proto(self):
817                 return self.get("proto")
818
819         @property
820         def url(self):
821                 # The update URL is different by the used protocol.
822                 if self.proto == "ipv4":
823                         return "https://ipv4.nsupdate.info/nic/update"
824                 elif self.proto == "ipv6":
825                         return "https://ipv6.nsupdate.info/nic/update"
826                 else:
827                         raise DDNSUpdateError(_("Invalid protocol has been given"))
828
829         def _prepare_request_data(self):
830                 data = {
831                         "myip" : self.get_address(self.proto),
832                 }
833
834                 return data
835
836
837 class DDNSProviderOpenDNS(DDNSProtocolDynDNS2, DDNSProvider):
838         handle    = "opendns.com"
839         name      = "OpenDNS"
840         website   = "http://www.opendns.com"
841
842         # Detailed information about the update request and possible
843         # response codes can be obtained from here:
844         # https://support.opendns.com/entries/23891440
845
846         url = "https://updates.opendns.com/nic/update"
847
848         @property
849         def proto(self):
850                 return self.get("proto")
851
852         def _prepare_request_data(self):
853                 data = {
854                         "hostname" : self.hostname,
855                         "myip"     : self.get_address(self.proto)
856                 }
857
858                 return data
859
860
861 class DDNSProviderOVH(DDNSProtocolDynDNS2, DDNSProvider):
862         handle    = "ovh.com"
863         name      = "OVH"
864         website   = "http://www.ovh.com/"
865         protocols = ("ipv4",)
866
867         # OVH only provides very limited information about how to
868         # update a DynDNS host. They only provide the update url
869         # on the their german subpage.
870         #
871         # http://hilfe.ovh.de/DomainDynHost
872
873         url = "https://www.ovh.com/nic/update"
874
875         def _prepare_request_data(self):
876                 data = DDNSProtocolDynDNS2._prepare_request_data(self)
877                 data.update({
878                         "system" : "dyndns",
879                 })
880
881                 return data
882
883
884 class DDNSProviderRegfish(DDNSProvider):
885         handle  = "regfish.com"
886         name    = "Regfish GmbH"
887         website = "http://www.regfish.com/"
888
889         # A full documentation to the providers api can be found here
890         # but is only available in german.
891         # https://www.regfish.de/domains/dyndns/dokumentation
892
893         url = "https://dyndns.regfish.de/"
894
895         def update(self):
896                 data = {
897                         "fqdn" : self.hostname,
898                 }
899
900                 # Check if we update an IPv6 address.
901                 address6 = self.get_address("ipv6")
902                 if address6:
903                         data["ipv6"] = address6
904
905                 # Check if we update an IPv4 address.
906                 address4 = self.get_address("ipv4")
907                 if address4:
908                         data["ipv4"] = address4
909
910                 # Raise an error if none address is given.
911                 if not data.has_key("ipv6") and not data.has_key("ipv4"):
912                         raise DDNSConfigurationError
913
914                 # Check if a token has been set.
915                 if self.token:
916                         data["token"] = self.token
917
918                 # Raise an error if no token and no useranem and password
919                 # are given.
920                 elif not self.username and not self.password:
921                         raise DDNSConfigurationError(_("No Auth details specified."))
922
923                 # HTTP Basic Auth is only allowed if no token is used.
924                 if self.token:
925                         # Send update to the server.
926                         response = self.send_request(self.url, data=data)
927                 else:
928                         # Send update to the server.
929                         response = self.send_request(self.url, username=self.username, password=self.password,
930                                 data=data)
931
932                 # Get the full response message.
933                 output = response.read()
934
935                 # Handle success messages.
936                 if "100" in output or "101" in output:
937                         return
938
939                 # Handle error codes.
940                 if "401" or "402" in output:
941                         raise DDNSAuthenticationError
942                 elif "408" in output:
943                         raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
944                 elif "409" in output:
945                         raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
946                 elif "412" in output:
947                         raise DDNSRequestError(_("No valid FQDN was given."))
948                 elif "414" in output:
949                         raise DDNSInternalServerError
950
951                 # If we got here, some other update error happened.
952                 raise DDNSUpdateError
953
954
955 class DDNSProviderSelfhost(DDNSProtocolDynDNS2, DDNSProvider):
956         handle    = "selfhost.de"
957         name      = "Selfhost.de"
958         website   = "http://www.selfhost.de/"
959         protocols = ("ipv4",)
960
961         url = "https://carol.selfhost.de/nic/update"
962
963         def _prepare_request_data(self):
964                 data = DDNSProtocolDynDNS2._prepare_request_data(self)
965                 data.update({
966                         "hostname" : "1",
967                 })
968
969                 return data
970
971
972 class DDNSProviderSPDNS(DDNSProtocolDynDNS2, DDNSProvider):
973         handle    = "spdns.org"
974         name      = "SPDNS"
975         website   = "http://spdns.org/"
976         protocols = ("ipv4",)
977
978         # Detailed information about request and response codes are provided
979         # by the vendor. They are using almost the same mechanism and status
980         # codes as dyndns.org so we can inherit all those stuff.
981         #
982         # http://wiki.securepoint.de/index.php/SPDNS_FAQ
983         # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
984
985         url = "https://update.spdns.de/nic/update"
986
987
988 class DDNSProviderStrato(DDNSProtocolDynDNS2, DDNSProvider):
989         handle    = "strato.com"
990         name      = "Strato AG"
991         website   = "http:/www.strato.com/"
992         protocols = ("ipv4",)
993
994         # Information about the request and response can be obtained here:
995         # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
996
997         url = "https://dyndns.strato.com/nic/update"
998
999
1000 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2, DDNSProvider):
1001         handle    = "twodns.de"
1002         name      = "TwoDNS"
1003         website   = "http://www.twodns.de"
1004         protocols = ("ipv4",)
1005
1006         # Detailed information about the request can be found here
1007         # http://twodns.de/en/faqs
1008         # http://twodns.de/en/api
1009
1010         url = "https://update.twodns.de/update"
1011
1012         def _prepare_request_data(self):
1013                 data = {
1014                         "ip" : self.get_address("ipv4"),
1015                         "hostname" : self.hostname
1016                 }
1017
1018                 return data
1019
1020
1021 class DDNSProviderUdmedia(DDNSProtocolDynDNS2, DDNSProvider):
1022         handle    = "udmedia.de"
1023         name      = "Udmedia GmbH"
1024         website   = "http://www.udmedia.de"
1025         protocols = ("ipv4",)
1026
1027         # Information about the request can be found here
1028         # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1029
1030         url = "https://www.udmedia.de/nic/update"
1031
1032
1033 class DDNSProviderVariomedia(DDNSProtocolDynDNS2, DDNSProvider):
1034         handle    = "variomedia.de"
1035         name      = "Variomedia"
1036         website   = "http://www.variomedia.de/"
1037         protocols = ("ipv6", "ipv4",)
1038
1039         # Detailed information about the request can be found here
1040         # https://dyndns.variomedia.de/
1041
1042         url = "https://dyndns.variomedia.de/nic/update"
1043
1044         @property
1045         def proto(self):
1046                 return self.get("proto")
1047
1048         def _prepare_request_data(self):
1049                 data = {
1050                         "hostname" : self.hostname,
1051                         "myip"     : self.get_address(self.proto)
1052                 }
1053
1054                 return data
1055
1056
1057 class DDNSProviderZoneedit(DDNSProtocolDynDNS2, DDNSProvider):
1058         handle    = "zoneedit.com"
1059         name      = "Zoneedit"
1060         website   = "http://www.zoneedit.com"
1061         protocols = ("ipv4",)
1062
1063         # Detailed information about the request and the response codes can be
1064         # obtained here:
1065         # http://www.zoneedit.com/doc/api/other.html
1066         # http://www.zoneedit.com/faq.html
1067
1068         url = "https://dynamic.zoneedit.com/auth/dynamic.html"
1069
1070         @property
1071         def proto(self):
1072                 return self.get("proto")
1073
1074         def update(self):
1075                 data = {
1076                         "dnsto" : self.get_address(self.proto),
1077                         "host"  : self.hostname
1078                 }
1079
1080                 # Send update to the server.
1081                 response = self.send_request(self.url, username=self.username, password=self.password,
1082                         data=data)
1083
1084                 # Get the full response message.
1085                 output = response.read()
1086
1087                 # Handle success messages.
1088                 if output.startswith("<SUCCESS"):
1089                         return
1090
1091                 # Handle error codes.
1092                 if output.startswith("invalid login"):
1093                         raise DDNSAuthenticationError
1094                 elif output.startswith("<ERROR CODE=\"704\""):
1095                         raise DDNSRequestError(_("No valid FQDN was given.")) 
1096                 elif output.startswith("<ERROR CODE=\"702\""):
1097                         raise DDNSInternalServerError
1098
1099                 # If we got here, some other update error happened.
1100                 raise DDNSUpdateError