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