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