]> git.ipfire.org Git - oddments/ddns.git/blob - src/ddns/providers.py
Handle HTTP error 404 generically
[oddments/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 raise NotImplementedError
144
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
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
161 if not current_address in addresses:
162 return False
163
164 return True
165
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
173 def get_address(self, proto, default=None):
174 """
175 Proxy method to get the current IP address.
176 """
177 return self.core.system.get_address(proto) or default
178
179
180 class 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
189 # http://dyn.com/support/developers/api/perform-update/
190 # http://dyn.com/support/developers/api/return-codes/
191
192 def _prepare_request_data(self):
193 data = {
194 "hostname" : self.hostname,
195 "myip" : self.get_address("ipv4"),
196 }
197
198 return data
199
200 def update(self):
201 data = self._prepare_request_data()
202
203 # Send update to the server.
204 response = self.send_request(self.url, data=data,
205 username=self.username, password=self.password)
206
207 # Get the full response message.
208 output = response.read()
209
210 # Handle success messages.
211 if output.startswith("good") or output.startswith("nochg"):
212 return
213
214 # Handle error codes.
215 if output == "badauth":
216 raise DDNSAuthenticationError
217 elif output == "abuse":
218 raise DDNSAbuseError
219 elif output == "notfqdn":
220 raise DDNSRequestError(_("No valid FQDN was given."))
221 elif output == "nohost":
222 raise DDNSRequestError(_("Specified host does not exist."))
223 elif output == "911":
224 raise DDNSInternalServerError
225 elif output == "dnserr":
226 raise DDNSInternalServerError(_("DNS error encountered."))
227 elif output == "badagent":
228 raise DDNSBlockedError
229
230 # If we got here, some other update error happened.
231 raise DDNSUpdateError(_("Server response: %s") % output)
232
233
234 class 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
263 class DDNSProviderAllInkl(DDNSProvider):
264 handle = "all-inkl.com"
265 name = "All-inkl.com"
266 website = "http://all-inkl.com/"
267 protocols = ("ipv4",)
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):
277 # There is no additional data required so we directly can
278 # send our request.
279 response = self.send_request(self.url, username=self.username, password=self.password)
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
292 class 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
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
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
363 class DDNSProviderDHS(DDNSProvider):
364 handle = "dhs.org"
365 name = "DHS International"
366 website = "http://dhs.org/"
367 protocols = ("ipv4",)
368
369 # No information about the used update api provided on webpage,
370 # grabed from source code of ez-ipudate.
371
372 url = "http://members.dhs.org/nic/hosts"
373
374 def update(self):
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.
384 response = self.send_request(self.url, username=self.username, password=self.password,
385 data=data)
386
387 # Handle success messages.
388 if response.code == 200:
389 return
390
391 # If we got here, some other update error happened.
392 raise DDNSUpdateError
393
394
395 class DDNSProviderDNSpark(DDNSProvider):
396 handle = "dnspark.com"
397 name = "DNS Park"
398 website = "http://dnspark.com/"
399 protocols = ("ipv4",)
400
401 # Informations to the used api can be found here:
402 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
403
404 url = "https://control.dnspark.com/api/dynamic/update.php"
405
406 def update(self):
407 data = {
408 "domain" : self.hostname,
409 "ip" : self.get_address("ipv4"),
410 }
411
412 # Send update to the server.
413 response = self.send_request(self.url, username=self.username, password=self.password,
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
442
443 class DDNSProviderDtDNS(DDNSProvider):
444 handle = "dtdns.com"
445 name = "DtDNS"
446 website = "http://dtdns.com/"
447 protocols = ("ipv4",)
448
449 # Information about the format of the HTTPS request is to be found
450 # http://www.dtdns.com/dtsite/updatespec
451
452 url = "https://www.dtdns.com/api/autodns.cfm"
453
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
497 class DDNSProviderDynDNS(DDNSProtocolDynDNS2, DDNSProvider):
498 handle = "dyndns.org"
499 name = "Dyn"
500 website = "http://dyn.com/dns/"
501 protocols = ("ipv4",)
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/
506
507 url = "https://members.dyndns.org/nic/update"
508
509
510 class DDNSProviderDynU(DDNSProtocolDynDNS2, DDNSProvider):
511 handle = "dynu.com"
512 name = "Dynu"
513 website = "http://dynu.com/"
514 protocols = ("ipv6", "ipv4",)
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):
523 data = DDNSProtocolDynDNS2._prepare_request_data(self)
524
525 # This one supports IPv6
526 myipv6 = self.get_address("ipv6")
527
528 # Add update information if we have an IPv6 address.
529 if myipv6:
530 data["myipv6"] = myipv6
531
532 return data
533
534
535 class DDNSProviderEasyDNS(DDNSProtocolDynDNS2, DDNSProvider):
536 handle = "easydns.com"
537 name = "EasyDNS"
538 website = "http://www.easydns.com/"
539 protocols = ("ipv4",)
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
548 class 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
559 class 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
603 class 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
645 class 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:
665 response = self.send_request(url, data=data)
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
685 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider):
686 handle = "freedns.afraid.org"
687 name = "freedns.afraid.org"
688 website = "http://freedns.afraid.org/"
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
711 # Get the full response message.
712 output = response.read()
713
714 # Handle success messages.
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
724 # If we got here, some other update error happened.
725 raise DDNSUpdateError
726
727
728 class DDNSProviderLightningWireLabs(DDNSProvider):
729 handle = "dns.lightningwirelabs.com"
730 name = "Lightning Wire Labs DNS Service"
731 website = "http://dns.lightningwirelabs.com/"
732
733 # Information about the format of the HTTPS request is to be found
734 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
735
736 url = "https://dns.lightningwirelabs.com/update"
737
738 def update(self):
739 data = {
740 "hostname" : self.hostname,
741 "address6" : self.get_address("ipv6", "-"),
742 "address4" : self.get_address("ipv4", "-"),
743 }
744
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.
761 response = self.send_request(self.url, data=data)
762
763 # Handle success messages.
764 if response.code == 200:
765 return
766
767 # If we got here, some other update error happened.
768 raise DDNSUpdateError
769
770
771 class DDNSProviderNamecheap(DDNSResponseParserXML, DDNSProvider):
772 handle = "namecheap.com"
773 name = "Namecheap"
774 website = "http://namecheap.com"
775 protocols = ("ipv4",)
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
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.
801 if self.get_xml_tag_value(output, "IP") == self.get_address("ipv4"):
802 return
803
804 # Handle error codes.
805 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
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
820 class DDNSProviderNOIP(DDNSProtocolDynDNS2, DDNSProvider):
821 handle = "no-ip.com"
822 name = "No-IP"
823 website = "http://www.no-ip.com/"
824 protocols = ("ipv4",)
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
830 url = "http://dynupdate.no-ip.com/nic/update"
831
832 def _prepare_request_data(self):
833 data = {
834 "hostname" : self.hostname,
835 "address" : self.get_address("ipv4"),
836 }
837
838 return data
839
840
841 class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2, DDNSProvider):
842 handle = "nsupdate.info"
843 name = "nsupdate.info"
844 website = "http://nsupdate.info/"
845 protocols = ("ipv6", "ipv4",)
846
847 # Information about the format of the HTTP request can be found
848 # after login on the provider user interface and here:
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
883 class 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
907 class DDNSProviderOVH(DDNSProtocolDynDNS2, DDNSProvider):
908 handle = "ovh.com"
909 name = "OVH"
910 website = "http://www.ovh.com/"
911 protocols = ("ipv4",)
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):
922 data = DDNSProtocolDynDNS2._prepare_request_data(self)
923 data.update({
924 "system" : "dyndns",
925 })
926
927 return data
928
929
930 class DDNSProviderRegfish(DDNSProvider):
931 handle = "regfish.com"
932 name = "Regfish GmbH"
933 website = "http://www.regfish.com/"
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
1001 class DDNSProviderSelfhost(DDNSProtocolDynDNS2, DDNSProvider):
1002 handle = "selfhost.de"
1003 name = "Selfhost.de"
1004 website = "http://www.selfhost.de/"
1005 protocols = ("ipv4",)
1006
1007 url = "https://carol.selfhost.de/nic/update"
1008
1009 def _prepare_request_data(self):
1010 data = DDNSProtocolDynDNS2._prepare_request_data(self)
1011 data.update({
1012 "hostname" : "1",
1013 })
1014
1015 return data
1016
1017
1018 class DDNSProviderSPDNS(DDNSProtocolDynDNS2, DDNSProvider):
1019 handle = "spdns.org"
1020 name = "SPDNS"
1021 website = "http://spdns.org/"
1022 protocols = ("ipv4",)
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"
1032
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
1041
1042 class DDNSProviderStrato(DDNSProtocolDynDNS2, DDNSProvider):
1043 handle = "strato.com"
1044 name = "Strato AG"
1045 website = "http:/www.strato.com/"
1046 protocols = ("ipv4",)
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
1054 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2, DDNSProvider):
1055 handle = "twodns.de"
1056 name = "TwoDNS"
1057 website = "http://www.twodns.de"
1058 protocols = ("ipv4",)
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
1075 class DDNSProviderUdmedia(DDNSProtocolDynDNS2, DDNSProvider):
1076 handle = "udmedia.de"
1077 name = "Udmedia GmbH"
1078 website = "http://www.udmedia.de"
1079 protocols = ("ipv4",)
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
1087 class DDNSProviderVariomedia(DDNSProtocolDynDNS2, DDNSProvider):
1088 handle = "variomedia.de"
1089 name = "Variomedia"
1090 website = "http://www.variomedia.de/"
1091 protocols = ("ipv6", "ipv4",)
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 }
1107
1108 return data
1109
1110
1111 class DDNSProviderZoneedit(DDNSProtocolDynDNS2, DDNSProvider):
1112 handle = "zoneedit.com"
1113 name = "Zoneedit"
1114 website = "http://www.zoneedit.com"
1115 protocols = ("ipv4",)
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
1155
1156
1157 class 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.
1189 except DDNSNotFound:
1190 raise DDNSRequestError(_("Invalid hostname specified"))
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