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