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