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