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