dynu: Fix updating if no IPv6 address is available.
[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.info(_("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                 key = self.get("key", None)
324                 if key:
325                         secret = self.get("secret")
326
327                         scriptlet.append("key %s %s" % (key, secret))
328
329                 ttl = self.get("ttl", self.DEFAULT_TTL)
330
331                 # Perform an update for each supported protocol.
332                 for rrtype, proto in (("AAAA", "ipv6"), ("A", "ipv4")):
333                         address = self.get_address(proto)
334                         if not address:
335                                 continue
336
337                         scriptlet.append("update delete %s. %s" % (self.hostname, rrtype))
338                         scriptlet.append("update add %s. %s %s %s" % \
339                                 (self.hostname, ttl, rrtype, address))
340
341                 # Send the actions to the server.
342                 scriptlet.append("send")
343                 scriptlet.append("quit")
344
345                 logger.debug(_("Scriptlet:"))
346                 for line in scriptlet:
347                         # Masquerade the line with the secret key.
348                         if line.startswith("key"):
349                                 line = "key **** ****"
350
351                         logger.debug("  %s" % line)
352
353                 return "\n".join(scriptlet)
354
355
356 class DDNSProviderDHS(DDNSProvider):
357         handle    = "dhs.org"
358         name      = "DHS International"
359         website   = "http://dhs.org/"
360         protocols = ("ipv4",)
361
362         # No information about the used update api provided on webpage,
363         # grabed from source code of ez-ipudate.
364
365         url = "http://members.dhs.org/nic/hosts"
366
367         def update(self):
368                 data = {
369                         "domain"       : self.hostname,
370                         "ip"           : self.get_address("ipv4"),
371                         "hostcmd"      : "edit",
372                         "hostcmdstage" : "2",
373                         "type"         : "4",
374                 }
375
376                 # Send update to the server.
377                 response = self.send_request(self.url, username=self.username, password=self.password,
378                         data=data)
379
380                 # Handle success messages.
381                 if response.code == 200:
382                         return
383
384                 # If we got here, some other update error happened.
385                 raise DDNSUpdateError
386
387
388 class DDNSProviderDNSpark(DDNSProvider):
389         handle    = "dnspark.com"
390         name      = "DNS Park"
391         website   = "http://dnspark.com/"
392         protocols = ("ipv4",)
393
394         # Informations to the used api can be found here:
395         # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
396
397         url = "https://control.dnspark.com/api/dynamic/update.php"
398
399         def update(self):
400                 data = {
401                         "domain" : self.hostname,
402                         "ip"     : self.get_address("ipv4"),
403                 }
404
405                 # Send update to the server.
406                 response = self.send_request(self.url, username=self.username, password=self.password,
407                         data=data)
408
409                 # Get the full response message.
410                 output = response.read()
411
412                 # Handle success messages.
413                 if output.startswith("ok") or output.startswith("nochange"):
414                         return
415
416                 # Handle error codes.
417                 if output == "unauth":
418                         raise DDNSAuthenticationError
419                 elif output == "abuse":
420                         raise DDNSAbuseError
421                 elif output == "blocked":
422                         raise DDNSBlockedError
423                 elif output == "nofqdn":
424                         raise DDNSRequestError(_("No valid FQDN was given."))
425                 elif output == "nohost":
426                         raise DDNSRequestError(_("Invalid hostname specified."))
427                 elif output == "notdyn":
428                         raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
429                 elif output == "invalid":
430                         raise DDNSRequestError(_("Invalid IP address has been sent."))
431
432                 # If we got here, some other update error happened.
433                 raise DDNSUpdateError
434
435
436 class DDNSProviderDtDNS(DDNSProvider):
437         handle    = "dtdns.com"
438         name      = "DtDNS"
439         website   = "http://dtdns.com/"
440         protocols = ("ipv4",)
441
442         # Information about the format of the HTTPS request is to be found
443         # http://www.dtdns.com/dtsite/updatespec
444
445         url = "https://www.dtdns.com/api/autodns.cfm"
446
447         def update(self):
448                 data = {
449                         "ip" : self.get_address("ipv4"),
450                         "id" : self.hostname,
451                         "pw" : self.password
452                 }
453
454                 # Send update to the server.
455                 response = self.send_request(self.url, data=data)
456
457                 # Get the full response message.
458                 output = response.read()
459
460                 # Remove all leading and trailing whitespace.
461                 output = output.strip()
462
463                 # Handle success messages.
464                 if "now points to" in output:
465                         return
466
467                 # Handle error codes.
468                 if output == "No hostname to update was supplied.":
469                         raise DDNSRequestError(_("No hostname specified."))
470
471                 elif output == "The hostname you supplied is not valid.":
472                         raise DDNSRequestError(_("Invalid hostname specified."))
473
474                 elif output == "The password you supplied is not valid.":
475                         raise DDNSAuthenticationError
476
477                 elif output == "Administration has disabled this account.":
478                         raise DDNSRequestError(_("Account has been disabled."))
479
480                 elif output == "Illegal character in IP.":
481                         raise DDNSRequestError(_("Invalid IP address has been sent."))
482
483                 elif output == "Too many failed requests.":
484                         raise DDNSRequestError(_("Too many failed requests."))
485
486                 # If we got here, some other update error happened.
487                 raise DDNSUpdateError
488
489
490 class DDNSProviderDynDNS(DDNSProtocolDynDNS2, DDNSProvider):
491         handle    = "dyndns.org"
492         name      = "Dyn"
493         website   = "http://dyn.com/dns/"
494         protocols = ("ipv4",)
495
496         # Information about the format of the request is to be found
497         # http://http://dyn.com/support/developers/api/perform-update/
498         # http://dyn.com/support/developers/api/return-codes/
499
500         url = "https://members.dyndns.org/nic/update"
501
502
503 class DDNSProviderDynU(DDNSProtocolDynDNS2, DDNSProvider):
504         handle    = "dynu.com"
505         name      = "Dynu"
506         website   = "http://dynu.com/"
507         protocols = ("ipv6", "ipv4",)
508
509         # Detailed information about the request and response codes
510         # are available on the providers webpage.
511         # http://dynu.com/Default.aspx?page=dnsapi
512
513         url = "https://api.dynu.com/nic/update"
514
515         def _prepare_request_data(self):
516                 data = DDNSProtocolDynDNS2._prepare_request_data(self)
517
518                 # This one supports IPv6
519                 myipv6 = self.get_address("ipv6")
520
521                 # Add update information if we have an IPv6 address.
522                 if myipv6:
523                         data["myipv6"] = myipv6
524
525                 return data
526
527
528 class DDNSProviderEasyDNS(DDNSProtocolDynDNS2, DDNSProvider):
529         handle    = "easydns.com"
530         name      = "EasyDNS"
531         website   = "http://www.easydns.com/"
532         protocols = ("ipv4",)
533
534         # There is only some basic documentation provided by the vendor,
535         # also searching the web gain very poor results.
536         # http://mediawiki.easydns.com/index.php/Dynamic_DNS
537
538         url = "http://api.cp.easydns.com/dyn/tomato.php"
539
540
541 class DDNSProviderEnomCom(DDNSResponseParserXML, DDNSProvider):
542         handle    = "enom.com"
543         name      = "eNom Inc."
544         website   = "http://www.enom.com/"
545
546         # There are very detailed information about how to send an update request and
547         # the respone codes.
548         # http://www.enom.com/APICommandCatalog/
549
550         url = "https://dynamic.name-services.com/interface.asp"
551
552         def update(self):
553                 data = {
554                         "command"        : "setdnshost",
555                         "responsetype"   : "xml",
556                         "address"        : self.get_address("ipv4"),
557                         "domainpassword" : self.password,
558                         "zone"           : self.hostname
559                 }
560
561                 # Send update to the server.
562                 response = self.send_request(self.url, data=data)
563
564                 # Get the full response message.
565                 output = response.read()
566
567                 # Handle success messages.
568                 if self.get_xml_tag_value(output, "ErrCount") == "0":
569                         return
570
571                 # Handle error codes.
572                 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
573
574                 if errorcode == "304155":
575                         raise DDNSAuthenticationError
576                 elif errorcode == "304153":
577                         raise DDNSRequestError(_("Domain not found."))
578
579                 # If we got here, some other update error happened.
580                 raise DDNSUpdateError
581
582
583 class DDNSProviderEntryDNS(DDNSProvider):
584         handle    = "entrydns.net"
585         name      = "EntryDNS"
586         website   = "http://entrydns.net/"
587         protocols = ("ipv4",)
588
589         # Some very tiny details about their so called "Simple API" can be found
590         # here: https://entrydns.net/help
591         url = "https://entrydns.net/records/modify"
592
593         def update(self):
594                 data = {
595                         "ip" : self.get_address("ipv4")
596                 }
597
598                 # Add auth token to the update url.
599                 url = "%s/%s" % (self.url, self.token)
600
601                 # Send update to the server.
602                 try:
603                         response = self.send_request(url, data=data)
604
605                 # Handle error codes
606                 except urllib2.HTTPError, e:
607                         if e.code == 404:
608                                 raise DDNSAuthenticationError
609
610                         elif e.code == 422:
611                                 raise DDNSRequestError(_("An invalid IP address was submitted"))
612
613                         raise
614
615                 # Handle success messages.
616                 if response.code == 200:
617                         return
618
619                 # If we got here, some other update error happened.
620                 raise DDNSUpdateError
621
622
623 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider):
624         handle    = "freedns.afraid.org"
625         name      = "freedns.afraid.org"
626         website   = "http://freedns.afraid.org/"
627
628         # No information about the request or response could be found on the vendor
629         # page. All used values have been collected by testing.
630         url = "https://freedns.afraid.org/dynamic/update.php"
631
632         @property
633         def proto(self):
634                 return self.get("proto")
635
636         def update(self):
637                 address = self.get_address(self.proto)
638
639                 data = {
640                         "address" : address,
641                 }
642
643                 # Add auth token to the update url.
644                 url = "%s?%s" % (self.url, self.token)
645
646                 # Send update to the server.
647                 response = self.send_request(url, data=data)
648
649                 # Get the full response message.
650                 output = response.read()
651
652                 # Handle success messages.
653                 if output.startswith("Updated") or "has not changed" in output:
654                         return
655
656                 # Handle error codes.
657                 if output == "ERROR: Unable to locate this record":
658                         raise DDNSAuthenticationError
659                 elif "is an invalid IP address" in output:
660                         raise DDNSRequestError(_("Invalid IP address has been sent."))
661
662                 # If we got here, some other update error happened.
663                 raise DDNSUpdateError
664
665
666 class DDNSProviderLightningWireLabs(DDNSProvider):
667         handle    = "dns.lightningwirelabs.com"
668         name      = "Lightning Wire Labs DNS Service"
669         website   = "http://dns.lightningwirelabs.com/"
670
671         # Information about the format of the HTTPS request is to be found
672         # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
673
674         url = "https://dns.lightningwirelabs.com/update"
675
676         def update(self):
677                 data =  {
678                         "hostname" : self.hostname,
679                         "address6" : self.get_address("ipv6", "-"),
680                         "address4" : self.get_address("ipv4", "-"),
681                 }
682
683                 # Check if a token has been set.
684                 if self.token:
685                         data["token"] = self.token
686
687                 # Check for username and password.
688                 elif self.username and self.password:
689                         data.update({
690                                 "username" : self.username,
691                                 "password" : self.password,
692                         })
693
694                 # Raise an error if no auth details are given.
695                 else:
696                         raise DDNSConfigurationError
697
698                 # Send update to the server.
699                 response = self.send_request(self.url, data=data)
700
701                 # Handle success messages.
702                 if response.code == 200:
703                         return
704
705                 # If we got here, some other update error happened.
706                 raise DDNSUpdateError
707
708
709 class DDNSProviderNamecheap(DDNSResponseParserXML, DDNSProvider):
710         handle    = "namecheap.com"
711         name      = "Namecheap"
712         website   = "http://namecheap.com"
713         protocols = ("ipv4",)
714
715         # Information about the format of the HTTP request is to be found
716         # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
717         # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
718
719         url = "https://dynamicdns.park-your-domain.com/update"
720
721         def update(self):
722                 # Namecheap requires the hostname splitted into a host and domain part.
723                 host, domain = self.hostname.split(".", 1)
724
725                 data = {
726                         "ip"       : self.get_address("ipv4"),
727                         "password" : self.password,
728                         "host"     : host,
729                         "domain"   : domain
730                 }
731
732                 # Send update to the server.
733                 response = self.send_request(self.url, data=data)
734
735                 # Get the full response message.
736                 output = response.read()
737
738                 # Handle success messages.
739                 if self.get_xml_tag_value(output, "IP") == self.get_address("ipv4"):
740                         return
741
742                 # Handle error codes.
743                 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
744
745                 if errorcode == "304156":
746                         raise DDNSAuthenticationError
747                 elif errorcode == "316153":
748                         raise DDNSRequestError(_("Domain not found."))
749                 elif errorcode == "316154":
750                         raise DDNSRequestError(_("Domain not active."))
751                 elif errorcode in ("380098", "380099"):
752                         raise DDNSInternalServerError
753
754                 # If we got here, some other update error happened.
755                 raise DDNSUpdateError
756
757
758 class DDNSProviderNOIP(DDNSProtocolDynDNS2, DDNSProvider):
759         handle    = "no-ip.com"
760         name      = "No-IP"
761         website   = "http://www.no-ip.com/"
762         protocols = ("ipv4",)
763
764         # Information about the format of the HTTP request is to be found
765         # here: http://www.no-ip.com/integrate/request and
766         # here: http://www.no-ip.com/integrate/response
767
768         url = "http://dynupdate.no-ip.com/nic/update"
769
770         def _prepare_request_data(self):
771                 data = {
772                         "hostname" : self.hostname,
773                         "address"  : self.get_address("ipv4"),
774                 }
775
776                 return data
777
778
779 class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2, DDNSProvider):
780         handle    = "nsupdate.info"
781         name      = "nsupdate.info"
782         website   = "http://www.nsupdate.info/"
783         protocols = ("ipv6", "ipv4",)
784
785         # Information about the format of the HTTP request can be found
786         # after login on the provider user intrface and here:
787         # http://nsupdateinfo.readthedocs.org/en/latest/user.html
788
789         # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
790         # and for the password a so called secret.
791         @property
792         def username(self):
793                 return self.get("hostname")
794
795         @property
796         def password(self):
797                 return self.get("secret")
798
799         @property
800         def proto(self):
801                 return self.get("proto")
802
803         @property
804         def url(self):
805                 # The update URL is different by the used protocol.
806                 if self.proto == "ipv4":
807                         return "https://ipv4.nsupdate.info/nic/update"
808                 elif self.proto == "ipv6":
809                         return "https://ipv6.nsupdate.info/nic/update"
810                 else:
811                         raise DDNSUpdateError(_("Invalid protocol has been given"))
812
813         def _prepare_request_data(self):
814                 data = {
815                         "myip" : self.get_address(self.proto),
816                 }
817
818                 return data
819
820
821 class DDNSProviderOpenDNS(DDNSProtocolDynDNS2, DDNSProvider):
822         handle    = "opendns.com"
823         name      = "OpenDNS"
824         website   = "http://www.opendns.com"
825
826         # Detailed information about the update request and possible
827         # response codes can be obtained from here:
828         # https://support.opendns.com/entries/23891440
829
830         url = "https://updates.opendns.com/nic/update"
831
832         @property
833         def proto(self):
834                 return self.get("proto")
835
836         def _prepare_request_data(self):
837                 data = {
838                         "hostname" : self.hostname,
839                         "myip"     : self.get_address(self.proto)
840                 }
841
842                 return data
843
844
845 class DDNSProviderOVH(DDNSProtocolDynDNS2, DDNSProvider):
846         handle    = "ovh.com"
847         name      = "OVH"
848         website   = "http://www.ovh.com/"
849         protocols = ("ipv4",)
850
851         # OVH only provides very limited information about how to
852         # update a DynDNS host. They only provide the update url
853         # on the their german subpage.
854         #
855         # http://hilfe.ovh.de/DomainDynHost
856
857         url = "https://www.ovh.com/nic/update"
858
859         def _prepare_request_data(self):
860                 data = DDNSProtocolDynDNS2._prepare_request_data(self)
861                 data.update({
862                         "system" : "dyndns",
863                 })
864
865                 return data
866
867
868 class DDNSProviderRegfish(DDNSProvider):
869         handle  = "regfish.com"
870         name    = "Regfish GmbH"
871         website = "http://www.regfish.com/"
872
873         # A full documentation to the providers api can be found here
874         # but is only available in german.
875         # https://www.regfish.de/domains/dyndns/dokumentation
876
877         url = "https://dyndns.regfish.de/"
878
879         def update(self):
880                 data = {
881                         "fqdn" : self.hostname,
882                 }
883
884                 # Check if we update an IPv6 address.
885                 address6 = self.get_address("ipv6")
886                 if address6:
887                         data["ipv6"] = address6
888
889                 # Check if we update an IPv4 address.
890                 address4 = self.get_address("ipv4")
891                 if address4:
892                         data["ipv4"] = address4
893
894                 # Raise an error if none address is given.
895                 if not data.has_key("ipv6") and not data.has_key("ipv4"):
896                         raise DDNSConfigurationError
897
898                 # Check if a token has been set.
899                 if self.token:
900                         data["token"] = self.token
901
902                 # Raise an error if no token and no useranem and password
903                 # are given.
904                 elif not self.username and not self.password:
905                         raise DDNSConfigurationError(_("No Auth details specified."))
906
907                 # HTTP Basic Auth is only allowed if no token is used.
908                 if self.token:
909                         # Send update to the server.
910                         response = self.send_request(self.url, data=data)
911                 else:
912                         # Send update to the server.
913                         response = self.send_request(self.url, username=self.username, password=self.password,
914                                 data=data)
915
916                 # Get the full response message.
917                 output = response.read()
918
919                 # Handle success messages.
920                 if "100" in output or "101" in output:
921                         return
922
923                 # Handle error codes.
924                 if "401" or "402" in output:
925                         raise DDNSAuthenticationError
926                 elif "408" in output:
927                         raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
928                 elif "409" in output:
929                         raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
930                 elif "412" in output:
931                         raise DDNSRequestError(_("No valid FQDN was given."))
932                 elif "414" in output:
933                         raise DDNSInternalServerError
934
935                 # If we got here, some other update error happened.
936                 raise DDNSUpdateError
937
938
939 class DDNSProviderSelfhost(DDNSProtocolDynDNS2, DDNSProvider):
940         handle    = "selfhost.de"
941         name      = "Selfhost.de"
942         website   = "http://www.selfhost.de/"
943         protocols = ("ipv4",)
944
945         url = "https://carol.selfhost.de/nic/update"
946
947         def _prepare_request_data(self):
948                 data = DDNSProtocolDynDNS2._prepare_request_data(self)
949                 data.update({
950                         "hostname" : "1",
951                 })
952
953                 return data
954
955
956 class DDNSProviderSPDNS(DDNSProtocolDynDNS2, DDNSProvider):
957         handle    = "spdns.org"
958         name      = "SPDNS"
959         website   = "http://spdns.org/"
960         protocols = ("ipv4",)
961
962         # Detailed information about request and response codes are provided
963         # by the vendor. They are using almost the same mechanism and status
964         # codes as dyndns.org so we can inherit all those stuff.
965         #
966         # http://wiki.securepoint.de/index.php/SPDNS_FAQ
967         # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
968
969         url = "https://update.spdns.de/nic/update"
970
971
972 class DDNSProviderStrato(DDNSProtocolDynDNS2, DDNSProvider):
973         handle    = "strato.com"
974         name      = "Strato AG"
975         website   = "http:/www.strato.com/"
976         protocols = ("ipv4",)
977
978         # Information about the request and response can be obtained here:
979         # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
980
981         url = "https://dyndns.strato.com/nic/update"
982
983
984 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2, DDNSProvider):
985         handle    = "twodns.de"
986         name      = "TwoDNS"
987         website   = "http://www.twodns.de"
988         protocols = ("ipv4",)
989
990         # Detailed information about the request can be found here
991         # http://twodns.de/en/faqs
992         # http://twodns.de/en/api
993
994         url = "https://update.twodns.de/update"
995
996         def _prepare_request_data(self):
997                 data = {
998                         "ip" : self.get_address("ipv4"),
999                         "hostname" : self.hostname
1000                 }
1001
1002                 return data
1003
1004
1005 class DDNSProviderUdmedia(DDNSProtocolDynDNS2, DDNSProvider):
1006         handle    = "udmedia.de"
1007         name      = "Udmedia GmbH"
1008         website   = "http://www.udmedia.de"
1009         protocols = ("ipv4",)
1010
1011         # Information about the request can be found here
1012         # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1013
1014         url = "https://www.udmedia.de/nic/update"
1015
1016
1017 class DDNSProviderVariomedia(DDNSProtocolDynDNS2, DDNSProvider):
1018         handle    = "variomedia.de"
1019         name      = "Variomedia"
1020         website   = "http://www.variomedia.de/"
1021         protocols = ("ipv6", "ipv4",)
1022
1023         # Detailed information about the request can be found here
1024         # https://dyndns.variomedia.de/
1025
1026         url = "https://dyndns.variomedia.de/nic/update"
1027
1028         @property
1029         def proto(self):
1030                 return self.get("proto")
1031
1032         def _prepare_request_data(self):
1033                 data = {
1034                         "hostname" : self.hostname,
1035                         "myip"     : self.get_address(self.proto)
1036                 }
1037
1038                 return data
1039
1040
1041 class DDNSProviderZoneedit(DDNSProtocolDynDNS2, DDNSProvider):
1042         handle    = "zoneedit.com"
1043         name      = "Zoneedit"
1044         website   = "http://www.zoneedit.com"
1045         protocols = ("ipv4",)
1046
1047         # Detailed information about the request and the response codes can be
1048         # obtained here:
1049         # http://www.zoneedit.com/doc/api/other.html
1050         # http://www.zoneedit.com/faq.html
1051
1052         url = "https://dynamic.zoneedit.com/auth/dynamic.html"
1053
1054         @property
1055         def proto(self):
1056                 return self.get("proto")
1057
1058         def update(self):
1059                 data = {
1060                         "dnsto" : self.get_address(self.proto),
1061                         "host"  : self.hostname
1062                 }
1063
1064                 # Send update to the server.
1065                 response = self.send_request(self.url, username=self.username, password=self.password,
1066                         data=data)
1067
1068                 # Get the full response message.
1069                 output = response.read()
1070
1071                 # Handle success messages.
1072                 if output.startswith("<SUCCESS"):
1073                         return
1074
1075                 # Handle error codes.
1076                 if output.startswith("invalid login"):
1077                         raise DDNSAuthenticationError
1078                 elif output.startswith("<ERROR CODE=\"704\""):
1079                         raise DDNSRequestError(_("No valid FQDN was given.")) 
1080                 elif output.startswith("<ERROR CODE=\"702\""):
1081                         raise DDNSInternalServerError
1082
1083                 # If we got here, some other update error happened.
1084                 raise DDNSUpdateError