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