]> git.ipfire.org Git - ddns.git/blob - src/ddns/providers.py
spdns.de: Add support for IPv6.
[ddns.git] / src / ddns / providers.py
1 #!/usr/bin/python
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 ###############################################################################
21
22 import logging
23 import subprocess
24 import urllib2
25 import xml.dom.minidom
26
27 from i18n import _
28
29 # Import all possible exception types.
30 from .errors import *
31
32 logger = logging.getLogger("ddns.providers")
33 logger.propagate = 1
34
35 _providers = {}
36
37 def get():
38 """
39 Returns a dict with all automatically registered providers.
40 """
41 return _providers.copy()
42
43 class DDNSProvider(object):
44 # A short string that uniquely identifies
45 # this provider.
46 handle = None
47
48 # The full name of the provider.
49 name = None
50
51 # A weburl to the homepage of the provider.
52 # (Where to register a new account?)
53 website = None
54
55 # A list of supported protocols.
56 protocols = ("ipv6", "ipv4")
57
58 DEFAULT_SETTINGS = {}
59
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
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
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
119 @property
120 def token(self):
121 """
122 Fast access to the token.
123 """
124 return self.get("token")
125
126 def __call__(self, force=False):
127 if force:
128 logger.debug(_("Updating %s forced") % self.hostname)
129
130 # Check if we actually need to update this host.
131 elif self.is_uptodate(self.protocols):
132 logger.debug(_("The dynamic host %(hostname)s (%(provider)s) is already up to date") % \
133 { "hostname" : self.hostname, "provider" : self.name })
134 return
135
136 # Execute the update.
137 self.update()
138
139 logger.info(_("Dynamic DNS update for %(hostname)s (%(provider)s) successful") % \
140 { "hostname" : self.hostname, "provider" : self.name })
141
142 def update(self):
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):
150 raise NotImplementedError
151
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
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
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
176 if not current_address in addresses:
177 return False
178
179 return True
180
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
188 def get_address(self, proto, default=None):
189 """
190 Proxy method to get the current IP address.
191 """
192 return self.core.system.get_address(proto) or default
193
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
206
207 class 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
216 # http://dyn.com/support/developers/api/perform-update/
217 # http://dyn.com/support/developers/api/return-codes/
218
219 def prepare_request_data(self, proto):
220 data = {
221 "hostname" : self.hostname,
222 "myip" : self.get_address(proto),
223 }
224
225 return data
226
227 def update_protocol(self, proto):
228 data = self.prepare_request_data(proto)
229
230 return self.send_request(data)
231
232 def send_request(self, data):
233 # Send update to the server.
234 response = DDNSProvider.send_request(self, self.url, data=data,
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
247 elif output == "abuse":
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."))
257 elif output == "badagent":
258 raise DDNSBlockedError
259
260 # If we got here, some other update error happened.
261 raise DDNSUpdateError(_("Server response: %s") % output)
262
263
264 class 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
293 class DDNSProviderAllInkl(DDNSProvider):
294 handle = "all-inkl.com"
295 name = "All-inkl.com"
296 website = "http://all-inkl.com/"
297 protocols = ("ipv4",)
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):
307 # There is no additional data required so we directly can
308 # send our request.
309 response = self.send_request(self.url, username=self.username, password=self.password)
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
322 class 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
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
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
393 class DDNSProviderDHS(DDNSProvider):
394 handle = "dhs.org"
395 name = "DHS International"
396 website = "http://dhs.org/"
397 protocols = ("ipv4",)
398
399 # No information about the used update api provided on webpage,
400 # grabed from source code of ez-ipudate.
401
402 url = "http://members.dhs.org/nic/hosts"
403
404 def update_protocol(self, proto):
405 data = {
406 "domain" : self.hostname,
407 "ip" : self.get_address(proto),
408 "hostcmd" : "edit",
409 "hostcmdstage" : "2",
410 "type" : "4",
411 }
412
413 # Send update to the server.
414 response = self.send_request(self.url, username=self.username, password=self.password,
415 data=data)
416
417 # Handle success messages.
418 if response.code == 200:
419 return
420
421 # If we got here, some other update error happened.
422 raise DDNSUpdateError
423
424
425 class DDNSProviderDNSpark(DDNSProvider):
426 handle = "dnspark.com"
427 name = "DNS Park"
428 website = "http://dnspark.com/"
429 protocols = ("ipv4",)
430
431 # Informations to the used api can be found here:
432 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
433
434 url = "https://control.dnspark.com/api/dynamic/update.php"
435
436 def update_protocol(self, proto):
437 data = {
438 "domain" : self.hostname,
439 "ip" : self.get_address(proto),
440 }
441
442 # Send update to the server.
443 response = self.send_request(self.url, username=self.username, password=self.password,
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
472
473 class DDNSProviderDtDNS(DDNSProvider):
474 handle = "dtdns.com"
475 name = "DtDNS"
476 website = "http://dtdns.com/"
477 protocols = ("ipv4",)
478
479 # Information about the format of the HTTPS request is to be found
480 # http://www.dtdns.com/dtsite/updatespec
481
482 url = "https://www.dtdns.com/api/autodns.cfm"
483
484 def update_protocol(self, proto):
485 data = {
486 "ip" : self.get_address(proto),
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
527 class DDNSProviderDynDNS(DDNSProtocolDynDNS2, DDNSProvider):
528 handle = "dyndns.org"
529 name = "Dyn"
530 website = "http://dyn.com/dns/"
531 protocols = ("ipv4",)
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/
536
537 url = "https://members.dyndns.org/nic/update"
538
539
540 class DDNSProviderDynU(DDNSProtocolDynDNS2, DDNSProvider):
541 handle = "dynu.com"
542 name = "Dynu"
543 website = "http://dynu.com/"
544 protocols = ("ipv6", "ipv4",)
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
552 # DynU sends the IPv6 and IPv4 address in one request
553
554 def update(self):
555 data = DDNSProtocolDynDNS2.prepare_request_data(self, "ipv4")
556
557 # This one supports IPv6
558 myipv6 = self.get_address("ipv6")
559
560 # Add update information if we have an IPv6 address.
561 if myipv6:
562 data["myipv6"] = myipv6
563
564 self._send_request(data)
565
566
567 class DDNSProviderEasyDNS(DDNSProtocolDynDNS2, DDNSProvider):
568 handle = "easydns.com"
569 name = "EasyDNS"
570 website = "http://www.easydns.com/"
571 protocols = ("ipv4",)
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
580 class 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
591 class 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
603 def update_protocol(self, proto):
604 data = {
605 "ip" : self.get_address(proto),
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
635 class DDNSProviderEnomCom(DDNSResponseParserXML, DDNSProvider):
636 handle = "enom.com"
637 name = "eNom Inc."
638 website = "http://www.enom.com/"
639 protocols = ("ipv4",)
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
647 def update_protocol(self, proto):
648 data = {
649 "command" : "setdnshost",
650 "responsetype" : "xml",
651 "address" : self.get_address(proto),
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
678 class 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
688 def update_protocol(self, proto):
689 data = {
690 "ip" : self.get_address(proto),
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:
698 response = self.send_request(url, data=data)
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
718 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider):
719 handle = "freedns.afraid.org"
720 name = "freedns.afraid.org"
721 website = "http://freedns.afraid.org/"
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
727 def update_protocol(self, proto):
728 data = {
729 "address" : self.get_address(proto),
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
738 # Get the full response message.
739 output = response.read()
740
741 # Handle success messages.
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
751 # If we got here, some other update error happened.
752 raise DDNSUpdateError
753
754
755 class DDNSProviderLightningWireLabs(DDNSProvider):
756 handle = "dns.lightningwirelabs.com"
757 name = "Lightning Wire Labs DNS Service"
758 website = "http://dns.lightningwirelabs.com/"
759
760 # Information about the format of the HTTPS request is to be found
761 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
762
763 url = "https://dns.lightningwirelabs.com/update"
764
765 def update(self):
766 data = {
767 "hostname" : self.hostname,
768 "address6" : self.get_address("ipv6", "-"),
769 "address4" : self.get_address("ipv4", "-"),
770 }
771
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.
788 response = self.send_request(self.url, data=data)
789
790 # Handle success messages.
791 if response.code == 200:
792 return
793
794 # If we got here, some other update error happened.
795 raise DDNSUpdateError
796
797
798 class DDNSProviderNamecheap(DDNSResponseParserXML, DDNSProvider):
799 handle = "namecheap.com"
800 name = "Namecheap"
801 website = "http://namecheap.com"
802 protocols = ("ipv4",)
803
804 # Information about the format of the HTTP request is to be found
805 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
806 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
807
808 url = "https://dynamicdns.park-your-domain.com/update"
809
810 def update_protocol(self, proto):
811 # Namecheap requires the hostname splitted into a host and domain part.
812 host, domain = self.hostname.split(".", 1)
813
814 data = {
815 "ip" : self.get_address(proto),
816 "password" : self.password,
817 "host" : host,
818 "domain" : domain
819 }
820
821 # Send update to the server.
822 response = self.send_request(self.url, data=data)
823
824 # Get the full response message.
825 output = response.read()
826
827 # Handle success messages.
828 if self.get_xml_tag_value(output, "IP") == address:
829 return
830
831 # Handle error codes.
832 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
833
834 if errorcode == "304156":
835 raise DDNSAuthenticationError
836 elif errorcode == "316153":
837 raise DDNSRequestError(_("Domain not found."))
838 elif errorcode == "316154":
839 raise DDNSRequestError(_("Domain not active."))
840 elif errorcode in ("380098", "380099"):
841 raise DDNSInternalServerError
842
843 # If we got here, some other update error happened.
844 raise DDNSUpdateError
845
846
847 class DDNSProviderNOIP(DDNSProtocolDynDNS2, DDNSProvider):
848 handle = "no-ip.com"
849 name = "No-IP"
850 website = "http://www.no-ip.com/"
851 protocols = ("ipv4",)
852
853 # Information about the format of the HTTP request is to be found
854 # here: http://www.no-ip.com/integrate/request and
855 # here: http://www.no-ip.com/integrate/response
856
857 url = "http://dynupdate.no-ip.com/nic/update"
858
859 def prepare_request_data(self, proto):
860 assert proto == "ipv4"
861
862 data = {
863 "hostname" : self.hostname,
864 "address" : self.get_address(proto),
865 }
866
867 return data
868
869
870 class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2, DDNSProvider):
871 handle = "nsupdate.info"
872 name = "nsupdate.info"
873 website = "http://nsupdate.info/"
874 protocols = ("ipv6", "ipv4",)
875
876 # Information about the format of the HTTP request can be found
877 # after login on the provider user interface and here:
878 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
879
880 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
881 # and for the password a so called secret.
882 @property
883 def username(self):
884 return self.get("hostname")
885
886 @property
887 def password(self):
888 return self.get("secret")
889
890 @property
891 def url(self):
892 # The update URL is different by the used protocol.
893 if self.proto == "ipv4":
894 return "https://ipv4.nsupdate.info/nic/update"
895 elif self.proto == "ipv6":
896 return "https://ipv6.nsupdate.info/nic/update"
897 else:
898 raise DDNSUpdateError(_("Invalid protocol has been given"))
899
900 def prepare_request_data(self, proto):
901 data = {
902 "myip" : self.get_address(proto),
903 }
904
905 return data
906
907
908 class DDNSProviderOpenDNS(DDNSProtocolDynDNS2, DDNSProvider):
909 handle = "opendns.com"
910 name = "OpenDNS"
911 website = "http://www.opendns.com"
912
913 # Detailed information about the update request and possible
914 # response codes can be obtained from here:
915 # https://support.opendns.com/entries/23891440
916
917 url = "https://updates.opendns.com/nic/update"
918
919 def prepare_request_data(self, proto):
920 data = {
921 "hostname" : self.hostname,
922 "myip" : self.get_address(proto),
923 }
924
925 return data
926
927
928 class DDNSProviderOVH(DDNSProtocolDynDNS2, DDNSProvider):
929 handle = "ovh.com"
930 name = "OVH"
931 website = "http://www.ovh.com/"
932 protocols = ("ipv4",)
933
934 # OVH only provides very limited information about how to
935 # update a DynDNS host. They only provide the update url
936 # on the their german subpage.
937 #
938 # http://hilfe.ovh.de/DomainDynHost
939
940 url = "https://www.ovh.com/nic/update"
941
942 def prepare_request_data(self, proto):
943 data = DDNSProtocolDynDNS2.prepare_request_data(self, proto)
944 data.update({
945 "system" : "dyndns",
946 })
947
948 return data
949
950
951 class DDNSProviderRegfish(DDNSProvider):
952 handle = "regfish.com"
953 name = "Regfish GmbH"
954 website = "http://www.regfish.com/"
955
956 # A full documentation to the providers api can be found here
957 # but is only available in german.
958 # https://www.regfish.de/domains/dyndns/dokumentation
959
960 url = "https://dyndns.regfish.de/"
961
962 def update(self):
963 data = {
964 "fqdn" : self.hostname,
965 }
966
967 # Check if we update an IPv6 address.
968 address6 = self.get_address("ipv6")
969 if address6:
970 data["ipv6"] = address6
971
972 # Check if we update an IPv4 address.
973 address4 = self.get_address("ipv4")
974 if address4:
975 data["ipv4"] = address4
976
977 # Raise an error if none address is given.
978 if not data.has_key("ipv6") and not data.has_key("ipv4"):
979 raise DDNSConfigurationError
980
981 # Check if a token has been set.
982 if self.token:
983 data["token"] = self.token
984
985 # Raise an error if no token and no useranem and password
986 # are given.
987 elif not self.username and not self.password:
988 raise DDNSConfigurationError(_("No Auth details specified."))
989
990 # HTTP Basic Auth is only allowed if no token is used.
991 if self.token:
992 # Send update to the server.
993 response = self.send_request(self.url, data=data)
994 else:
995 # Send update to the server.
996 response = self.send_request(self.url, username=self.username, password=self.password,
997 data=data)
998
999 # Get the full response message.
1000 output = response.read()
1001
1002 # Handle success messages.
1003 if "100" in output or "101" in output:
1004 return
1005
1006 # Handle error codes.
1007 if "401" or "402" in output:
1008 raise DDNSAuthenticationError
1009 elif "408" in output:
1010 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
1011 elif "409" in output:
1012 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
1013 elif "412" in output:
1014 raise DDNSRequestError(_("No valid FQDN was given."))
1015 elif "414" in output:
1016 raise DDNSInternalServerError
1017
1018 # If we got here, some other update error happened.
1019 raise DDNSUpdateError
1020
1021
1022 class DDNSProviderSelfhost(DDNSProtocolDynDNS2, DDNSProvider):
1023 handle = "selfhost.de"
1024 name = "Selfhost.de"
1025 website = "http://www.selfhost.de/"
1026 protocols = ("ipv4",)
1027
1028 url = "https://carol.selfhost.de/nic/update"
1029
1030 def prepare_request_data(self, proto):
1031 data = DDNSProtocolDynDNS2.prepare_request_data(self, proto)
1032 data.update({
1033 "hostname" : "1",
1034 })
1035
1036 return data
1037
1038
1039 class DDNSProviderSPDNS(DDNSProtocolDynDNS2, DDNSProvider):
1040 handle = "spdns.org"
1041 name = "SPDNS"
1042 website = "http://spdns.org/"
1043
1044 # Detailed information about request and response codes are provided
1045 # by the vendor. They are using almost the same mechanism and status
1046 # codes as dyndns.org so we can inherit all those stuff.
1047 #
1048 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
1049 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
1050
1051 url = "https://update.spdns.de/nic/update"
1052
1053 @property
1054 def username(self):
1055 return self.get("username") or self.hostname
1056
1057 @property
1058 def password(self):
1059 return self.get("username") or self.token
1060
1061
1062 class DDNSProviderStrato(DDNSProtocolDynDNS2, DDNSProvider):
1063 handle = "strato.com"
1064 name = "Strato AG"
1065 website = "http:/www.strato.com/"
1066 protocols = ("ipv4",)
1067
1068 # Information about the request and response can be obtained here:
1069 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
1070
1071 url = "https://dyndns.strato.com/nic/update"
1072
1073
1074 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2, DDNSProvider):
1075 handle = "twodns.de"
1076 name = "TwoDNS"
1077 website = "http://www.twodns.de"
1078 protocols = ("ipv4",)
1079
1080 # Detailed information about the request can be found here
1081 # http://twodns.de/en/faqs
1082 # http://twodns.de/en/api
1083
1084 url = "https://update.twodns.de/update"
1085
1086 def prepare_request_data(self, proto):
1087 assert proto == "ipv4"
1088
1089 data = {
1090 "ip" : self.get_address(proto),
1091 "hostname" : self.hostname
1092 }
1093
1094 return data
1095
1096
1097 class DDNSProviderUdmedia(DDNSProtocolDynDNS2, DDNSProvider):
1098 handle = "udmedia.de"
1099 name = "Udmedia GmbH"
1100 website = "http://www.udmedia.de"
1101 protocols = ("ipv4",)
1102
1103 # Information about the request can be found here
1104 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1105
1106 url = "https://www.udmedia.de/nic/update"
1107
1108
1109 class DDNSProviderVariomedia(DDNSProtocolDynDNS2, DDNSProvider):
1110 handle = "variomedia.de"
1111 name = "Variomedia"
1112 website = "http://www.variomedia.de/"
1113 protocols = ("ipv6", "ipv4",)
1114
1115 # Detailed information about the request can be found here
1116 # https://dyndns.variomedia.de/
1117
1118 url = "https://dyndns.variomedia.de/nic/update"
1119
1120 def prepare_request_data(self, proto):
1121 data = {
1122 "hostname" : self.hostname,
1123 "myip" : self.get_address(proto),
1124 }
1125
1126 return data
1127
1128
1129 class DDNSProviderZoneedit(DDNSProtocolDynDNS2, DDNSProvider):
1130 handle = "zoneedit.com"
1131 name = "Zoneedit"
1132 website = "http://www.zoneedit.com"
1133 protocols = ("ipv4",)
1134
1135 # Detailed information about the request and the response codes can be
1136 # obtained here:
1137 # http://www.zoneedit.com/doc/api/other.html
1138 # http://www.zoneedit.com/faq.html
1139
1140 url = "https://dynamic.zoneedit.com/auth/dynamic.html"
1141
1142 def update_protocol(self, proto):
1143 data = {
1144 "dnsto" : self.get_address(proto),
1145 "host" : self.hostname
1146 }
1147
1148 # Send update to the server.
1149 response = self.send_request(self.url, username=self.username, password=self.password,
1150 data=data)
1151
1152 # Get the full response message.
1153 output = response.read()
1154
1155 # Handle success messages.
1156 if output.startswith("<SUCCESS"):
1157 return
1158
1159 # Handle error codes.
1160 if output.startswith("invalid login"):
1161 raise DDNSAuthenticationError
1162 elif output.startswith("<ERROR CODE=\"704\""):
1163 raise DDNSRequestError(_("No valid FQDN was given."))
1164 elif output.startswith("<ERROR CODE=\"702\""):
1165 raise DDNSInternalServerError
1166
1167 # If we got here, some other update error happened.
1168 raise DDNSUpdateError
1169
1170
1171 class DDNSProviderZZZZ(DDNSProvider):
1172 handle = "zzzz.io"
1173 name = "zzzz"
1174 website = "https://zzzz.io"
1175 protocols = ("ipv6", "ipv4",)
1176
1177 # Detailed information about the update request can be found here:
1178 # https://zzzz.io/faq/
1179
1180 # Details about the possible response codes have been provided in the bugtracker:
1181 # https://bugzilla.ipfire.org/show_bug.cgi?id=10584#c2
1182
1183 url = "https://zzzz.io/api/v1/update"
1184
1185 def update_protocol(self, proto):
1186 data = {
1187 "ip" : self.get_address(proto),
1188 "token" : self.token,
1189 }
1190
1191 if proto == "ipv6":
1192 data["type"] = "aaaa"
1193
1194 # zzzz uses the host from the full hostname as part
1195 # of the update url.
1196 host, domain = self.hostname.split(".", 1)
1197
1198 # Add host value to the update url.
1199 url = "%s/%s" % (self.url, host)
1200
1201 # Send update to the server.
1202 try:
1203 response = self.send_request(url, data=data)
1204
1205 # Handle error codes.
1206 except DDNSNotFound:
1207 raise DDNSRequestError(_("Invalid hostname specified"))
1208
1209 # Handle success messages.
1210 if response.code == 200:
1211 return
1212
1213 # If we got here, some other update error happened.
1214 raise DDNSUpdateError