Move XML response parser into own class.
[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 DDNSProviderFreeDNSAfraidOrg(DDNSProvider):
536         handle    = "freedns.afraid.org"
537         name      = "freedns.afraid.org"
538         website   = "http://freedns.afraid.org/"
539
540         # No information about the request or response could be found on the vendor
541         # page. All used values have been collected by testing.
542         url = "https://freedns.afraid.org/dynamic/update.php"
543
544         @property
545         def proto(self):
546                 return self.get("proto")
547
548         def update(self):
549                 address = self.get_address(self.proto)
550
551                 data = {
552                         "address" : address,
553                 }
554
555                 # Add auth token to the update url.
556                 url = "%s?%s" % (self.url, self.token)
557
558                 # Send update to the server.
559                 response = self.send_request(url, data=data)
560
561                 if output.startswith("Updated") or "has not changed" in output:
562                         return
563
564                 # Handle error codes.
565                 if output == "ERROR: Unable to locate this record":
566                         raise DDNSAuthenticationError
567                 elif "is an invalid IP address" in output:
568                         raise DDNSRequestError(_("Invalid IP address has been sent."))
569
570                 # If we got here, some other update error happened.
571                 raise DDNSUpdateError
572
573
574 class DDNSProviderLightningWireLabs(DDNSProvider):
575         handle    = "dns.lightningwirelabs.com"
576         name      = "Lightning Wire Labs DNS Service"
577         website   = "http://dns.lightningwirelabs.com/"
578
579         # Information about the format of the HTTPS request is to be found
580         # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
581
582         url = "https://dns.lightningwirelabs.com/update"
583
584         def update(self):
585                 data =  {
586                         "hostname" : self.hostname,
587                         "address6" : self.get_address("ipv6", "-"),
588                         "address4" : self.get_address("ipv4", "-"),
589                 }
590
591                 # Check if a token has been set.
592                 if self.token:
593                         data["token"] = self.token
594
595                 # Check for username and password.
596                 elif self.username and self.password:
597                         data.update({
598                                 "username" : self.username,
599                                 "password" : self.password,
600                         })
601
602                 # Raise an error if no auth details are given.
603                 else:
604                         raise DDNSConfigurationError
605
606                 # Send update to the server.
607                 response = self.send_request(self.url, data=data)
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 DDNSProviderNamecheap(DDNSResponseParserXML, DDNSProvider):
618         handle    = "namecheap.com"
619         name      = "Namecheap"
620         website   = "http://namecheap.com"
621         protocols = ("ipv4",)
622
623         # Information about the format of the HTTP request is to be found
624         # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
625         # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
626
627         url = "https://dynamicdns.park-your-domain.com/update"
628
629         def update(self):
630                 # Namecheap requires the hostname splitted into a host and domain part.
631                 host, domain = self.hostname.split(".", 1)
632
633                 data = {
634                         "ip"       : self.get_address("ipv4"),
635                         "password" : self.password,
636                         "host"     : host,
637                         "domain"   : domain
638                 }
639
640                 # Send update to the server.
641                 response = self.send_request(self.url, data=data)
642
643                 # Get the full response message.
644                 output = response.read()
645
646                 # Handle success messages.
647                 if self.get_xml_tag_value(output, "IP") == self.get_address("ipv4"):
648                         return
649
650                 # Handle error codes.
651                 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
652
653                 if errorcode == "304156":
654                         raise DDNSAuthenticationError
655                 elif errorcode == "316153":
656                         raise DDNSRequestError(_("Domain not found."))
657                 elif errorcode == "316154":
658                         raise DDNSRequestError(_("Domain not active."))
659                 elif errorcode in ("380098", "380099"):
660                         raise DDNSInternalServerError
661
662                 # If we got here, some other update error happened.
663                 raise DDNSUpdateError
664
665
666 class DDNSProviderNOIP(DDNSProtocolDynDNS2, DDNSProvider):
667         handle    = "no-ip.com"
668         name      = "No-IP"
669         website   = "http://www.no-ip.com/"
670         protocols = ("ipv4",)
671
672         # Information about the format of the HTTP request is to be found
673         # here: http://www.no-ip.com/integrate/request and
674         # here: http://www.no-ip.com/integrate/response
675
676         url = "http://dynupdate.no-ip.com/nic/update"
677
678         def _prepare_request_data(self):
679                 data = {
680                         "hostname" : self.hostname,
681                         "address"  : self.get_address("ipv4"),
682                 }
683
684                 return data
685
686
687 class DDNSProviderOVH(DDNSProtocolDynDNS2, DDNSProvider):
688         handle    = "ovh.com"
689         name      = "OVH"
690         website   = "http://www.ovh.com/"
691         protocols = ("ipv4",)
692
693         # OVH only provides very limited information about how to
694         # update a DynDNS host. They only provide the update url
695         # on the their german subpage.
696         #
697         # http://hilfe.ovh.de/DomainDynHost
698
699         url = "https://www.ovh.com/nic/update"
700
701         def _prepare_request_data(self):
702                 data = DDNSProtocolDynDNS2._prepare_request_data(self)
703                 data.update({
704                         "system" : "dyndns",
705                 })
706
707                 return data
708
709
710 class DDNSProviderRegfish(DDNSProvider):
711         handle  = "regfish.com"
712         name    = "Regfish GmbH"
713         website = "http://www.regfish.com/"
714
715         # A full documentation to the providers api can be found here
716         # but is only available in german.
717         # https://www.regfish.de/domains/dyndns/dokumentation
718
719         url = "https://dyndns.regfish.de/"
720
721         def update(self):
722                 data = {
723                         "fqdn" : self.hostname,
724                 }
725
726                 # Check if we update an IPv6 address.
727                 address6 = self.get_address("ipv6")
728                 if address6:
729                         data["ipv6"] = address6
730
731                 # Check if we update an IPv4 address.
732                 address4 = self.get_address("ipv4")
733                 if address4:
734                         data["ipv4"] = address4
735
736                 # Raise an error if none address is given.
737                 if not data.has_key("ipv6") and not data.has_key("ipv4"):
738                         raise DDNSConfigurationError
739
740                 # Check if a token has been set.
741                 if self.token:
742                         data["token"] = self.token
743
744                 # Raise an error if no token and no useranem and password
745                 # are given.
746                 elif not self.username and not self.password:
747                         raise DDNSConfigurationError(_("No Auth details specified."))
748
749                 # HTTP Basic Auth is only allowed if no token is used.
750                 if self.token:
751                         # Send update to the server.
752                         response = self.send_request(self.url, data=data)
753                 else:
754                         # Send update to the server.
755                         response = self.send_request(self.url, username=self.username, password=self.password,
756                                 data=data)
757
758                 # Get the full response message.
759                 output = response.read()
760
761                 # Handle success messages.
762                 if "100" in output or "101" in output:
763                         return
764
765                 # Handle error codes.
766                 if "401" or "402" in output:
767                         raise DDNSAuthenticationError
768                 elif "408" in output:
769                         raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
770                 elif "409" in output:
771                         raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
772                 elif "412" in output:
773                         raise DDNSRequestError(_("No valid FQDN was given."))
774                 elif "414" in output:
775                         raise DDNSInternalServerError
776
777                 # If we got here, some other update error happened.
778                 raise DDNSUpdateError
779
780
781 class DDNSProviderSelfhost(DDNSProtocolDynDNS2, DDNSProvider):
782         handle    = "selfhost.de"
783         name      = "Selfhost.de"
784         website   = "http://www.selfhost.de/"
785         protocols = ("ipv4",)
786
787         url = "https://carol.selfhost.de/nic/update"
788
789         def _prepare_request_data(self):
790                 data = DDNSProviderDynDNS._prepare_request_data(self)
791                 data.update({
792                         "hostname" : "1",
793                 })
794
795                 return data
796
797
798 class DDNSProviderSPDNS(DDNSProtocolDynDNS2, DDNSProvider):
799         handle    = "spdns.org"
800         name      = "SPDNS"
801         website   = "http://spdns.org/"
802         protocols = ("ipv4",)
803
804         # Detailed information about request and response codes are provided
805         # by the vendor. They are using almost the same mechanism and status
806         # codes as dyndns.org so we can inherit all those stuff.
807         #
808         # http://wiki.securepoint.de/index.php/SPDNS_FAQ
809         # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
810
811         url = "https://update.spdns.de/nic/update"
812
813
814 class DDNSProviderStrato(DDNSProtocolDynDNS2, DDNSProvider):
815         handle    = "strato.com"
816         name      = "Strato AG"
817         website   = "http:/www.strato.com/"
818         protocols = ("ipv4",)
819
820         # Information about the request and response can be obtained here:
821         # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
822
823         url = "https://dyndns.strato.com/nic/update"
824
825
826 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2, DDNSProvider):
827         handle    = "twodns.de"
828         name      = "TwoDNS"
829         website   = "http://www.twodns.de"
830         protocols = ("ipv4",)
831
832         # Detailed information about the request can be found here
833         # http://twodns.de/en/faqs
834         # http://twodns.de/en/api
835
836         url = "https://update.twodns.de/update"
837
838         def _prepare_request_data(self):
839                 data = {
840                         "ip" : self.get_address("ipv4"),
841                         "hostname" : self.hostname
842                 }
843
844                 return data
845
846
847 class DDNSProviderUdmedia(DDNSProtocolDynDNS2, DDNSProvider):
848         handle    = "udmedia.de"
849         name      = "Udmedia GmbH"
850         website   = "http://www.udmedia.de"
851         protocols = ("ipv4",)
852
853         # Information about the request can be found here
854         # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
855
856         url = "https://www.udmedia.de/nic/update"
857
858
859 class DDNSProviderVariomedia(DDNSProtocolDynDNS2, DDNSProvider):
860         handle    = "variomedia.de"
861         name      = "Variomedia"
862         website   = "http://www.variomedia.de/"
863         protocols = ("ipv6", "ipv4",)
864
865         # Detailed information about the request can be found here
866         # https://dyndns.variomedia.de/
867
868         url = "https://dyndns.variomedia.de/nic/update"
869
870         @property
871         def proto(self):
872                 return self.get("proto")
873
874         def _prepare_request_data(self):
875                 data = {
876                         "hostname" : self.hostname,
877                         "myip"     : self.get_address(self.proto)
878                 }
879
880                 return data
881
882
883 class DDNSProviderZoneedit(DDNSProtocolDynDNS2, DDNSProvider):
884         handle    = "zoneedit.com"
885         name      = "Zoneedit"
886         website   = "http://www.zoneedit.com"
887         protocols = ("ipv4",)
888
889         # Detailed information about the request and the response codes can be
890         # obtained here:
891         # http://www.zoneedit.com/doc/api/other.html
892         # http://www.zoneedit.com/faq.html
893
894         url = "https://dynamic.zoneedit.com/auth/dynamic.html"
895
896         @property
897         def proto(self):
898                 return self.get("proto")
899
900         def update(self):
901                 data = {
902                         "dnsto" : self.get_address(self.proto),
903                         "host"  : self.hostname
904                 }
905
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 output.startswith("<SUCCESS"):
915                         return
916
917                 # Handle error codes.
918                 if output.startswith("invalid login"):
919                         raise DDNSAuthenticationError
920                 elif output.startswith("<ERROR CODE=\"704\""):
921                         raise DDNSRequestError(_("No valid FQDN was given.")) 
922                 elif output.startswith("<ERROR CODE=\"702\""):
923                         raise DDNSInternalServerError
924
925                 # If we got here, some other update error happened.
926                 raise DDNSUpdateError