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