]> git.ipfire.org Git - ddns.git/blame - src/ddns/providers.py
dynu: Fix updating if no IPv6 address is available.
[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
cdc078dc
SS
519 myipv6 = self.get_address("ipv6")
520
521 # Add update information if we have an IPv6 address.
522 if myipv6:
523 data["myipv6"] = myipv6
54d3efc8
MT
524
525 return data
3a8407fa
SS
526
527
5d4bec40
MT
528class DDNSProviderEasyDNS(DDNSProtocolDynDNS2, DDNSProvider):
529 handle = "easydns.com"
530 name = "EasyDNS"
531 website = "http://www.easydns.com/"
532 protocols = ("ipv4",)
ee071271
SS
533
534 # There is only some basic documentation provided by the vendor,
535 # also searching the web gain very poor results.
536 # http://mediawiki.easydns.com/index.php/Dynamic_DNS
537
538 url = "http://api.cp.easydns.com/dyn/tomato.php"
539
540
35216523
SS
541class DDNSProviderEnomCom(DDNSResponseParserXML, DDNSProvider):
542 handle = "enom.com"
543 name = "eNom Inc."
544 website = "http://www.enom.com/"
545
546 # There are very detailed information about how to send an update request and
547 # the respone codes.
548 # http://www.enom.com/APICommandCatalog/
549
550 url = "https://dynamic.name-services.com/interface.asp"
551
552 def update(self):
553 data = {
554 "command" : "setdnshost",
555 "responsetype" : "xml",
556 "address" : self.get_address("ipv4"),
557 "domainpassword" : self.password,
558 "zone" : self.hostname
559 }
560
561 # Send update to the server.
562 response = self.send_request(self.url, data=data)
563
564 # Get the full response message.
565 output = response.read()
566
567 # Handle success messages.
568 if self.get_xml_tag_value(output, "ErrCount") == "0":
569 return
570
571 # Handle error codes.
572 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
573
574 if errorcode == "304155":
575 raise DDNSAuthenticationError
576 elif errorcode == "304153":
577 raise DDNSRequestError(_("Domain not found."))
578
579 # If we got here, some other update error happened.
580 raise DDNSUpdateError
581
582
ab4e352e
SS
583class DDNSProviderEntryDNS(DDNSProvider):
584 handle = "entrydns.net"
585 name = "EntryDNS"
586 website = "http://entrydns.net/"
587 protocols = ("ipv4",)
588
589 # Some very tiny details about their so called "Simple API" can be found
590 # here: https://entrydns.net/help
591 url = "https://entrydns.net/records/modify"
592
593 def update(self):
594 data = {
595 "ip" : self.get_address("ipv4")
596 }
597
598 # Add auth token to the update url.
599 url = "%s/%s" % (self.url, self.token)
600
601 # Send update to the server.
602 try:
babc5e6d 603 response = self.send_request(url, data=data)
ab4e352e
SS
604
605 # Handle error codes
606 except urllib2.HTTPError, e:
607 if e.code == 404:
608 raise DDNSAuthenticationError
609
610 elif e.code == 422:
611 raise DDNSRequestError(_("An invalid IP address was submitted"))
612
613 raise
614
615 # Handle success messages.
616 if response.code == 200:
617 return
618
619 # If we got here, some other update error happened.
620 raise DDNSUpdateError
621
622
aa21a4c6 623class DDNSProviderFreeDNSAfraidOrg(DDNSProvider):
6a11646e
MT
624 handle = "freedns.afraid.org"
625 name = "freedns.afraid.org"
626 website = "http://freedns.afraid.org/"
aa21a4c6
SS
627
628 # No information about the request or response could be found on the vendor
629 # page. All used values have been collected by testing.
630 url = "https://freedns.afraid.org/dynamic/update.php"
631
632 @property
633 def proto(self):
634 return self.get("proto")
635
636 def update(self):
637 address = self.get_address(self.proto)
638
639 data = {
640 "address" : address,
641 }
642
643 # Add auth token to the update url.
644 url = "%s?%s" % (self.url, self.token)
645
646 # Send update to the server.
647 response = self.send_request(url, data=data)
648
a204b107
SS
649 # Get the full response message.
650 output = response.read()
651
652 # Handle success messages.
aa21a4c6
SS
653 if output.startswith("Updated") or "has not changed" in output:
654 return
655
656 # Handle error codes.
657 if output == "ERROR: Unable to locate this record":
658 raise DDNSAuthenticationError
659 elif "is an invalid IP address" in output:
660 raise DDNSRequestError(_("Invalid IP address has been sent."))
661
3b524cf2
SS
662 # If we got here, some other update error happened.
663 raise DDNSUpdateError
664
aa21a4c6 665
a08c1b72 666class DDNSProviderLightningWireLabs(DDNSProvider):
6a11646e 667 handle = "dns.lightningwirelabs.com"
fb115fdc 668 name = "Lightning Wire Labs DNS Service"
6a11646e 669 website = "http://dns.lightningwirelabs.com/"
a08c1b72
SS
670
671 # Information about the format of the HTTPS request is to be found
672 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
b2b05ef3 673
a08c1b72
SS
674 url = "https://dns.lightningwirelabs.com/update"
675
5f402f36 676 def update(self):
a08c1b72
SS
677 data = {
678 "hostname" : self.hostname,
e3c70807
MT
679 "address6" : self.get_address("ipv6", "-"),
680 "address4" : self.get_address("ipv4", "-"),
a08c1b72
SS
681 }
682
a08c1b72
SS
683 # Check if a token has been set.
684 if self.token:
685 data["token"] = self.token
686
687 # Check for username and password.
688 elif self.username and self.password:
689 data.update({
690 "username" : self.username,
691 "password" : self.password,
692 })
693
694 # Raise an error if no auth details are given.
695 else:
696 raise DDNSConfigurationError
697
698 # Send update to the server.
cb455540 699 response = self.send_request(self.url, data=data)
a08c1b72
SS
700
701 # Handle success messages.
702 if response.code == 200:
703 return
704
a08c1b72
SS
705 # If we got here, some other update error happened.
706 raise DDNSUpdateError
707
708
78c9780b 709class DDNSProviderNamecheap(DDNSResponseParserXML, DDNSProvider):
6a11646e
MT
710 handle = "namecheap.com"
711 name = "Namecheap"
712 website = "http://namecheap.com"
713 protocols = ("ipv4",)
d1cd57eb
SS
714
715 # Information about the format of the HTTP request is to be found
716 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
717 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
718
719 url = "https://dynamicdns.park-your-domain.com/update"
720
d1cd57eb
SS
721 def update(self):
722 # Namecheap requires the hostname splitted into a host and domain part.
723 host, domain = self.hostname.split(".", 1)
724
725 data = {
726 "ip" : self.get_address("ipv4"),
727 "password" : self.password,
728 "host" : host,
729 "domain" : domain
730 }
731
732 # Send update to the server.
733 response = self.send_request(self.url, data=data)
734
735 # Get the full response message.
736 output = response.read()
737
738 # Handle success messages.
78c9780b 739 if self.get_xml_tag_value(output, "IP") == self.get_address("ipv4"):
d1cd57eb
SS
740 return
741
742 # Handle error codes.
78c9780b 743 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
d1cd57eb
SS
744
745 if errorcode == "304156":
746 raise DDNSAuthenticationError
747 elif errorcode == "316153":
748 raise DDNSRequestError(_("Domain not found."))
749 elif errorcode == "316154":
750 raise DDNSRequestError(_("Domain not active."))
751 elif errorcode in ("380098", "380099"):
752 raise DDNSInternalServerError
753
754 # If we got here, some other update error happened.
755 raise DDNSUpdateError
756
757
5d4bec40
MT
758class DDNSProviderNOIP(DDNSProtocolDynDNS2, DDNSProvider):
759 handle = "no-ip.com"
760 name = "No-IP"
761 website = "http://www.no-ip.com/"
762 protocols = ("ipv4",)
f22ab085
MT
763
764 # Information about the format of the HTTP request is to be found
765 # here: http://www.no-ip.com/integrate/request and
766 # here: http://www.no-ip.com/integrate/response
767
88f39629 768 url = "http://dynupdate.no-ip.com/nic/update"
2de06f59 769
88f39629 770 def _prepare_request_data(self):
2de06f59
MT
771 data = {
772 "hostname" : self.hostname,
f22ab085
MT
773 "address" : self.get_address("ipv4"),
774 }
775
88f39629 776 return data
f22ab085
MT
777
778
31c95e4b
SS
779class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2, DDNSProvider):
780 handle = "nsupdate.info"
781 name = "nsupdate.info"
782 website = "http://www.nsupdate.info/"
783 protocols = ("ipv6", "ipv4",)
784
785 # Information about the format of the HTTP request can be found
786 # after login on the provider user intrface and here:
787 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
788
789 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
790 # and for the password a so called secret.
791 @property
792 def username(self):
793 return self.get("hostname")
794
795 @property
796 def password(self):
797 return self.get("secret")
798
799 @property
800 def proto(self):
801 return self.get("proto")
802
803 @property
804 def url(self):
805 # The update URL is different by the used protocol.
806 if self.proto == "ipv4":
807 return "https://ipv4.nsupdate.info/nic/update"
808 elif self.proto == "ipv6":
809 return "https://ipv6.nsupdate.info/nic/update"
810 else:
811 raise DDNSUpdateError(_("Invalid protocol has been given"))
812
813 def _prepare_request_data(self):
814 data = {
815 "myip" : self.get_address(self.proto),
816 }
817
818 return data
819
820
90663439
SS
821class DDNSProviderOpenDNS(DDNSProtocolDynDNS2, DDNSProvider):
822 handle = "opendns.com"
823 name = "OpenDNS"
824 website = "http://www.opendns.com"
825
826 # Detailed information about the update request and possible
827 # response codes can be obtained from here:
828 # https://support.opendns.com/entries/23891440
829
830 url = "https://updates.opendns.com/nic/update"
831
832 @property
833 def proto(self):
834 return self.get("proto")
835
836 def _prepare_request_data(self):
837 data = {
838 "hostname" : self.hostname,
839 "myip" : self.get_address(self.proto)
840 }
841
842 return data
843
844
5d4bec40
MT
845class DDNSProviderOVH(DDNSProtocolDynDNS2, DDNSProvider):
846 handle = "ovh.com"
847 name = "OVH"
848 website = "http://www.ovh.com/"
849 protocols = ("ipv4",)
a508bda6
SS
850
851 # OVH only provides very limited information about how to
852 # update a DynDNS host. They only provide the update url
853 # on the their german subpage.
854 #
855 # http://hilfe.ovh.de/DomainDynHost
856
857 url = "https://www.ovh.com/nic/update"
858
859 def _prepare_request_data(self):
5d4bec40 860 data = DDNSProtocolDynDNS2._prepare_request_data(self)
54d3efc8
MT
861 data.update({
862 "system" : "dyndns",
863 })
864
865 return data
a508bda6
SS
866
867
ef33455e 868class DDNSProviderRegfish(DDNSProvider):
6a11646e
MT
869 handle = "regfish.com"
870 name = "Regfish GmbH"
871 website = "http://www.regfish.com/"
ef33455e
SS
872
873 # A full documentation to the providers api can be found here
874 # but is only available in german.
875 # https://www.regfish.de/domains/dyndns/dokumentation
876
877 url = "https://dyndns.regfish.de/"
878
879 def update(self):
880 data = {
881 "fqdn" : self.hostname,
882 }
883
884 # Check if we update an IPv6 address.
885 address6 = self.get_address("ipv6")
886 if address6:
887 data["ipv6"] = address6
888
889 # Check if we update an IPv4 address.
890 address4 = self.get_address("ipv4")
891 if address4:
892 data["ipv4"] = address4
893
894 # Raise an error if none address is given.
895 if not data.has_key("ipv6") and not data.has_key("ipv4"):
896 raise DDNSConfigurationError
897
898 # Check if a token has been set.
899 if self.token:
900 data["token"] = self.token
901
902 # Raise an error if no token and no useranem and password
903 # are given.
904 elif not self.username and not self.password:
905 raise DDNSConfigurationError(_("No Auth details specified."))
906
907 # HTTP Basic Auth is only allowed if no token is used.
908 if self.token:
909 # Send update to the server.
910 response = self.send_request(self.url, data=data)
911 else:
912 # Send update to the server.
913 response = self.send_request(self.url, username=self.username, password=self.password,
914 data=data)
915
916 # Get the full response message.
917 output = response.read()
918
919 # Handle success messages.
920 if "100" in output or "101" in output:
921 return
922
923 # Handle error codes.
924 if "401" or "402" in output:
925 raise DDNSAuthenticationError
926 elif "408" in output:
927 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
928 elif "409" in output:
929 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
930 elif "412" in output:
931 raise DDNSRequestError(_("No valid FQDN was given."))
932 elif "414" in output:
933 raise DDNSInternalServerError
934
935 # If we got here, some other update error happened.
936 raise DDNSUpdateError
937
938
5d4bec40 939class DDNSProviderSelfhost(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
940 handle = "selfhost.de"
941 name = "Selfhost.de"
942 website = "http://www.selfhost.de/"
5d4bec40 943 protocols = ("ipv4",)
f22ab085 944
04db1862 945 url = "https://carol.selfhost.de/nic/update"
f22ab085 946
04db1862 947 def _prepare_request_data(self):
5b153a22 948 data = DDNSProtocolDynDNS2._prepare_request_data(self)
04db1862
MT
949 data.update({
950 "hostname" : "1",
951 })
f22ab085 952
04db1862 953 return data
b09b1545
SS
954
955
5d4bec40
MT
956class DDNSProviderSPDNS(DDNSProtocolDynDNS2, DDNSProvider):
957 handle = "spdns.org"
958 name = "SPDNS"
959 website = "http://spdns.org/"
960 protocols = ("ipv4",)
b09b1545
SS
961
962 # Detailed information about request and response codes are provided
963 # by the vendor. They are using almost the same mechanism and status
964 # codes as dyndns.org so we can inherit all those stuff.
965 #
966 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
967 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
968
969 url = "https://update.spdns.de/nic/update"
4ec90b93
MT
970
971
5d4bec40
MT
972class DDNSProviderStrato(DDNSProtocolDynDNS2, DDNSProvider):
973 handle = "strato.com"
974 name = "Strato AG"
975 website = "http:/www.strato.com/"
976 protocols = ("ipv4",)
7488825c
SS
977
978 # Information about the request and response can be obtained here:
979 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
980
981 url = "https://dyndns.strato.com/nic/update"
982
983
5d4bec40
MT
984class DDNSProviderTwoDNS(DDNSProtocolDynDNS2, DDNSProvider):
985 handle = "twodns.de"
986 name = "TwoDNS"
987 website = "http://www.twodns.de"
988 protocols = ("ipv4",)
a6183090
SS
989
990 # Detailed information about the request can be found here
991 # http://twodns.de/en/faqs
992 # http://twodns.de/en/api
993
994 url = "https://update.twodns.de/update"
995
996 def _prepare_request_data(self):
997 data = {
998 "ip" : self.get_address("ipv4"),
999 "hostname" : self.hostname
1000 }
1001
1002 return data
1003
1004
5d4bec40
MT
1005class DDNSProviderUdmedia(DDNSProtocolDynDNS2, DDNSProvider):
1006 handle = "udmedia.de"
1007 name = "Udmedia GmbH"
1008 website = "http://www.udmedia.de"
1009 protocols = ("ipv4",)
03bdd188
SS
1010
1011 # Information about the request can be found here
1012 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1013
1014 url = "https://www.udmedia.de/nic/update"
1015
1016
5d4bec40 1017class DDNSProviderVariomedia(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
1018 handle = "variomedia.de"
1019 name = "Variomedia"
1020 website = "http://www.variomedia.de/"
1021 protocols = ("ipv6", "ipv4",)
c8c7ca8f
SS
1022
1023 # Detailed information about the request can be found here
1024 # https://dyndns.variomedia.de/
1025
1026 url = "https://dyndns.variomedia.de/nic/update"
1027
1028 @property
1029 def proto(self):
1030 return self.get("proto")
1031
1032 def _prepare_request_data(self):
1033 data = {
1034 "hostname" : self.hostname,
1035 "myip" : self.get_address(self.proto)
1036 }
54d3efc8
MT
1037
1038 return data
98fbe467
SS
1039
1040
5d4bec40
MT
1041class DDNSProviderZoneedit(DDNSProtocolDynDNS2, DDNSProvider):
1042 handle = "zoneedit.com"
1043 name = "Zoneedit"
1044 website = "http://www.zoneedit.com"
1045 protocols = ("ipv4",)
98fbe467
SS
1046
1047 # Detailed information about the request and the response codes can be
1048 # obtained here:
1049 # http://www.zoneedit.com/doc/api/other.html
1050 # http://www.zoneedit.com/faq.html
1051
1052 url = "https://dynamic.zoneedit.com/auth/dynamic.html"
1053
1054 @property
1055 def proto(self):
1056 return self.get("proto")
1057
1058 def update(self):
1059 data = {
1060 "dnsto" : self.get_address(self.proto),
1061 "host" : self.hostname
1062 }
1063
1064 # Send update to the server.
1065 response = self.send_request(self.url, username=self.username, password=self.password,
1066 data=data)
1067
1068 # Get the full response message.
1069 output = response.read()
1070
1071 # Handle success messages.
1072 if output.startswith("<SUCCESS"):
1073 return
1074
1075 # Handle error codes.
1076 if output.startswith("invalid login"):
1077 raise DDNSAuthenticationError
1078 elif output.startswith("<ERROR CODE=\"704\""):
1079 raise DDNSRequestError(_("No valid FQDN was given."))
1080 elif output.startswith("<ERROR CODE=\"702\""):
1081 raise DDNSInternalServerError
1082
1083 # If we got here, some other update error happened.
1084 raise DDNSUpdateError