Print "already up to date" message only in debugging mode
[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 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                 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                 data.update({
520                         "myipv6"   : self.get_address("ipv6"),
521                 })
522
523                 return data
524
525
526 class DDNSProviderEasyDNS(DDNSProtocolDynDNS2, DDNSProvider):
527         handle    = "easydns.com"
528         name      = "EasyDNS"
529         website   = "http://www.easydns.com/"
530         protocols = ("ipv4",)
531
532         # There is only some basic documentation provided by the vendor,
533         # also searching the web gain very poor results.
534         # http://mediawiki.easydns.com/index.php/Dynamic_DNS
535
536         url = "http://api.cp.easydns.com/dyn/tomato.php"
537
538
539 class DDNSProviderEnomCom(DDNSResponseParserXML, DDNSProvider):
540         handle    = "enom.com"
541         name      = "eNom Inc."
542         website   = "http://www.enom.com/"
543
544         # There are very detailed information about how to send an update request and
545         # the respone codes.
546         # http://www.enom.com/APICommandCatalog/
547
548         url = "https://dynamic.name-services.com/interface.asp"
549
550         def update(self):
551                 data = {
552                         "command"        : "setdnshost",
553                         "responsetype"   : "xml",
554                         "address"        : self.get_address("ipv4"),
555                         "domainpassword" : self.password,
556                         "zone"           : self.hostname
557                 }
558
559                 # Send update to the server.
560                 response = self.send_request(self.url, data=data)
561
562                 # Get the full response message.
563                 output = response.read()
564
565                 # Handle success messages.
566                 if self.get_xml_tag_value(output, "ErrCount") == "0":
567                         return
568
569                 # Handle error codes.
570                 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
571
572                 if errorcode == "304155":
573                         raise DDNSAuthenticationError
574                 elif errorcode == "304153":
575                         raise DDNSRequestError(_("Domain not found."))
576
577                 # If we got here, some other update error happened.
578                 raise DDNSUpdateError
579
580
581 class DDNSProviderEntryDNS(DDNSProvider):
582         handle    = "entrydns.net"
583         name      = "EntryDNS"
584         website   = "http://entrydns.net/"
585         protocols = ("ipv4",)
586
587         # Some very tiny details about their so called "Simple API" can be found
588         # here: https://entrydns.net/help
589         url = "https://entrydns.net/records/modify"
590
591         def update(self):
592                 data = {
593                         "ip" : self.get_address("ipv4")
594                 }
595
596                 # Add auth token to the update url.
597                 url = "%s/%s" % (self.url, self.token)
598
599                 # Send update to the server.
600                 try:
601                         response = self.send_request(url, method="PUT", data=data)
602
603                 # Handle error codes
604                 except urllib2.HTTPError, e:
605                         if e.code == 404:
606                                 raise DDNSAuthenticationError
607
608                         elif e.code == 422:
609                                 raise DDNSRequestError(_("An invalid IP address was submitted"))
610
611                         raise
612
613                 # Handle success messages.
614                 if response.code == 200:
615                         return
616
617                 # If we got here, some other update error happened.
618                 raise DDNSUpdateError
619
620
621 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider):
622         handle    = "freedns.afraid.org"
623         name      = "freedns.afraid.org"
624         website   = "http://freedns.afraid.org/"
625
626         # No information about the request or response could be found on the vendor
627         # page. All used values have been collected by testing.
628         url = "https://freedns.afraid.org/dynamic/update.php"
629
630         @property
631         def proto(self):
632                 return self.get("proto")
633
634         def update(self):
635                 address = self.get_address(self.proto)
636
637                 data = {
638                         "address" : address,
639                 }
640
641                 # Add auth token to the update url.
642                 url = "%s?%s" % (self.url, self.token)
643
644                 # Send update to the server.
645                 response = self.send_request(url, data=data)
646
647                 # Get the full response message.
648                 output = response.read()
649
650                 # Handle success messages.
651                 if output.startswith("Updated") or "has not changed" in output:
652                         return
653
654                 # Handle error codes.
655                 if output == "ERROR: Unable to locate this record":
656                         raise DDNSAuthenticationError
657                 elif "is an invalid IP address" in output:
658                         raise DDNSRequestError(_("Invalid IP address has been sent."))
659
660                 # If we got here, some other update error happened.
661                 raise DDNSUpdateError
662
663
664 class DDNSProviderLightningWireLabs(DDNSProvider):
665         handle    = "dns.lightningwirelabs.com"
666         name      = "Lightning Wire Labs DNS Service"
667         website   = "http://dns.lightningwirelabs.com/"
668
669         # Information about the format of the HTTPS request is to be found
670         # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
671
672         url = "https://dns.lightningwirelabs.com/update"
673
674         def update(self):
675                 data =  {
676                         "hostname" : self.hostname,
677                         "address6" : self.get_address("ipv6", "-"),
678                         "address4" : self.get_address("ipv4", "-"),
679                 }
680
681                 # Check if a token has been set.
682                 if self.token:
683                         data["token"] = self.token
684
685                 # Check for username and password.
686                 elif self.username and self.password:
687                         data.update({
688                                 "username" : self.username,
689                                 "password" : self.password,
690                         })
691
692                 # Raise an error if no auth details are given.
693                 else:
694                         raise DDNSConfigurationError
695
696                 # Send update to the server.
697                 response = self.send_request(self.url, data=data)
698
699                 # Handle success messages.
700                 if response.code == 200:
701                         return
702
703                 # If we got here, some other update error happened.
704                 raise DDNSUpdateError
705
706
707 class DDNSProviderNamecheap(DDNSResponseParserXML, DDNSProvider):
708         handle    = "namecheap.com"
709         name      = "Namecheap"
710         website   = "http://namecheap.com"
711         protocols = ("ipv4",)
712
713         # Information about the format of the HTTP request is to be found
714         # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
715         # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
716
717         url = "https://dynamicdns.park-your-domain.com/update"
718
719         def update(self):
720                 # Namecheap requires the hostname splitted into a host and domain part.
721                 host, domain = self.hostname.split(".", 1)
722
723                 data = {
724                         "ip"       : self.get_address("ipv4"),
725                         "password" : self.password,
726                         "host"     : host,
727                         "domain"   : domain
728                 }
729
730                 # Send update to the server.
731                 response = self.send_request(self.url, data=data)
732
733                 # Get the full response message.
734                 output = response.read()
735
736                 # Handle success messages.
737                 if self.get_xml_tag_value(output, "IP") == self.get_address("ipv4"):
738                         return
739
740                 # Handle error codes.
741                 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
742
743                 if errorcode == "304156":
744                         raise DDNSAuthenticationError
745                 elif errorcode == "316153":
746                         raise DDNSRequestError(_("Domain not found."))
747                 elif errorcode == "316154":
748                         raise DDNSRequestError(_("Domain not active."))
749                 elif errorcode in ("380098", "380099"):
750                         raise DDNSInternalServerError
751
752                 # If we got here, some other update error happened.
753                 raise DDNSUpdateError
754
755
756 class DDNSProviderNOIP(DDNSProtocolDynDNS2, DDNSProvider):
757         handle    = "no-ip.com"
758         name      = "No-IP"
759         website   = "http://www.no-ip.com/"
760         protocols = ("ipv4",)
761
762         # Information about the format of the HTTP request is to be found
763         # here: http://www.no-ip.com/integrate/request and
764         # here: http://www.no-ip.com/integrate/response
765
766         url = "http://dynupdate.no-ip.com/nic/update"
767
768         def _prepare_request_data(self):
769                 data = {
770                         "hostname" : self.hostname,
771                         "address"  : self.get_address("ipv4"),
772                 }
773
774                 return data
775
776
777 class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2, DDNSProvider):
778         handle    = "nsupdate.info"
779         name      = "nsupdate.info"
780         website   = "http://www.nsupdate.info/"
781         protocols = ("ipv6", "ipv4",)
782
783         # Information about the format of the HTTP request can be found
784         # after login on the provider user intrface and here:
785         # http://nsupdateinfo.readthedocs.org/en/latest/user.html
786
787         # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
788         # and for the password a so called secret.
789         @property
790         def username(self):
791                 return self.get("hostname")
792
793         @property
794         def password(self):
795                 return self.get("secret")
796
797         @property
798         def proto(self):
799                 return self.get("proto")
800
801         @property
802         def url(self):
803                 # The update URL is different by the used protocol.
804                 if self.proto == "ipv4":
805                         return "https://ipv4.nsupdate.info/nic/update"
806                 elif self.proto == "ipv6":
807                         return "https://ipv6.nsupdate.info/nic/update"
808                 else:
809                         raise DDNSUpdateError(_("Invalid protocol has been given"))
810
811         def _prepare_request_data(self):
812                 data = {
813                         "myip" : self.get_address(self.proto),
814                 }
815
816                 return data
817
818
819 class DDNSProviderOpenDNS(DDNSProtocolDynDNS2, DDNSProvider):
820         handle    = "opendns.com"
821         name      = "OpenDNS"
822         website   = "http://www.opendns.com"
823
824         # Detailed information about the update request and possible
825         # response codes can be obtained from here:
826         # https://support.opendns.com/entries/23891440
827
828         url = "https://updates.opendns.com/nic/update"
829
830         @property
831         def proto(self):
832                 return self.get("proto")
833
834         def _prepare_request_data(self):
835                 data = {
836                         "hostname" : self.hostname,
837                         "myip"     : self.get_address(self.proto)
838                 }
839
840                 return data
841
842
843 class DDNSProviderOVH(DDNSProtocolDynDNS2, DDNSProvider):
844         handle    = "ovh.com"
845         name      = "OVH"
846         website   = "http://www.ovh.com/"
847         protocols = ("ipv4",)
848
849         # OVH only provides very limited information about how to
850         # update a DynDNS host. They only provide the update url
851         # on the their german subpage.
852         #
853         # http://hilfe.ovh.de/DomainDynHost
854
855         url = "https://www.ovh.com/nic/update"
856
857         def _prepare_request_data(self):
858                 data = DDNSProtocolDynDNS2._prepare_request_data(self)
859                 data.update({
860                         "system" : "dyndns",
861                 })
862
863                 return data
864
865
866 class DDNSProviderRegfish(DDNSProvider):
867         handle  = "regfish.com"
868         name    = "Regfish GmbH"
869         website = "http://www.regfish.com/"
870
871         # A full documentation to the providers api can be found here
872         # but is only available in german.
873         # https://www.regfish.de/domains/dyndns/dokumentation
874
875         url = "https://dyndns.regfish.de/"
876
877         def update(self):
878                 data = {
879                         "fqdn" : self.hostname,
880                 }
881
882                 # Check if we update an IPv6 address.
883                 address6 = self.get_address("ipv6")
884                 if address6:
885                         data["ipv6"] = address6
886
887                 # Check if we update an IPv4 address.
888                 address4 = self.get_address("ipv4")
889                 if address4:
890                         data["ipv4"] = address4
891
892                 # Raise an error if none address is given.
893                 if not data.has_key("ipv6") and not data.has_key("ipv4"):
894                         raise DDNSConfigurationError
895
896                 # Check if a token has been set.
897                 if self.token:
898                         data["token"] = self.token
899
900                 # Raise an error if no token and no useranem and password
901                 # are given.
902                 elif not self.username and not self.password:
903                         raise DDNSConfigurationError(_("No Auth details specified."))
904
905                 # HTTP Basic Auth is only allowed if no token is used.
906                 if self.token:
907                         # Send update to the server.
908                         response = self.send_request(self.url, data=data)
909                 else:
910                         # Send update to the server.
911                         response = self.send_request(self.url, username=self.username, password=self.password,
912                                 data=data)
913
914                 # Get the full response message.
915                 output = response.read()
916
917                 # Handle success messages.
918                 if "100" in output or "101" in output:
919                         return
920
921                 # Handle error codes.
922                 if "401" or "402" in output:
923                         raise DDNSAuthenticationError
924                 elif "408" in output:
925                         raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
926                 elif "409" in output:
927                         raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
928                 elif "412" in output:
929                         raise DDNSRequestError(_("No valid FQDN was given."))
930                 elif "414" in output:
931                         raise DDNSInternalServerError
932
933                 # If we got here, some other update error happened.
934                 raise DDNSUpdateError
935
936
937 class DDNSProviderSelfhost(DDNSProtocolDynDNS2, DDNSProvider):
938         handle    = "selfhost.de"
939         name      = "Selfhost.de"
940         website   = "http://www.selfhost.de/"
941         protocols = ("ipv4",)
942
943         url = "https://carol.selfhost.de/nic/update"
944
945         def _prepare_request_data(self):
946                 data = DDNSProtocolDynDNS2._prepare_request_data(self)
947                 data.update({
948                         "hostname" : "1",
949                 })
950
951                 return data
952
953
954 class DDNSProviderSPDNS(DDNSProtocolDynDNS2, DDNSProvider):
955         handle    = "spdns.org"
956         name      = "SPDNS"
957         website   = "http://spdns.org/"
958         protocols = ("ipv4",)
959
960         # Detailed information about request and response codes are provided
961         # by the vendor. They are using almost the same mechanism and status
962         # codes as dyndns.org so we can inherit all those stuff.
963         #
964         # http://wiki.securepoint.de/index.php/SPDNS_FAQ
965         # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
966
967         url = "https://update.spdns.de/nic/update"
968
969
970 class DDNSProviderStrato(DDNSProtocolDynDNS2, DDNSProvider):
971         handle    = "strato.com"
972         name      = "Strato AG"
973         website   = "http:/www.strato.com/"
974         protocols = ("ipv4",)
975
976         # Information about the request and response can be obtained here:
977         # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
978
979         url = "https://dyndns.strato.com/nic/update"
980
981
982 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2, DDNSProvider):
983         handle    = "twodns.de"
984         name      = "TwoDNS"
985         website   = "http://www.twodns.de"
986         protocols = ("ipv4",)
987
988         # Detailed information about the request can be found here
989         # http://twodns.de/en/faqs
990         # http://twodns.de/en/api
991
992         url = "https://update.twodns.de/update"
993
994         def _prepare_request_data(self):
995                 data = {
996                         "ip" : self.get_address("ipv4"),
997                         "hostname" : self.hostname
998                 }
999
1000                 return data
1001
1002
1003 class DDNSProviderUdmedia(DDNSProtocolDynDNS2, DDNSProvider):
1004         handle    = "udmedia.de"
1005         name      = "Udmedia GmbH"
1006         website   = "http://www.udmedia.de"
1007         protocols = ("ipv4",)
1008
1009         # Information about the request can be found here
1010         # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1011
1012         url = "https://www.udmedia.de/nic/update"
1013
1014
1015 class DDNSProviderVariomedia(DDNSProtocolDynDNS2, DDNSProvider):
1016         handle    = "variomedia.de"
1017         name      = "Variomedia"
1018         website   = "http://www.variomedia.de/"
1019         protocols = ("ipv6", "ipv4",)
1020
1021         # Detailed information about the request can be found here
1022         # https://dyndns.variomedia.de/
1023
1024         url = "https://dyndns.variomedia.de/nic/update"
1025
1026         @property
1027         def proto(self):
1028                 return self.get("proto")
1029
1030         def _prepare_request_data(self):
1031                 data = {
1032                         "hostname" : self.hostname,
1033                         "myip"     : self.get_address(self.proto)
1034                 }
1035
1036                 return data
1037
1038
1039 class DDNSProviderZoneedit(DDNSProtocolDynDNS2, DDNSProvider):
1040         handle    = "zoneedit.com"
1041         name      = "Zoneedit"
1042         website   = "http://www.zoneedit.com"
1043         protocols = ("ipv4",)
1044
1045         # Detailed information about the request and the response codes can be
1046         # obtained here:
1047         # http://www.zoneedit.com/doc/api/other.html
1048         # http://www.zoneedit.com/faq.html
1049
1050         url = "https://dynamic.zoneedit.com/auth/dynamic.html"
1051
1052         @property
1053         def proto(self):
1054                 return self.get("proto")
1055
1056         def update(self):
1057                 data = {
1058                         "dnsto" : self.get_address(self.proto),
1059                         "host"  : self.hostname
1060                 }
1061
1062                 # Send update to the server.
1063                 response = self.send_request(self.url, username=self.username, password=self.password,
1064                         data=data)
1065
1066                 # Get the full response message.
1067                 output = response.read()
1068
1069                 # Handle success messages.
1070                 if output.startswith("<SUCCESS"):
1071                         return
1072
1073                 # Handle error codes.
1074                 if output.startswith("invalid login"):
1075                         raise DDNSAuthenticationError
1076                 elif output.startswith("<ERROR CODE=\"704\""):
1077                         raise DDNSRequestError(_("No valid FQDN was given.")) 
1078                 elif output.startswith("<ERROR CODE=\"702\""):
1079                         raise DDNSInternalServerError
1080
1081                 # If we got here, some other update error happened.
1082                 raise DDNSUpdateError