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