]> git.ipfire.org Git - ddns.git/blame - src/ddns/providers.py
Add "badagent" response handling for DDNSProtocolDynDNS2.
[ddns.git] / src / ddns / providers.py
CommitLineData
f22ab085 1#!/usr/bin/python
3fdcb9d1
MT
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###############################################################################
f22ab085 21
7399fc5b 22import logging
a892c594 23import subprocess
3b16fdb1 24import urllib2
d1cd57eb 25import xml.dom.minidom
7399fc5b
MT
26
27from i18n import _
28
f22ab085
MT
29# Import all possible exception types.
30from .errors import *
31
7399fc5b
MT
32logger = logging.getLogger("ddns.providers")
33logger.propagate = 1
34
adfe6272
MT
35_providers = {}
36
37def get():
38 """
39 Returns a dict with all automatically registered providers.
40 """
41 return _providers.copy()
42
f22ab085 43class DDNSProvider(object):
6a11646e
MT
44 # A short string that uniquely identifies
45 # this provider.
46 handle = None
f22ab085 47
6a11646e
MT
48 # The full name of the provider.
49 name = None
f22ab085 50
6a11646e
MT
51 # A weburl to the homepage of the provider.
52 # (Where to register a new account?)
53 website = None
f22ab085 54
6a11646e
MT
55 # A list of supported protocols.
56 protocols = ("ipv6", "ipv4")
f22ab085
MT
57
58 DEFAULT_SETTINGS = {}
59
adfe6272
MT
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
f22ab085
MT
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
f22ab085
MT
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
46687828
SS
119 @property
120 def token(self):
121 """
122 Fast access to the token.
123 """
124 return self.get("token")
125
9da3e685
MT
126 def __call__(self, force=False):
127 if force:
c3888f15 128 logger.debug(_("Updating %s forced") % self.hostname)
9da3e685 129
7399fc5b 130 # Check if we actually need to update this host.
9da3e685 131 elif self.is_uptodate(self.protocols):
7e1cc573 132 logger.debug(_("The dynamic host %(hostname)s (%(provider)s) is already up to date") % \
12b3818b 133 { "hostname" : self.hostname, "provider" : self.name })
7399fc5b
MT
134 return
135
136 # Execute the update.
5f402f36
MT
137 self.update()
138
12b3818b
MT
139 logger.info(_("Dynamic DNS update for %(hostname)s (%(provider)s) successful") % \
140 { "hostname" : self.hostname, "provider" : self.name })
141
5f402f36 142 def update(self):
f22ab085
MT
143 raise NotImplementedError
144
7399fc5b
MT
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
38d81db4
MT
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
7399fc5b
MT
161 if not current_address in addresses:
162 return False
163
164 return True
165
f22ab085
MT
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
e3c70807 173 def get_address(self, proto, default=None):
f22ab085
MT
174 """
175 Proxy method to get the current IP address.
176 """
e3c70807 177 return self.core.system.get_address(proto) or default
f22ab085
MT
178
179
5d4bec40
MT
180class 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
486c1b9d 189 # http://dyn.com/support/developers/api/perform-update/
5d4bec40
MT
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
af97e369 217 elif output == "abuse":
5d4bec40
MT
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."))
6ddfd5c7
SS
227 elif output == "badagent":
228 raise DDNSBlockedError
5d4bec40
MT
229
230 # If we got here, some other update error happened.
231 raise DDNSUpdateError(_("Server response: %s") % output)
232
233
78c9780b
SS
234class 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
3b16fdb1 263class DDNSProviderAllInkl(DDNSProvider):
6a11646e
MT
264 handle = "all-inkl.com"
265 name = "All-inkl.com"
266 website = "http://all-inkl.com/"
267 protocols = ("ipv4",)
3b16fdb1
SS
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):
3b16fdb1
SS
277 # There is no additional data required so we directly can
278 # send our request.
536e87d1 279 response = self.send_request(self.url, username=self.username, password=self.password)
3b16fdb1
SS
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
a892c594
MT
292class 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
97998aac
MW
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
a892c594
MT
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
f3cf1f70 363class DDNSProviderDHS(DDNSProvider):
6a11646e
MT
364 handle = "dhs.org"
365 name = "DHS International"
366 website = "http://dhs.org/"
367 protocols = ("ipv4",)
f3cf1f70
SS
368
369 # No information about the used update api provided on webpage,
370 # grabed from source code of ez-ipudate.
b2b05ef3 371
f3cf1f70
SS
372 url = "http://members.dhs.org/nic/hosts"
373
5f402f36 374 def update(self):
f3cf1f70
SS
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.
175c9b80 384 response = self.send_request(self.url, username=self.username, password=self.password,
f3cf1f70
SS
385 data=data)
386
387 # Handle success messages.
388 if response.code == 200:
389 return
390
f3cf1f70
SS
391 # If we got here, some other update error happened.
392 raise DDNSUpdateError
393
394
39301272 395class DDNSProviderDNSpark(DDNSProvider):
6a11646e
MT
396 handle = "dnspark.com"
397 name = "DNS Park"
398 website = "http://dnspark.com/"
399 protocols = ("ipv4",)
39301272
SS
400
401 # Informations to the used api can be found here:
402 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
b2b05ef3 403
39301272
SS
404 url = "https://control.dnspark.com/api/dynamic/update.php"
405
5f402f36 406 def update(self):
39301272
SS
407 data = {
408 "domain" : self.hostname,
409 "ip" : self.get_address("ipv4"),
410 }
411
412 # Send update to the server.
175c9b80 413 response = self.send_request(self.url, username=self.username, password=self.password,
39301272
SS
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
43b2cd59
SS
442
443class DDNSProviderDtDNS(DDNSProvider):
6a11646e
MT
444 handle = "dtdns.com"
445 name = "DtDNS"
446 website = "http://dtdns.com/"
447 protocols = ("ipv4",)
43b2cd59
SS
448
449 # Information about the format of the HTTPS request is to be found
450 # http://www.dtdns.com/dtsite/updatespec
b2b05ef3 451
43b2cd59
SS
452 url = "https://www.dtdns.com/api/autodns.cfm"
453
43b2cd59
SS
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
5d4bec40 497class DDNSProviderDynDNS(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
498 handle = "dyndns.org"
499 name = "Dyn"
500 website = "http://dyn.com/dns/"
501 protocols = ("ipv4",)
bfed6701
SS
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/
b2b05ef3 506
bfed6701
SS
507 url = "https://members.dyndns.org/nic/update"
508
bfed6701 509
5d4bec40 510class DDNSProviderDynU(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
511 handle = "dynu.com"
512 name = "Dynu"
513 website = "http://dynu.com/"
514 protocols = ("ipv6", "ipv4",)
3a8407fa
SS
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):
5b153a22 523 data = DDNSProtocolDynDNS2._prepare_request_data(self)
54d3efc8
MT
524
525 # This one supports IPv6
526 data.update({
3a8407fa 527 "myipv6" : self.get_address("ipv6"),
54d3efc8
MT
528 })
529
530 return data
3a8407fa
SS
531
532
5d4bec40
MT
533class DDNSProviderEasyDNS(DDNSProtocolDynDNS2, DDNSProvider):
534 handle = "easydns.com"
535 name = "EasyDNS"
536 website = "http://www.easydns.com/"
537 protocols = ("ipv4",)
ee071271
SS
538
539 # There is only some basic documentation provided by the vendor,
540 # also searching the web gain very poor results.
541 # http://mediawiki.easydns.com/index.php/Dynamic_DNS
542
543 url = "http://api.cp.easydns.com/dyn/tomato.php"
544
545
90fe8843
CE
546class DDNSProviderDomopoli(DDNSProtocolDynDNS2, DDNSProvider):
547 handle = "domopoli.de"
548 name = "domopoli.de"
549 website = "http://domopoli.de/"
550 protocols = ("ipv4",)
551
552 # https://www.domopoli.de/?page=howto#DynDns_start
553
554 url = "http://dyndns.domopoli.de/nic/update"
555
556
35216523
SS
557class DDNSProviderEnomCom(DDNSResponseParserXML, DDNSProvider):
558 handle = "enom.com"
559 name = "eNom Inc."
560 website = "http://www.enom.com/"
561
562 # There are very detailed information about how to send an update request and
563 # the respone codes.
564 # http://www.enom.com/APICommandCatalog/
565
566 url = "https://dynamic.name-services.com/interface.asp"
567
568 def update(self):
569 data = {
570 "command" : "setdnshost",
571 "responsetype" : "xml",
572 "address" : self.get_address("ipv4"),
573 "domainpassword" : self.password,
574 "zone" : self.hostname
575 }
576
577 # Send update to the server.
578 response = self.send_request(self.url, data=data)
579
580 # Get the full response message.
581 output = response.read()
582
583 # Handle success messages.
584 if self.get_xml_tag_value(output, "ErrCount") == "0":
585 return
586
587 # Handle error codes.
588 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
589
590 if errorcode == "304155":
591 raise DDNSAuthenticationError
592 elif errorcode == "304153":
593 raise DDNSRequestError(_("Domain not found."))
594
595 # If we got here, some other update error happened.
596 raise DDNSUpdateError
597
598
ab4e352e
SS
599class DDNSProviderEntryDNS(DDNSProvider):
600 handle = "entrydns.net"
601 name = "EntryDNS"
602 website = "http://entrydns.net/"
603 protocols = ("ipv4",)
604
605 # Some very tiny details about their so called "Simple API" can be found
606 # here: https://entrydns.net/help
607 url = "https://entrydns.net/records/modify"
608
609 def update(self):
610 data = {
611 "ip" : self.get_address("ipv4")
612 }
613
614 # Add auth token to the update url.
615 url = "%s/%s" % (self.url, self.token)
616
617 # Send update to the server.
618 try:
619 response = self.send_request(url, method="PUT", data=data)
620
621 # Handle error codes
622 except urllib2.HTTPError, e:
623 if e.code == 404:
624 raise DDNSAuthenticationError
625
626 elif e.code == 422:
627 raise DDNSRequestError(_("An invalid IP address was submitted"))
628
629 raise
630
631 # Handle success messages.
632 if response.code == 200:
633 return
634
635 # If we got here, some other update error happened.
636 raise DDNSUpdateError
637
638
aa21a4c6 639class DDNSProviderFreeDNSAfraidOrg(DDNSProvider):
6a11646e
MT
640 handle = "freedns.afraid.org"
641 name = "freedns.afraid.org"
642 website = "http://freedns.afraid.org/"
aa21a4c6
SS
643
644 # No information about the request or response could be found on the vendor
645 # page. All used values have been collected by testing.
646 url = "https://freedns.afraid.org/dynamic/update.php"
647
648 @property
649 def proto(self):
650 return self.get("proto")
651
652 def update(self):
653 address = self.get_address(self.proto)
654
655 data = {
656 "address" : address,
657 }
658
659 # Add auth token to the update url.
660 url = "%s?%s" % (self.url, self.token)
661
662 # Send update to the server.
663 response = self.send_request(url, data=data)
664
a204b107
SS
665 # Get the full response message.
666 output = response.read()
667
668 # Handle success messages.
aa21a4c6
SS
669 if output.startswith("Updated") or "has not changed" in output:
670 return
671
672 # Handle error codes.
673 if output == "ERROR: Unable to locate this record":
674 raise DDNSAuthenticationError
675 elif "is an invalid IP address" in output:
676 raise DDNSRequestError(_("Invalid IP address has been sent."))
677
3b524cf2
SS
678 # If we got here, some other update error happened.
679 raise DDNSUpdateError
680
aa21a4c6 681
a08c1b72 682class DDNSProviderLightningWireLabs(DDNSProvider):
6a11646e 683 handle = "dns.lightningwirelabs.com"
fb115fdc 684 name = "Lightning Wire Labs DNS Service"
6a11646e 685 website = "http://dns.lightningwirelabs.com/"
a08c1b72
SS
686
687 # Information about the format of the HTTPS request is to be found
688 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
b2b05ef3 689
a08c1b72
SS
690 url = "https://dns.lightningwirelabs.com/update"
691
5f402f36 692 def update(self):
a08c1b72
SS
693 data = {
694 "hostname" : self.hostname,
e3c70807
MT
695 "address6" : self.get_address("ipv6", "-"),
696 "address4" : self.get_address("ipv4", "-"),
a08c1b72
SS
697 }
698
a08c1b72
SS
699 # Check if a token has been set.
700 if self.token:
701 data["token"] = self.token
702
703 # Check for username and password.
704 elif self.username and self.password:
705 data.update({
706 "username" : self.username,
707 "password" : self.password,
708 })
709
710 # Raise an error if no auth details are given.
711 else:
712 raise DDNSConfigurationError
713
714 # Send update to the server.
cb455540 715 response = self.send_request(self.url, data=data)
a08c1b72
SS
716
717 # Handle success messages.
718 if response.code == 200:
719 return
720
a08c1b72
SS
721 # If we got here, some other update error happened.
722 raise DDNSUpdateError
723
724
78c9780b 725class DDNSProviderNamecheap(DDNSResponseParserXML, DDNSProvider):
6a11646e
MT
726 handle = "namecheap.com"
727 name = "Namecheap"
728 website = "http://namecheap.com"
729 protocols = ("ipv4",)
d1cd57eb
SS
730
731 # Information about the format of the HTTP request is to be found
732 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
733 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
734
735 url = "https://dynamicdns.park-your-domain.com/update"
736
d1cd57eb
SS
737 def update(self):
738 # Namecheap requires the hostname splitted into a host and domain part.
739 host, domain = self.hostname.split(".", 1)
740
741 data = {
742 "ip" : self.get_address("ipv4"),
743 "password" : self.password,
744 "host" : host,
745 "domain" : domain
746 }
747
748 # Send update to the server.
749 response = self.send_request(self.url, data=data)
750
751 # Get the full response message.
752 output = response.read()
753
754 # Handle success messages.
78c9780b 755 if self.get_xml_tag_value(output, "IP") == self.get_address("ipv4"):
d1cd57eb
SS
756 return
757
758 # Handle error codes.
78c9780b 759 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
d1cd57eb
SS
760
761 if errorcode == "304156":
762 raise DDNSAuthenticationError
763 elif errorcode == "316153":
764 raise DDNSRequestError(_("Domain not found."))
765 elif errorcode == "316154":
766 raise DDNSRequestError(_("Domain not active."))
767 elif errorcode in ("380098", "380099"):
768 raise DDNSInternalServerError
769
770 # If we got here, some other update error happened.
771 raise DDNSUpdateError
772
773
5d4bec40
MT
774class DDNSProviderNOIP(DDNSProtocolDynDNS2, DDNSProvider):
775 handle = "no-ip.com"
776 name = "No-IP"
777 website = "http://www.no-ip.com/"
778 protocols = ("ipv4",)
f22ab085
MT
779
780 # Information about the format of the HTTP request is to be found
781 # here: http://www.no-ip.com/integrate/request and
782 # here: http://www.no-ip.com/integrate/response
783
88f39629 784 url = "http://dynupdate.no-ip.com/nic/update"
2de06f59 785
88f39629 786 def _prepare_request_data(self):
2de06f59
MT
787 data = {
788 "hostname" : self.hostname,
f22ab085
MT
789 "address" : self.get_address("ipv4"),
790 }
791
88f39629 792 return data
f22ab085
MT
793
794
31c95e4b
SS
795class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2, DDNSProvider):
796 handle = "nsupdate.info"
797 name = "nsupdate.info"
798 website = "http://www.nsupdate.info/"
799 protocols = ("ipv6", "ipv4",)
800
801 # Information about the format of the HTTP request can be found
802 # after login on the provider user intrface and here:
803 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
804
805 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
806 # and for the password a so called secret.
807 @property
808 def username(self):
809 return self.get("hostname")
810
811 @property
812 def password(self):
813 return self.get("secret")
814
815 @property
816 def proto(self):
817 return self.get("proto")
818
819 @property
820 def url(self):
821 # The update URL is different by the used protocol.
822 if self.proto == "ipv4":
823 return "https://ipv4.nsupdate.info/nic/update"
824 elif self.proto == "ipv6":
825 return "https://ipv6.nsupdate.info/nic/update"
826 else:
827 raise DDNSUpdateError(_("Invalid protocol has been given"))
828
829 def _prepare_request_data(self):
830 data = {
831 "myip" : self.get_address(self.proto),
832 }
833
834 return data
835
836
90663439
SS
837class DDNSProviderOpenDNS(DDNSProtocolDynDNS2, DDNSProvider):
838 handle = "opendns.com"
839 name = "OpenDNS"
840 website = "http://www.opendns.com"
841
842 # Detailed information about the update request and possible
843 # response codes can be obtained from here:
844 # https://support.opendns.com/entries/23891440
845
846 url = "https://updates.opendns.com/nic/update"
847
848 @property
849 def proto(self):
850 return self.get("proto")
851
852 def _prepare_request_data(self):
853 data = {
854 "hostname" : self.hostname,
855 "myip" : self.get_address(self.proto)
856 }
857
858 return data
859
860
5d4bec40
MT
861class DDNSProviderOVH(DDNSProtocolDynDNS2, DDNSProvider):
862 handle = "ovh.com"
863 name = "OVH"
864 website = "http://www.ovh.com/"
865 protocols = ("ipv4",)
a508bda6
SS
866
867 # OVH only provides very limited information about how to
868 # update a DynDNS host. They only provide the update url
869 # on the their german subpage.
870 #
871 # http://hilfe.ovh.de/DomainDynHost
872
873 url = "https://www.ovh.com/nic/update"
874
875 def _prepare_request_data(self):
5d4bec40 876 data = DDNSProtocolDynDNS2._prepare_request_data(self)
54d3efc8
MT
877 data.update({
878 "system" : "dyndns",
879 })
880
881 return data
a508bda6
SS
882
883
ef33455e 884class DDNSProviderRegfish(DDNSProvider):
6a11646e
MT
885 handle = "regfish.com"
886 name = "Regfish GmbH"
887 website = "http://www.regfish.com/"
ef33455e
SS
888
889 # A full documentation to the providers api can be found here
890 # but is only available in german.
891 # https://www.regfish.de/domains/dyndns/dokumentation
892
893 url = "https://dyndns.regfish.de/"
894
895 def update(self):
896 data = {
897 "fqdn" : self.hostname,
898 }
899
900 # Check if we update an IPv6 address.
901 address6 = self.get_address("ipv6")
902 if address6:
903 data["ipv6"] = address6
904
905 # Check if we update an IPv4 address.
906 address4 = self.get_address("ipv4")
907 if address4:
908 data["ipv4"] = address4
909
910 # Raise an error if none address is given.
911 if not data.has_key("ipv6") and not data.has_key("ipv4"):
912 raise DDNSConfigurationError
913
914 # Check if a token has been set.
915 if self.token:
916 data["token"] = self.token
917
918 # Raise an error if no token and no useranem and password
919 # are given.
920 elif not self.username and not self.password:
921 raise DDNSConfigurationError(_("No Auth details specified."))
922
923 # HTTP Basic Auth is only allowed if no token is used.
924 if self.token:
925 # Send update to the server.
926 response = self.send_request(self.url, data=data)
927 else:
928 # Send update to the server.
929 response = self.send_request(self.url, username=self.username, password=self.password,
930 data=data)
931
932 # Get the full response message.
933 output = response.read()
934
935 # Handle success messages.
936 if "100" in output or "101" in output:
937 return
938
939 # Handle error codes.
940 if "401" or "402" in output:
941 raise DDNSAuthenticationError
942 elif "408" in output:
943 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
944 elif "409" in output:
945 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
946 elif "412" in output:
947 raise DDNSRequestError(_("No valid FQDN was given."))
948 elif "414" in output:
949 raise DDNSInternalServerError
950
951 # If we got here, some other update error happened.
952 raise DDNSUpdateError
953
954
5d4bec40 955class DDNSProviderSelfhost(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
956 handle = "selfhost.de"
957 name = "Selfhost.de"
958 website = "http://www.selfhost.de/"
5d4bec40 959 protocols = ("ipv4",)
f22ab085 960
04db1862 961 url = "https://carol.selfhost.de/nic/update"
f22ab085 962
04db1862 963 def _prepare_request_data(self):
5b153a22 964 data = DDNSProtocolDynDNS2._prepare_request_data(self)
04db1862
MT
965 data.update({
966 "hostname" : "1",
967 })
f22ab085 968
04db1862 969 return data
b09b1545
SS
970
971
5d4bec40
MT
972class DDNSProviderSPDNS(DDNSProtocolDynDNS2, DDNSProvider):
973 handle = "spdns.org"
974 name = "SPDNS"
975 website = "http://spdns.org/"
976 protocols = ("ipv4",)
b09b1545
SS
977
978 # Detailed information about request and response codes are provided
979 # by the vendor. They are using almost the same mechanism and status
980 # codes as dyndns.org so we can inherit all those stuff.
981 #
982 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
983 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
984
985 url = "https://update.spdns.de/nic/update"
4ec90b93
MT
986
987
5d4bec40
MT
988class DDNSProviderStrato(DDNSProtocolDynDNS2, DDNSProvider):
989 handle = "strato.com"
990 name = "Strato AG"
991 website = "http:/www.strato.com/"
992 protocols = ("ipv4",)
7488825c
SS
993
994 # Information about the request and response can be obtained here:
995 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
996
997 url = "https://dyndns.strato.com/nic/update"
998
999
5d4bec40
MT
1000class DDNSProviderTwoDNS(DDNSProtocolDynDNS2, DDNSProvider):
1001 handle = "twodns.de"
1002 name = "TwoDNS"
1003 website = "http://www.twodns.de"
1004 protocols = ("ipv4",)
a6183090
SS
1005
1006 # Detailed information about the request can be found here
1007 # http://twodns.de/en/faqs
1008 # http://twodns.de/en/api
1009
1010 url = "https://update.twodns.de/update"
1011
1012 def _prepare_request_data(self):
1013 data = {
1014 "ip" : self.get_address("ipv4"),
1015 "hostname" : self.hostname
1016 }
1017
1018 return data
1019
1020
5d4bec40
MT
1021class DDNSProviderUdmedia(DDNSProtocolDynDNS2, DDNSProvider):
1022 handle = "udmedia.de"
1023 name = "Udmedia GmbH"
1024 website = "http://www.udmedia.de"
1025 protocols = ("ipv4",)
03bdd188
SS
1026
1027 # Information about the request can be found here
1028 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1029
1030 url = "https://www.udmedia.de/nic/update"
1031
1032
5d4bec40 1033class DDNSProviderVariomedia(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
1034 handle = "variomedia.de"
1035 name = "Variomedia"
1036 website = "http://www.variomedia.de/"
1037 protocols = ("ipv6", "ipv4",)
c8c7ca8f
SS
1038
1039 # Detailed information about the request can be found here
1040 # https://dyndns.variomedia.de/
1041
1042 url = "https://dyndns.variomedia.de/nic/update"
1043
1044 @property
1045 def proto(self):
1046 return self.get("proto")
1047
1048 def _prepare_request_data(self):
1049 data = {
1050 "hostname" : self.hostname,
1051 "myip" : self.get_address(self.proto)
1052 }
54d3efc8
MT
1053
1054 return data
98fbe467
SS
1055
1056
5d4bec40
MT
1057class DDNSProviderZoneedit(DDNSProtocolDynDNS2, DDNSProvider):
1058 handle = "zoneedit.com"
1059 name = "Zoneedit"
1060 website = "http://www.zoneedit.com"
1061 protocols = ("ipv4",)
98fbe467
SS
1062
1063 # Detailed information about the request and the response codes can be
1064 # obtained here:
1065 # http://www.zoneedit.com/doc/api/other.html
1066 # http://www.zoneedit.com/faq.html
1067
1068 url = "https://dynamic.zoneedit.com/auth/dynamic.html"
1069
1070 @property
1071 def proto(self):
1072 return self.get("proto")
1073
1074 def update(self):
1075 data = {
1076 "dnsto" : self.get_address(self.proto),
1077 "host" : self.hostname
1078 }
1079
1080 # Send update to the server.
1081 response = self.send_request(self.url, username=self.username, password=self.password,
1082 data=data)
1083
1084 # Get the full response message.
1085 output = response.read()
1086
1087 # Handle success messages.
1088 if output.startswith("<SUCCESS"):
1089 return
1090
1091 # Handle error codes.
1092 if output.startswith("invalid login"):
1093 raise DDNSAuthenticationError
1094 elif output.startswith("<ERROR CODE=\"704\""):
1095 raise DDNSRequestError(_("No valid FQDN was given."))
1096 elif output.startswith("<ERROR CODE=\"702\""):
1097 raise DDNSInternalServerError
1098
1099 # If we got here, some other update error happened.
1100 raise DDNSUpdateError