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