]> git.ipfire.org Git - ddns.git/blame - src/ddns/providers.py
Merge remote-tracking branch 'stevee/ddns-fixes'
[ddns.git] / src / ddns / providers.py
CommitLineData
f22ab085 1#!/usr/bin/python
3fdcb9d1
MT
2###############################################################################
3# #
4# ddns - A dynamic DNS client for IPFire #
5# Copyright (C) 2012 IPFire development team #
6# #
7# This program is free software: you can redistribute it and/or modify #
8# it under the terms of the GNU General Public License as published by #
9# the Free Software Foundation, either version 3 of the License, or #
10# (at your option) any later version. #
11# #
12# This program is distributed in the hope that it will be useful, #
13# but WITHOUT ANY WARRANTY; without even the implied warranty of #
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15# GNU General Public License for more details. #
16# #
17# You should have received a copy of the GNU General Public License #
18# along with this program. If not, see <http://www.gnu.org/licenses/>. #
19# #
20###############################################################################
f22ab085 21
7399fc5b 22import logging
a892c594 23import subprocess
3b16fdb1 24import urllib2
d1cd57eb 25import xml.dom.minidom
7399fc5b
MT
26
27from i18n import _
28
f22ab085
MT
29# Import all possible exception types.
30from .errors import *
31
7399fc5b
MT
32logger = logging.getLogger("ddns.providers")
33logger.propagate = 1
34
adfe6272
MT
35_providers = {}
36
37def get():
38 """
39 Returns a dict with all automatically registered providers.
40 """
41 return _providers.copy()
42
f22ab085 43class DDNSProvider(object):
6a11646e
MT
44 # A short string that uniquely identifies
45 # this provider.
46 handle = None
f22ab085 47
6a11646e
MT
48 # The full name of the provider.
49 name = None
f22ab085 50
6a11646e
MT
51 # A weburl to the homepage of the provider.
52 # (Where to register a new account?)
53 website = None
f22ab085 54
6a11646e
MT
55 # A list of supported protocols.
56 protocols = ("ipv6", "ipv4")
f22ab085
MT
57
58 DEFAULT_SETTINGS = {}
59
adfe6272
MT
60 # Automatically register all providers.
61 class __metaclass__(type):
62 def __init__(provider, name, bases, dict):
63 type.__init__(provider, name, bases, dict)
64
65 # The main class from which is inherited is not registered
66 # as a provider.
67 if name == "DDNSProvider":
68 return
69
70 if not all((provider.handle, provider.name, provider.website)):
71 raise DDNSError(_("Provider is not properly configured"))
72
73 assert not _providers.has_key(provider.handle), \
74 "Provider '%s' has already been registered" % provider.handle
75
76 _providers[provider.handle] = provider
77
f22ab085
MT
78 def __init__(self, core, **settings):
79 self.core = core
80
81 # Copy a set of default settings and
82 # update them by those from the configuration file.
83 self.settings = self.DEFAULT_SETTINGS.copy()
84 self.settings.update(settings)
85
86 def __repr__(self):
87 return "<DDNS Provider %s (%s)>" % (self.name, self.handle)
88
89 def __cmp__(self, other):
90 return cmp(self.hostname, other.hostname)
91
f22ab085
MT
92 def get(self, key, default=None):
93 """
94 Get a setting from the settings dictionary.
95 """
96 return self.settings.get(key, default)
97
98 @property
99 def hostname(self):
100 """
101 Fast access to the hostname.
102 """
103 return self.get("hostname")
104
105 @property
106 def username(self):
107 """
108 Fast access to the username.
109 """
110 return self.get("username")
111
112 @property
113 def password(self):
114 """
115 Fast access to the password.
116 """
117 return self.get("password")
118
46687828
SS
119 @property
120 def token(self):
121 """
122 Fast access to the token.
123 """
124 return self.get("token")
125
9da3e685
MT
126 def __call__(self, force=False):
127 if force:
c3888f15 128 logger.debug(_("Updating %s forced") % self.hostname)
9da3e685 129
7399fc5b 130 # Check if we actually need to update this host.
9da3e685 131 elif self.is_uptodate(self.protocols):
7e1cc573 132 logger.debug(_("The dynamic host %(hostname)s (%(provider)s) is already up to date") % \
12b3818b 133 { "hostname" : self.hostname, "provider" : self.name })
7399fc5b
MT
134 return
135
136 # Execute the update.
5f402f36
MT
137 self.update()
138
12b3818b
MT
139 logger.info(_("Dynamic DNS update for %(hostname)s (%(provider)s) successful") % \
140 { "hostname" : self.hostname, "provider" : self.name })
141
5f402f36 142 def update(self):
f22ab085
MT
143 raise NotImplementedError
144
7399fc5b
MT
145 def is_uptodate(self, protos):
146 """
147 Returns True if this host is already up to date
148 and does not need to change the IP address on the
149 name server.
150 """
151 for proto in protos:
152 addresses = self.core.system.resolve(self.hostname, proto)
153
154 current_address = self.get_address(proto)
155
38d81db4
MT
156 # If no addresses for the given protocol exist, we
157 # are fine...
158 if current_address is None and not addresses:
159 continue
160
7399fc5b
MT
161 if not current_address in addresses:
162 return False
163
164 return True
165
f22ab085
MT
166 def send_request(self, *args, **kwargs):
167 """
168 Proxy connection to the send request
169 method.
170 """
171 return self.core.system.send_request(*args, **kwargs)
172
e3c70807 173 def get_address(self, proto, default=None):
f22ab085
MT
174 """
175 Proxy method to get the current IP address.
176 """
e3c70807 177 return self.core.system.get_address(proto) or default
f22ab085
MT
178
179
5d4bec40
MT
180class DDNSProtocolDynDNS2(object):
181 """
182 This is an abstract class that implements the DynDNS updater
183 protocol version 2. As this is a popular way to update dynamic
184 DNS records, this class is supposed make the provider classes
185 shorter and simpler.
186 """
187
188 # Information about the format of the request is to be found
486c1b9d 189 # http://dyn.com/support/developers/api/perform-update/
5d4bec40
MT
190 # http://dyn.com/support/developers/api/return-codes/
191
192 def _prepare_request_data(self):
193 data = {
194 "hostname" : self.hostname,
195 "myip" : self.get_address("ipv4"),
196 }
197
198 return data
199
200 def update(self):
201 data = self._prepare_request_data()
202
203 # Send update to the server.
204 response = self.send_request(self.url, data=data,
205 username=self.username, password=self.password)
206
207 # Get the full response message.
208 output = response.read()
209
210 # Handle success messages.
211 if output.startswith("good") or output.startswith("nochg"):
212 return
213
214 # Handle error codes.
215 if output == "badauth":
216 raise DDNSAuthenticationError
af97e369 217 elif output == "abuse":
5d4bec40
MT
218 raise DDNSAbuseError
219 elif output == "notfqdn":
220 raise DDNSRequestError(_("No valid FQDN was given."))
221 elif output == "nohost":
222 raise DDNSRequestError(_("Specified host does not exist."))
223 elif output == "911":
224 raise DDNSInternalServerError
225 elif output == "dnserr":
226 raise DDNSInternalServerError(_("DNS error encountered."))
6ddfd5c7
SS
227 elif output == "badagent":
228 raise DDNSBlockedError
5d4bec40
MT
229
230 # If we got here, some other update error happened.
231 raise DDNSUpdateError(_("Server response: %s") % output)
232
233
78c9780b
SS
234class DDNSResponseParserXML(object):
235 """
236 This class provides a parser for XML responses which
237 will be sent by various providers. This class uses the python
238 shipped XML minidom module to walk through the XML tree and return
239 a requested element.
240 """
241
242 def get_xml_tag_value(self, document, content):
243 # Send input to the parser.
244 xmldoc = xml.dom.minidom.parseString(document)
245
246 # Get XML elements by the given content.
247 element = xmldoc.getElementsByTagName(content)
248
249 # If no element has been found, we directly can return None.
250 if not element:
251 return None
252
253 # Only get the first child from an element, even there are more than one.
254 firstchild = element[0].firstChild
255
256 # Get the value of the child.
257 value = firstchild.nodeValue
258
259 # Return the value.
260 return value
261
262
3b16fdb1 263class DDNSProviderAllInkl(DDNSProvider):
6a11646e
MT
264 handle = "all-inkl.com"
265 name = "All-inkl.com"
266 website = "http://all-inkl.com/"
267 protocols = ("ipv4",)
3b16fdb1
SS
268
269 # There are only information provided by the vendor how to
270 # perform an update on a FRITZ Box. Grab requried informations
271 # from the net.
272 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
273
274 url = "http://dyndns.kasserver.com"
275
276 def update(self):
3b16fdb1
SS
277 # There is no additional data required so we directly can
278 # send our request.
536e87d1 279 response = self.send_request(self.url, username=self.username, password=self.password)
3b16fdb1
SS
280
281 # Get the full response message.
282 output = response.read()
283
284 # Handle success messages.
285 if output.startswith("good") or output.startswith("nochg"):
286 return
287
288 # If we got here, some other update error happened.
289 raise DDNSUpdateError
290
291
a892c594
MT
292class DDNSProviderBindNsupdate(DDNSProvider):
293 handle = "nsupdate"
294 name = "BIND nsupdate utility"
295 website = "http://en.wikipedia.org/wiki/Nsupdate"
296
297 DEFAULT_TTL = 60
298
299 def update(self):
300 scriptlet = self.__make_scriptlet()
301
302 # -v enables TCP hence we transfer keys and other data that may
303 # exceed the size of one packet.
304 # -t sets the timeout
305 command = ["nsupdate", "-v", "-t", "60"]
306
307 p = subprocess.Popen(command, shell=True,
308 stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
309 )
310 stdout, stderr = p.communicate(scriptlet)
311
312 if p.returncode == 0:
313 return
314
315 raise DDNSError("nsupdate terminated with error code: %s\n %s" % (p.returncode, stderr))
316
317 def __make_scriptlet(self):
318 scriptlet = []
319
320 # Set a different server the update is sent to.
321 server = self.get("server", None)
322 if server:
323 scriptlet.append("server %s" % server)
324
97998aac
MW
325 # Set the DNS zone the host should be added to.
326 zone = self.get("zone", None)
327 if zone:
328 scriptlet.append("zone %s" % zone)
329
a892c594
MT
330 key = self.get("key", None)
331 if key:
332 secret = self.get("secret")
333
334 scriptlet.append("key %s %s" % (key, secret))
335
336 ttl = self.get("ttl", self.DEFAULT_TTL)
337
338 # Perform an update for each supported protocol.
339 for rrtype, proto in (("AAAA", "ipv6"), ("A", "ipv4")):
340 address = self.get_address(proto)
341 if not address:
342 continue
343
344 scriptlet.append("update delete %s. %s" % (self.hostname, rrtype))
345 scriptlet.append("update add %s. %s %s %s" % \
346 (self.hostname, ttl, rrtype, address))
347
348 # Send the actions to the server.
349 scriptlet.append("send")
350 scriptlet.append("quit")
351
352 logger.debug(_("Scriptlet:"))
353 for line in scriptlet:
354 # Masquerade the line with the secret key.
355 if line.startswith("key"):
356 line = "key **** ****"
357
358 logger.debug(" %s" % line)
359
360 return "\n".join(scriptlet)
361
362
f3cf1f70 363class DDNSProviderDHS(DDNSProvider):
6a11646e
MT
364 handle = "dhs.org"
365 name = "DHS International"
366 website = "http://dhs.org/"
367 protocols = ("ipv4",)
f3cf1f70
SS
368
369 # No information about the used update api provided on webpage,
370 # grabed from source code of ez-ipudate.
b2b05ef3 371
f3cf1f70
SS
372 url = "http://members.dhs.org/nic/hosts"
373
5f402f36 374 def update(self):
f3cf1f70
SS
375 data = {
376 "domain" : self.hostname,
377 "ip" : self.get_address("ipv4"),
378 "hostcmd" : "edit",
379 "hostcmdstage" : "2",
380 "type" : "4",
381 }
382
383 # Send update to the server.
175c9b80 384 response = self.send_request(self.url, username=self.username, password=self.password,
f3cf1f70
SS
385 data=data)
386
387 # Handle success messages.
388 if response.code == 200:
389 return
390
f3cf1f70
SS
391 # If we got here, some other update error happened.
392 raise DDNSUpdateError
393
394
39301272 395class DDNSProviderDNSpark(DDNSProvider):
6a11646e
MT
396 handle = "dnspark.com"
397 name = "DNS Park"
398 website = "http://dnspark.com/"
399 protocols = ("ipv4",)
39301272
SS
400
401 # Informations to the used api can be found here:
402 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
b2b05ef3 403
39301272
SS
404 url = "https://control.dnspark.com/api/dynamic/update.php"
405
5f402f36 406 def update(self):
39301272
SS
407 data = {
408 "domain" : self.hostname,
409 "ip" : self.get_address("ipv4"),
410 }
411
412 # Send update to the server.
175c9b80 413 response = self.send_request(self.url, username=self.username, password=self.password,
39301272
SS
414 data=data)
415
416 # Get the full response message.
417 output = response.read()
418
419 # Handle success messages.
420 if output.startswith("ok") or output.startswith("nochange"):
421 return
422
423 # Handle error codes.
424 if output == "unauth":
425 raise DDNSAuthenticationError
426 elif output == "abuse":
427 raise DDNSAbuseError
428 elif output == "blocked":
429 raise DDNSBlockedError
430 elif output == "nofqdn":
431 raise DDNSRequestError(_("No valid FQDN was given."))
432 elif output == "nohost":
433 raise DDNSRequestError(_("Invalid hostname specified."))
434 elif output == "notdyn":
435 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
436 elif output == "invalid":
437 raise DDNSRequestError(_("Invalid IP address has been sent."))
438
439 # If we got here, some other update error happened.
440 raise DDNSUpdateError
441
43b2cd59
SS
442
443class DDNSProviderDtDNS(DDNSProvider):
6a11646e
MT
444 handle = "dtdns.com"
445 name = "DtDNS"
446 website = "http://dtdns.com/"
447 protocols = ("ipv4",)
43b2cd59
SS
448
449 # Information about the format of the HTTPS request is to be found
450 # http://www.dtdns.com/dtsite/updatespec
b2b05ef3 451
43b2cd59
SS
452 url = "https://www.dtdns.com/api/autodns.cfm"
453
43b2cd59
SS
454 def update(self):
455 data = {
456 "ip" : self.get_address("ipv4"),
457 "id" : self.hostname,
458 "pw" : self.password
459 }
460
461 # Send update to the server.
462 response = self.send_request(self.url, data=data)
463
464 # Get the full response message.
465 output = response.read()
466
467 # Remove all leading and trailing whitespace.
468 output = output.strip()
469
470 # Handle success messages.
471 if "now points to" in output:
472 return
473
474 # Handle error codes.
475 if output == "No hostname to update was supplied.":
476 raise DDNSRequestError(_("No hostname specified."))
477
478 elif output == "The hostname you supplied is not valid.":
479 raise DDNSRequestError(_("Invalid hostname specified."))
480
481 elif output == "The password you supplied is not valid.":
482 raise DDNSAuthenticationError
483
484 elif output == "Administration has disabled this account.":
485 raise DDNSRequestError(_("Account has been disabled."))
486
487 elif output == "Illegal character in IP.":
488 raise DDNSRequestError(_("Invalid IP address has been sent."))
489
490 elif output == "Too many failed requests.":
491 raise DDNSRequestError(_("Too many failed requests."))
492
493 # If we got here, some other update error happened.
494 raise DDNSUpdateError
495
496
5d4bec40 497class DDNSProviderDynDNS(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
498 handle = "dyndns.org"
499 name = "Dyn"
500 website = "http://dyn.com/dns/"
501 protocols = ("ipv4",)
bfed6701
SS
502
503 # Information about the format of the request is to be found
504 # http://http://dyn.com/support/developers/api/perform-update/
505 # http://dyn.com/support/developers/api/return-codes/
b2b05ef3 506
bfed6701
SS
507 url = "https://members.dyndns.org/nic/update"
508
bfed6701 509
5d4bec40 510class DDNSProviderDynU(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
511 handle = "dynu.com"
512 name = "Dynu"
513 website = "http://dynu.com/"
514 protocols = ("ipv6", "ipv4",)
3a8407fa
SS
515
516 # Detailed information about the request and response codes
517 # are available on the providers webpage.
518 # http://dynu.com/Default.aspx?page=dnsapi
519
520 url = "https://api.dynu.com/nic/update"
521
522 def _prepare_request_data(self):
5b153a22 523 data = DDNSProtocolDynDNS2._prepare_request_data(self)
54d3efc8
MT
524
525 # This one supports IPv6
cdc078dc
SS
526 myipv6 = self.get_address("ipv6")
527
528 # Add update information if we have an IPv6 address.
529 if myipv6:
530 data["myipv6"] = myipv6
54d3efc8
MT
531
532 return data
3a8407fa
SS
533
534
5d4bec40
MT
535class DDNSProviderEasyDNS(DDNSProtocolDynDNS2, DDNSProvider):
536 handle = "easydns.com"
537 name = "EasyDNS"
538 website = "http://www.easydns.com/"
539 protocols = ("ipv4",)
ee071271
SS
540
541 # There is only some basic documentation provided by the vendor,
542 # also searching the web gain very poor results.
543 # http://mediawiki.easydns.com/index.php/Dynamic_DNS
544
545 url = "http://api.cp.easydns.com/dyn/tomato.php"
546
547
90fe8843
CE
548class DDNSProviderDomopoli(DDNSProtocolDynDNS2, DDNSProvider):
549 handle = "domopoli.de"
550 name = "domopoli.de"
551 website = "http://domopoli.de/"
552 protocols = ("ipv4",)
553
554 # https://www.domopoli.de/?page=howto#DynDns_start
555
556 url = "http://dyndns.domopoli.de/nic/update"
557
558
35216523
SS
559class DDNSProviderEnomCom(DDNSResponseParserXML, DDNSProvider):
560 handle = "enom.com"
561 name = "eNom Inc."
562 website = "http://www.enom.com/"
563
564 # There are very detailed information about how to send an update request and
565 # the respone codes.
566 # http://www.enom.com/APICommandCatalog/
567
568 url = "https://dynamic.name-services.com/interface.asp"
569
570 def update(self):
571 data = {
572 "command" : "setdnshost",
573 "responsetype" : "xml",
574 "address" : self.get_address("ipv4"),
575 "domainpassword" : self.password,
576 "zone" : self.hostname
577 }
578
579 # Send update to the server.
580 response = self.send_request(self.url, data=data)
581
582 # Get the full response message.
583 output = response.read()
584
585 # Handle success messages.
586 if self.get_xml_tag_value(output, "ErrCount") == "0":
587 return
588
589 # Handle error codes.
590 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
591
592 if errorcode == "304155":
593 raise DDNSAuthenticationError
594 elif errorcode == "304153":
595 raise DDNSRequestError(_("Domain not found."))
596
597 # If we got here, some other update error happened.
598 raise DDNSUpdateError
599
600
ab4e352e
SS
601class DDNSProviderEntryDNS(DDNSProvider):
602 handle = "entrydns.net"
603 name = "EntryDNS"
604 website = "http://entrydns.net/"
605 protocols = ("ipv4",)
606
607 # Some very tiny details about their so called "Simple API" can be found
608 # here: https://entrydns.net/help
609 url = "https://entrydns.net/records/modify"
610
611 def update(self):
612 data = {
613 "ip" : self.get_address("ipv4")
614 }
615
616 # Add auth token to the update url.
617 url = "%s/%s" % (self.url, self.token)
618
619 # Send update to the server.
620 try:
babc5e6d 621 response = self.send_request(url, data=data)
ab4e352e
SS
622
623 # Handle error codes
624 except urllib2.HTTPError, e:
625 if e.code == 404:
626 raise DDNSAuthenticationError
627
628 elif e.code == 422:
629 raise DDNSRequestError(_("An invalid IP address was submitted"))
630
631 raise
632
633 # Handle success messages.
634 if response.code == 200:
635 return
636
637 # If we got here, some other update error happened.
638 raise DDNSUpdateError
639
640
aa21a4c6 641class DDNSProviderFreeDNSAfraidOrg(DDNSProvider):
6a11646e
MT
642 handle = "freedns.afraid.org"
643 name = "freedns.afraid.org"
644 website = "http://freedns.afraid.org/"
aa21a4c6
SS
645
646 # No information about the request or response could be found on the vendor
647 # page. All used values have been collected by testing.
648 url = "https://freedns.afraid.org/dynamic/update.php"
649
650 @property
651 def proto(self):
652 return self.get("proto")
653
654 def update(self):
655 address = self.get_address(self.proto)
656
657 data = {
658 "address" : address,
659 }
660
661 # Add auth token to the update url.
662 url = "%s?%s" % (self.url, self.token)
663
664 # Send update to the server.
665 response = self.send_request(url, data=data)
666
a204b107
SS
667 # Get the full response message.
668 output = response.read()
669
670 # Handle success messages.
aa21a4c6
SS
671 if output.startswith("Updated") or "has not changed" in output:
672 return
673
674 # Handle error codes.
675 if output == "ERROR: Unable to locate this record":
676 raise DDNSAuthenticationError
677 elif "is an invalid IP address" in output:
678 raise DDNSRequestError(_("Invalid IP address has been sent."))
679
3b524cf2
SS
680 # If we got here, some other update error happened.
681 raise DDNSUpdateError
682
aa21a4c6 683
a08c1b72 684class DDNSProviderLightningWireLabs(DDNSProvider):
6a11646e 685 handle = "dns.lightningwirelabs.com"
fb115fdc 686 name = "Lightning Wire Labs DNS Service"
6a11646e 687 website = "http://dns.lightningwirelabs.com/"
a08c1b72
SS
688
689 # Information about the format of the HTTPS request is to be found
690 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
b2b05ef3 691
a08c1b72
SS
692 url = "https://dns.lightningwirelabs.com/update"
693
5f402f36 694 def update(self):
a08c1b72
SS
695 data = {
696 "hostname" : self.hostname,
e3c70807
MT
697 "address6" : self.get_address("ipv6", "-"),
698 "address4" : self.get_address("ipv4", "-"),
a08c1b72
SS
699 }
700
a08c1b72
SS
701 # Check if a token has been set.
702 if self.token:
703 data["token"] = self.token
704
705 # Check for username and password.
706 elif self.username and self.password:
707 data.update({
708 "username" : self.username,
709 "password" : self.password,
710 })
711
712 # Raise an error if no auth details are given.
713 else:
714 raise DDNSConfigurationError
715
716 # Send update to the server.
cb455540 717 response = self.send_request(self.url, data=data)
a08c1b72
SS
718
719 # Handle success messages.
720 if response.code == 200:
721 return
722
a08c1b72
SS
723 # If we got here, some other update error happened.
724 raise DDNSUpdateError
725
726
78c9780b 727class DDNSProviderNamecheap(DDNSResponseParserXML, DDNSProvider):
6a11646e
MT
728 handle = "namecheap.com"
729 name = "Namecheap"
730 website = "http://namecheap.com"
731 protocols = ("ipv4",)
d1cd57eb
SS
732
733 # Information about the format of the HTTP request is to be found
734 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
735 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
736
737 url = "https://dynamicdns.park-your-domain.com/update"
738
d1cd57eb
SS
739 def update(self):
740 # Namecheap requires the hostname splitted into a host and domain part.
741 host, domain = self.hostname.split(".", 1)
742
743 data = {
744 "ip" : self.get_address("ipv4"),
745 "password" : self.password,
746 "host" : host,
747 "domain" : domain
748 }
749
750 # Send update to the server.
751 response = self.send_request(self.url, data=data)
752
753 # Get the full response message.
754 output = response.read()
755
756 # Handle success messages.
78c9780b 757 if self.get_xml_tag_value(output, "IP") == self.get_address("ipv4"):
d1cd57eb
SS
758 return
759
760 # Handle error codes.
78c9780b 761 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
d1cd57eb
SS
762
763 if errorcode == "304156":
764 raise DDNSAuthenticationError
765 elif errorcode == "316153":
766 raise DDNSRequestError(_("Domain not found."))
767 elif errorcode == "316154":
768 raise DDNSRequestError(_("Domain not active."))
769 elif errorcode in ("380098", "380099"):
770 raise DDNSInternalServerError
771
772 # If we got here, some other update error happened.
773 raise DDNSUpdateError
774
775
5d4bec40
MT
776class DDNSProviderNOIP(DDNSProtocolDynDNS2, DDNSProvider):
777 handle = "no-ip.com"
778 name = "No-IP"
779 website = "http://www.no-ip.com/"
780 protocols = ("ipv4",)
f22ab085
MT
781
782 # Information about the format of the HTTP request is to be found
783 # here: http://www.no-ip.com/integrate/request and
784 # here: http://www.no-ip.com/integrate/response
785
88f39629 786 url = "http://dynupdate.no-ip.com/nic/update"
2de06f59 787
88f39629 788 def _prepare_request_data(self):
2de06f59
MT
789 data = {
790 "hostname" : self.hostname,
f22ab085
MT
791 "address" : self.get_address("ipv4"),
792 }
793
88f39629 794 return data
f22ab085
MT
795
796
31c95e4b
SS
797class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2, DDNSProvider):
798 handle = "nsupdate.info"
799 name = "nsupdate.info"
800 website = "http://www.nsupdate.info/"
801 protocols = ("ipv6", "ipv4",)
802
803 # Information about the format of the HTTP request can be found
804 # after login on the provider user intrface and here:
805 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
806
807 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
808 # and for the password a so called secret.
809 @property
810 def username(self):
811 return self.get("hostname")
812
813 @property
814 def password(self):
815 return self.get("secret")
816
817 @property
818 def proto(self):
819 return self.get("proto")
820
821 @property
822 def url(self):
823 # The update URL is different by the used protocol.
824 if self.proto == "ipv4":
825 return "https://ipv4.nsupdate.info/nic/update"
826 elif self.proto == "ipv6":
827 return "https://ipv6.nsupdate.info/nic/update"
828 else:
829 raise DDNSUpdateError(_("Invalid protocol has been given"))
830
831 def _prepare_request_data(self):
832 data = {
833 "myip" : self.get_address(self.proto),
834 }
835
836 return data
837
838
90663439
SS
839class DDNSProviderOpenDNS(DDNSProtocolDynDNS2, DDNSProvider):
840 handle = "opendns.com"
841 name = "OpenDNS"
842 website = "http://www.opendns.com"
843
844 # Detailed information about the update request and possible
845 # response codes can be obtained from here:
846 # https://support.opendns.com/entries/23891440
847
848 url = "https://updates.opendns.com/nic/update"
849
850 @property
851 def proto(self):
852 return self.get("proto")
853
854 def _prepare_request_data(self):
855 data = {
856 "hostname" : self.hostname,
857 "myip" : self.get_address(self.proto)
858 }
859
860 return data
861
862
5d4bec40
MT
863class DDNSProviderOVH(DDNSProtocolDynDNS2, DDNSProvider):
864 handle = "ovh.com"
865 name = "OVH"
866 website = "http://www.ovh.com/"
867 protocols = ("ipv4",)
a508bda6
SS
868
869 # OVH only provides very limited information about how to
870 # update a DynDNS host. They only provide the update url
871 # on the their german subpage.
872 #
873 # http://hilfe.ovh.de/DomainDynHost
874
875 url = "https://www.ovh.com/nic/update"
876
877 def _prepare_request_data(self):
5d4bec40 878 data = DDNSProtocolDynDNS2._prepare_request_data(self)
54d3efc8
MT
879 data.update({
880 "system" : "dyndns",
881 })
882
883 return data
a508bda6
SS
884
885
ef33455e 886class DDNSProviderRegfish(DDNSProvider):
6a11646e
MT
887 handle = "regfish.com"
888 name = "Regfish GmbH"
889 website = "http://www.regfish.com/"
ef33455e
SS
890
891 # A full documentation to the providers api can be found here
892 # but is only available in german.
893 # https://www.regfish.de/domains/dyndns/dokumentation
894
895 url = "https://dyndns.regfish.de/"
896
897 def update(self):
898 data = {
899 "fqdn" : self.hostname,
900 }
901
902 # Check if we update an IPv6 address.
903 address6 = self.get_address("ipv6")
904 if address6:
905 data["ipv6"] = address6
906
907 # Check if we update an IPv4 address.
908 address4 = self.get_address("ipv4")
909 if address4:
910 data["ipv4"] = address4
911
912 # Raise an error if none address is given.
913 if not data.has_key("ipv6") and not data.has_key("ipv4"):
914 raise DDNSConfigurationError
915
916 # Check if a token has been set.
917 if self.token:
918 data["token"] = self.token
919
920 # Raise an error if no token and no useranem and password
921 # are given.
922 elif not self.username and not self.password:
923 raise DDNSConfigurationError(_("No Auth details specified."))
924
925 # HTTP Basic Auth is only allowed if no token is used.
926 if self.token:
927 # Send update to the server.
928 response = self.send_request(self.url, data=data)
929 else:
930 # Send update to the server.
931 response = self.send_request(self.url, username=self.username, password=self.password,
932 data=data)
933
934 # Get the full response message.
935 output = response.read()
936
937 # Handle success messages.
938 if "100" in output or "101" in output:
939 return
940
941 # Handle error codes.
942 if "401" or "402" in output:
943 raise DDNSAuthenticationError
944 elif "408" in output:
945 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
946 elif "409" in output:
947 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
948 elif "412" in output:
949 raise DDNSRequestError(_("No valid FQDN was given."))
950 elif "414" in output:
951 raise DDNSInternalServerError
952
953 # If we got here, some other update error happened.
954 raise DDNSUpdateError
955
956
5d4bec40 957class DDNSProviderSelfhost(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
958 handle = "selfhost.de"
959 name = "Selfhost.de"
960 website = "http://www.selfhost.de/"
5d4bec40 961 protocols = ("ipv4",)
f22ab085 962
04db1862 963 url = "https://carol.selfhost.de/nic/update"
f22ab085 964
04db1862 965 def _prepare_request_data(self):
5b153a22 966 data = DDNSProtocolDynDNS2._prepare_request_data(self)
04db1862
MT
967 data.update({
968 "hostname" : "1",
969 })
f22ab085 970
04db1862 971 return data
b09b1545
SS
972
973
5d4bec40
MT
974class DDNSProviderSPDNS(DDNSProtocolDynDNS2, DDNSProvider):
975 handle = "spdns.org"
976 name = "SPDNS"
977 website = "http://spdns.org/"
978 protocols = ("ipv4",)
b09b1545
SS
979
980 # Detailed information about request and response codes are provided
981 # by the vendor. They are using almost the same mechanism and status
982 # codes as dyndns.org so we can inherit all those stuff.
983 #
984 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
985 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
986
987 url = "https://update.spdns.de/nic/update"
4ec90b93
MT
988
989
5d4bec40
MT
990class DDNSProviderStrato(DDNSProtocolDynDNS2, DDNSProvider):
991 handle = "strato.com"
992 name = "Strato AG"
993 website = "http:/www.strato.com/"
994 protocols = ("ipv4",)
7488825c
SS
995
996 # Information about the request and response can be obtained here:
997 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
998
999 url = "https://dyndns.strato.com/nic/update"
1000
1001
5d4bec40
MT
1002class DDNSProviderTwoDNS(DDNSProtocolDynDNS2, DDNSProvider):
1003 handle = "twodns.de"
1004 name = "TwoDNS"
1005 website = "http://www.twodns.de"
1006 protocols = ("ipv4",)
a6183090
SS
1007
1008 # Detailed information about the request can be found here
1009 # http://twodns.de/en/faqs
1010 # http://twodns.de/en/api
1011
1012 url = "https://update.twodns.de/update"
1013
1014 def _prepare_request_data(self):
1015 data = {
1016 "ip" : self.get_address("ipv4"),
1017 "hostname" : self.hostname
1018 }
1019
1020 return data
1021
1022
5d4bec40
MT
1023class DDNSProviderUdmedia(DDNSProtocolDynDNS2, DDNSProvider):
1024 handle = "udmedia.de"
1025 name = "Udmedia GmbH"
1026 website = "http://www.udmedia.de"
1027 protocols = ("ipv4",)
03bdd188
SS
1028
1029 # Information about the request can be found here
1030 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1031
1032 url = "https://www.udmedia.de/nic/update"
1033
1034
5d4bec40 1035class DDNSProviderVariomedia(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
1036 handle = "variomedia.de"
1037 name = "Variomedia"
1038 website = "http://www.variomedia.de/"
1039 protocols = ("ipv6", "ipv4",)
c8c7ca8f
SS
1040
1041 # Detailed information about the request can be found here
1042 # https://dyndns.variomedia.de/
1043
1044 url = "https://dyndns.variomedia.de/nic/update"
1045
1046 @property
1047 def proto(self):
1048 return self.get("proto")
1049
1050 def _prepare_request_data(self):
1051 data = {
1052 "hostname" : self.hostname,
1053 "myip" : self.get_address(self.proto)
1054 }
54d3efc8
MT
1055
1056 return data
98fbe467
SS
1057
1058
5d4bec40
MT
1059class DDNSProviderZoneedit(DDNSProtocolDynDNS2, DDNSProvider):
1060 handle = "zoneedit.com"
1061 name = "Zoneedit"
1062 website = "http://www.zoneedit.com"
1063 protocols = ("ipv4",)
98fbe467
SS
1064
1065 # Detailed information about the request and the response codes can be
1066 # obtained here:
1067 # http://www.zoneedit.com/doc/api/other.html
1068 # http://www.zoneedit.com/faq.html
1069
1070 url = "https://dynamic.zoneedit.com/auth/dynamic.html"
1071
1072 @property
1073 def proto(self):
1074 return self.get("proto")
1075
1076 def update(self):
1077 data = {
1078 "dnsto" : self.get_address(self.proto),
1079 "host" : self.hostname
1080 }
1081
1082 # Send update to the server.
1083 response = self.send_request(self.url, username=self.username, password=self.password,
1084 data=data)
1085
1086 # Get the full response message.
1087 output = response.read()
1088
1089 # Handle success messages.
1090 if output.startswith("<SUCCESS"):
1091 return
1092
1093 # Handle error codes.
1094 if output.startswith("invalid login"):
1095 raise DDNSAuthenticationError
1096 elif output.startswith("<ERROR CODE=\"704\""):
1097 raise DDNSRequestError(_("No valid FQDN was given."))
1098 elif output.startswith("<ERROR CODE=\"702\""):
1099 raise DDNSInternalServerError
1100
1101 # If we got here, some other update error happened.
1102 raise DDNSUpdateError