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