]> git.ipfire.org Git - oddments/ddns.git/blob - src/ddns/providers.py
Added support for selecting a DNS zone nsupdate should use.
[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 == "aduse":
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
228 # If we got here, some other update error happened.
229 raise DDNSUpdateError(_("Server response: %s") % output)
230
231
232 class DDNSResponseParserXML(object):
233 """
234 This class provides a parser for XML responses which
235 will be sent by various providers. This class uses the python
236 shipped XML minidom module to walk through the XML tree and return
237 a requested element.
238 """
239
240 def get_xml_tag_value(self, document, content):
241 # Send input to the parser.
242 xmldoc = xml.dom.minidom.parseString(document)
243
244 # Get XML elements by the given content.
245 element = xmldoc.getElementsByTagName(content)
246
247 # If no element has been found, we directly can return None.
248 if not element:
249 return None
250
251 # Only get the first child from an element, even there are more than one.
252 firstchild = element[0].firstChild
253
254 # Get the value of the child.
255 value = firstchild.nodeValue
256
257 # Return the value.
258 return value
259
260
261 class DDNSProviderAllInkl(DDNSProvider):
262 handle = "all-inkl.com"
263 name = "All-inkl.com"
264 website = "http://all-inkl.com/"
265 protocols = ("ipv4",)
266
267 # There are only information provided by the vendor how to
268 # perform an update on a FRITZ Box. Grab requried informations
269 # from the net.
270 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
271
272 url = "http://dyndns.kasserver.com"
273
274 def update(self):
275 # There is no additional data required so we directly can
276 # send our request.
277 response = self.send_request(self.url, username=self.username, password=self.password)
278
279 # Get the full response message.
280 output = response.read()
281
282 # Handle success messages.
283 if output.startswith("good") or output.startswith("nochg"):
284 return
285
286 # If we got here, some other update error happened.
287 raise DDNSUpdateError
288
289
290 class DDNSProviderBindNsupdate(DDNSProvider):
291 handle = "nsupdate"
292 name = "BIND nsupdate utility"
293 website = "http://en.wikipedia.org/wiki/Nsupdate"
294
295 DEFAULT_TTL = 60
296
297 def update(self):
298 scriptlet = self.__make_scriptlet()
299
300 # -v enables TCP hence we transfer keys and other data that may
301 # exceed the size of one packet.
302 # -t sets the timeout
303 command = ["nsupdate", "-v", "-t", "60"]
304
305 p = subprocess.Popen(command, shell=True,
306 stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
307 )
308 stdout, stderr = p.communicate(scriptlet)
309
310 if p.returncode == 0:
311 return
312
313 raise DDNSError("nsupdate terminated with error code: %s\n %s" % (p.returncode, stderr))
314
315 def __make_scriptlet(self):
316 scriptlet = []
317
318 # Set a different server the update is sent to.
319 server = self.get("server", None)
320 if server:
321 scriptlet.append("server %s" % server)
322
323 # Set the DNS zone the host should be added to.
324 zone = self.get("zone", None)
325 if zone:
326 scriptlet.append("zone %s" % zone)
327
328 key = self.get("key", None)
329 if key:
330 secret = self.get("secret")
331
332 scriptlet.append("key %s %s" % (key, secret))
333
334 ttl = self.get("ttl", self.DEFAULT_TTL)
335
336 # Perform an update for each supported protocol.
337 for rrtype, proto in (("AAAA", "ipv6"), ("A", "ipv4")):
338 address = self.get_address(proto)
339 if not address:
340 continue
341
342 scriptlet.append("update delete %s. %s" % (self.hostname, rrtype))
343 scriptlet.append("update add %s. %s %s %s" % \
344 (self.hostname, ttl, rrtype, address))
345
346 # Send the actions to the server.
347 scriptlet.append("send")
348 scriptlet.append("quit")
349
350 logger.debug(_("Scriptlet:"))
351 for line in scriptlet:
352 # Masquerade the line with the secret key.
353 if line.startswith("key"):
354 line = "key **** ****"
355
356 logger.debug(" %s" % line)
357
358 return "\n".join(scriptlet)
359
360
361 class DDNSProviderDHS(DDNSProvider):
362 handle = "dhs.org"
363 name = "DHS International"
364 website = "http://dhs.org/"
365 protocols = ("ipv4",)
366
367 # No information about the used update api provided on webpage,
368 # grabed from source code of ez-ipudate.
369
370 url = "http://members.dhs.org/nic/hosts"
371
372 def update(self):
373 data = {
374 "domain" : self.hostname,
375 "ip" : self.get_address("ipv4"),
376 "hostcmd" : "edit",
377 "hostcmdstage" : "2",
378 "type" : "4",
379 }
380
381 # Send update to the server.
382 response = self.send_request(self.url, username=self.username, password=self.password,
383 data=data)
384
385 # Handle success messages.
386 if response.code == 200:
387 return
388
389 # If we got here, some other update error happened.
390 raise DDNSUpdateError
391
392
393 class DDNSProviderDNSpark(DDNSProvider):
394 handle = "dnspark.com"
395 name = "DNS Park"
396 website = "http://dnspark.com/"
397 protocols = ("ipv4",)
398
399 # Informations to the used api can be found here:
400 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
401
402 url = "https://control.dnspark.com/api/dynamic/update.php"
403
404 def update(self):
405 data = {
406 "domain" : self.hostname,
407 "ip" : self.get_address("ipv4"),
408 }
409
410 # Send update to the server.
411 response = self.send_request(self.url, username=self.username, password=self.password,
412 data=data)
413
414 # Get the full response message.
415 output = response.read()
416
417 # Handle success messages.
418 if output.startswith("ok") or output.startswith("nochange"):
419 return
420
421 # Handle error codes.
422 if output == "unauth":
423 raise DDNSAuthenticationError
424 elif output == "abuse":
425 raise DDNSAbuseError
426 elif output == "blocked":
427 raise DDNSBlockedError
428 elif output == "nofqdn":
429 raise DDNSRequestError(_("No valid FQDN was given."))
430 elif output == "nohost":
431 raise DDNSRequestError(_("Invalid hostname specified."))
432 elif output == "notdyn":
433 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
434 elif output == "invalid":
435 raise DDNSRequestError(_("Invalid IP address has been sent."))
436
437 # If we got here, some other update error happened.
438 raise DDNSUpdateError
439
440
441 class DDNSProviderDtDNS(DDNSProvider):
442 handle = "dtdns.com"
443 name = "DtDNS"
444 website = "http://dtdns.com/"
445 protocols = ("ipv4",)
446
447 # Information about the format of the HTTPS request is to be found
448 # http://www.dtdns.com/dtsite/updatespec
449
450 url = "https://www.dtdns.com/api/autodns.cfm"
451
452 def update(self):
453 data = {
454 "ip" : self.get_address("ipv4"),
455 "id" : self.hostname,
456 "pw" : self.password
457 }
458
459 # Send update to the server.
460 response = self.send_request(self.url, data=data)
461
462 # Get the full response message.
463 output = response.read()
464
465 # Remove all leading and trailing whitespace.
466 output = output.strip()
467
468 # Handle success messages.
469 if "now points to" in output:
470 return
471
472 # Handle error codes.
473 if output == "No hostname to update was supplied.":
474 raise DDNSRequestError(_("No hostname specified."))
475
476 elif output == "The hostname you supplied is not valid.":
477 raise DDNSRequestError(_("Invalid hostname specified."))
478
479 elif output == "The password you supplied is not valid.":
480 raise DDNSAuthenticationError
481
482 elif output == "Administration has disabled this account.":
483 raise DDNSRequestError(_("Account has been disabled."))
484
485 elif output == "Illegal character in IP.":
486 raise DDNSRequestError(_("Invalid IP address has been sent."))
487
488 elif output == "Too many failed requests.":
489 raise DDNSRequestError(_("Too many failed requests."))
490
491 # If we got here, some other update error happened.
492 raise DDNSUpdateError
493
494
495 class DDNSProviderDynDNS(DDNSProtocolDynDNS2, DDNSProvider):
496 handle = "dyndns.org"
497 name = "Dyn"
498 website = "http://dyn.com/dns/"
499 protocols = ("ipv4",)
500
501 # Information about the format of the request is to be found
502 # http://http://dyn.com/support/developers/api/perform-update/
503 # http://dyn.com/support/developers/api/return-codes/
504
505 url = "https://members.dyndns.org/nic/update"
506
507
508 class DDNSProviderDynU(DDNSProtocolDynDNS2, DDNSProvider):
509 handle = "dynu.com"
510 name = "Dynu"
511 website = "http://dynu.com/"
512 protocols = ("ipv6", "ipv4",)
513
514 # Detailed information about the request and response codes
515 # are available on the providers webpage.
516 # http://dynu.com/Default.aspx?page=dnsapi
517
518 url = "https://api.dynu.com/nic/update"
519
520 def _prepare_request_data(self):
521 data = DDNSProtocolDynDNS2._prepare_request_data(self)
522
523 # This one supports IPv6
524 data.update({
525 "myipv6" : self.get_address("ipv6"),
526 })
527
528 return data
529
530
531 class DDNSProviderEasyDNS(DDNSProtocolDynDNS2, DDNSProvider):
532 handle = "easydns.com"
533 name = "EasyDNS"
534 website = "http://www.easydns.com/"
535 protocols = ("ipv4",)
536
537 # There is only some basic documentation provided by the vendor,
538 # also searching the web gain very poor results.
539 # http://mediawiki.easydns.com/index.php/Dynamic_DNS
540
541 url = "http://api.cp.easydns.com/dyn/tomato.php"
542
543
544 class DDNSProviderEnomCom(DDNSResponseParserXML, DDNSProvider):
545 handle = "enom.com"
546 name = "eNom Inc."
547 website = "http://www.enom.com/"
548
549 # There are very detailed information about how to send an update request and
550 # the respone codes.
551 # http://www.enom.com/APICommandCatalog/
552
553 url = "https://dynamic.name-services.com/interface.asp"
554
555 def update(self):
556 data = {
557 "command" : "setdnshost",
558 "responsetype" : "xml",
559 "address" : self.get_address("ipv4"),
560 "domainpassword" : self.password,
561 "zone" : self.hostname
562 }
563
564 # Send update to the server.
565 response = self.send_request(self.url, data=data)
566
567 # Get the full response message.
568 output = response.read()
569
570 # Handle success messages.
571 if self.get_xml_tag_value(output, "ErrCount") == "0":
572 return
573
574 # Handle error codes.
575 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
576
577 if errorcode == "304155":
578 raise DDNSAuthenticationError
579 elif errorcode == "304153":
580 raise DDNSRequestError(_("Domain not found."))
581
582 # If we got here, some other update error happened.
583 raise DDNSUpdateError
584
585
586 class DDNSProviderEntryDNS(DDNSProvider):
587 handle = "entrydns.net"
588 name = "EntryDNS"
589 website = "http://entrydns.net/"
590 protocols = ("ipv4",)
591
592 # Some very tiny details about their so called "Simple API" can be found
593 # here: https://entrydns.net/help
594 url = "https://entrydns.net/records/modify"
595
596 def update(self):
597 data = {
598 "ip" : self.get_address("ipv4")
599 }
600
601 # Add auth token to the update url.
602 url = "%s/%s" % (self.url, self.token)
603
604 # Send update to the server.
605 try:
606 response = self.send_request(url, method="PUT", data=data)
607
608 # Handle error codes
609 except urllib2.HTTPError, e:
610 if e.code == 404:
611 raise DDNSAuthenticationError
612
613 elif e.code == 422:
614 raise DDNSRequestError(_("An invalid IP address was submitted"))
615
616 raise
617
618 # Handle success messages.
619 if response.code == 200:
620 return
621
622 # If we got here, some other update error happened.
623 raise DDNSUpdateError
624
625
626 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider):
627 handle = "freedns.afraid.org"
628 name = "freedns.afraid.org"
629 website = "http://freedns.afraid.org/"
630
631 # No information about the request or response could be found on the vendor
632 # page. All used values have been collected by testing.
633 url = "https://freedns.afraid.org/dynamic/update.php"
634
635 @property
636 def proto(self):
637 return self.get("proto")
638
639 def update(self):
640 address = self.get_address(self.proto)
641
642 data = {
643 "address" : address,
644 }
645
646 # Add auth token to the update url.
647 url = "%s?%s" % (self.url, self.token)
648
649 # Send update to the server.
650 response = self.send_request(url, data=data)
651
652 # Get the full response message.
653 output = response.read()
654
655 # Handle success messages.
656 if output.startswith("Updated") or "has not changed" in output:
657 return
658
659 # Handle error codes.
660 if output == "ERROR: Unable to locate this record":
661 raise DDNSAuthenticationError
662 elif "is an invalid IP address" in output:
663 raise DDNSRequestError(_("Invalid IP address has been sent."))
664
665 # If we got here, some other update error happened.
666 raise DDNSUpdateError
667
668
669 class DDNSProviderLightningWireLabs(DDNSProvider):
670 handle = "dns.lightningwirelabs.com"
671 name = "Lightning Wire Labs DNS Service"
672 website = "http://dns.lightningwirelabs.com/"
673
674 # Information about the format of the HTTPS request is to be found
675 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
676
677 url = "https://dns.lightningwirelabs.com/update"
678
679 def update(self):
680 data = {
681 "hostname" : self.hostname,
682 "address6" : self.get_address("ipv6", "-"),
683 "address4" : self.get_address("ipv4", "-"),
684 }
685
686 # Check if a token has been set.
687 if self.token:
688 data["token"] = self.token
689
690 # Check for username and password.
691 elif self.username and self.password:
692 data.update({
693 "username" : self.username,
694 "password" : self.password,
695 })
696
697 # Raise an error if no auth details are given.
698 else:
699 raise DDNSConfigurationError
700
701 # Send update to the server.
702 response = self.send_request(self.url, data=data)
703
704 # Handle success messages.
705 if response.code == 200:
706 return
707
708 # If we got here, some other update error happened.
709 raise DDNSUpdateError
710
711
712 class DDNSProviderNamecheap(DDNSResponseParserXML, DDNSProvider):
713 handle = "namecheap.com"
714 name = "Namecheap"
715 website = "http://namecheap.com"
716 protocols = ("ipv4",)
717
718 # Information about the format of the HTTP request is to be found
719 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
720 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
721
722 url = "https://dynamicdns.park-your-domain.com/update"
723
724 def update(self):
725 # Namecheap requires the hostname splitted into a host and domain part.
726 host, domain = self.hostname.split(".", 1)
727
728 data = {
729 "ip" : self.get_address("ipv4"),
730 "password" : self.password,
731 "host" : host,
732 "domain" : domain
733 }
734
735 # Send update to the server.
736 response = self.send_request(self.url, data=data)
737
738 # Get the full response message.
739 output = response.read()
740
741 # Handle success messages.
742 if self.get_xml_tag_value(output, "IP") == self.get_address("ipv4"):
743 return
744
745 # Handle error codes.
746 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
747
748 if errorcode == "304156":
749 raise DDNSAuthenticationError
750 elif errorcode == "316153":
751 raise DDNSRequestError(_("Domain not found."))
752 elif errorcode == "316154":
753 raise DDNSRequestError(_("Domain not active."))
754 elif errorcode in ("380098", "380099"):
755 raise DDNSInternalServerError
756
757 # If we got here, some other update error happened.
758 raise DDNSUpdateError
759
760
761 class DDNSProviderNOIP(DDNSProtocolDynDNS2, DDNSProvider):
762 handle = "no-ip.com"
763 name = "No-IP"
764 website = "http://www.no-ip.com/"
765 protocols = ("ipv4",)
766
767 # Information about the format of the HTTP request is to be found
768 # here: http://www.no-ip.com/integrate/request and
769 # here: http://www.no-ip.com/integrate/response
770
771 url = "http://dynupdate.no-ip.com/nic/update"
772
773 def _prepare_request_data(self):
774 data = {
775 "hostname" : self.hostname,
776 "address" : self.get_address("ipv4"),
777 }
778
779 return data
780
781
782 class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2, DDNSProvider):
783 handle = "nsupdate.info"
784 name = "nsupdate.info"
785 website = "http://www.nsupdate.info/"
786 protocols = ("ipv6", "ipv4",)
787
788 # Information about the format of the HTTP request can be found
789 # after login on the provider user intrface and here:
790 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
791
792 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
793 # and for the password a so called secret.
794 @property
795 def username(self):
796 return self.get("hostname")
797
798 @property
799 def password(self):
800 return self.get("secret")
801
802 @property
803 def proto(self):
804 return self.get("proto")
805
806 @property
807 def url(self):
808 # The update URL is different by the used protocol.
809 if self.proto == "ipv4":
810 return "https://ipv4.nsupdate.info/nic/update"
811 elif self.proto == "ipv6":
812 return "https://ipv6.nsupdate.info/nic/update"
813 else:
814 raise DDNSUpdateError(_("Invalid protocol has been given"))
815
816 def _prepare_request_data(self):
817 data = {
818 "myip" : self.get_address(self.proto),
819 }
820
821 return data
822
823
824 class DDNSProviderOpenDNS(DDNSProtocolDynDNS2, DDNSProvider):
825 handle = "opendns.com"
826 name = "OpenDNS"
827 website = "http://www.opendns.com"
828
829 # Detailed information about the update request and possible
830 # response codes can be obtained from here:
831 # https://support.opendns.com/entries/23891440
832
833 url = "https://updates.opendns.com/nic/update"
834
835 @property
836 def proto(self):
837 return self.get("proto")
838
839 def _prepare_request_data(self):
840 data = {
841 "hostname" : self.hostname,
842 "myip" : self.get_address(self.proto)
843 }
844
845 return data
846
847
848 class DDNSProviderOVH(DDNSProtocolDynDNS2, DDNSProvider):
849 handle = "ovh.com"
850 name = "OVH"
851 website = "http://www.ovh.com/"
852 protocols = ("ipv4",)
853
854 # OVH only provides very limited information about how to
855 # update a DynDNS host. They only provide the update url
856 # on the their german subpage.
857 #
858 # http://hilfe.ovh.de/DomainDynHost
859
860 url = "https://www.ovh.com/nic/update"
861
862 def _prepare_request_data(self):
863 data = DDNSProtocolDynDNS2._prepare_request_data(self)
864 data.update({
865 "system" : "dyndns",
866 })
867
868 return data
869
870
871 class DDNSProviderRegfish(DDNSProvider):
872 handle = "regfish.com"
873 name = "Regfish GmbH"
874 website = "http://www.regfish.com/"
875
876 # A full documentation to the providers api can be found here
877 # but is only available in german.
878 # https://www.regfish.de/domains/dyndns/dokumentation
879
880 url = "https://dyndns.regfish.de/"
881
882 def update(self):
883 data = {
884 "fqdn" : self.hostname,
885 }
886
887 # Check if we update an IPv6 address.
888 address6 = self.get_address("ipv6")
889 if address6:
890 data["ipv6"] = address6
891
892 # Check if we update an IPv4 address.
893 address4 = self.get_address("ipv4")
894 if address4:
895 data["ipv4"] = address4
896
897 # Raise an error if none address is given.
898 if not data.has_key("ipv6") and not data.has_key("ipv4"):
899 raise DDNSConfigurationError
900
901 # Check if a token has been set.
902 if self.token:
903 data["token"] = self.token
904
905 # Raise an error if no token and no useranem and password
906 # are given.
907 elif not self.username and not self.password:
908 raise DDNSConfigurationError(_("No Auth details specified."))
909
910 # HTTP Basic Auth is only allowed if no token is used.
911 if self.token:
912 # Send update to the server.
913 response = self.send_request(self.url, data=data)
914 else:
915 # Send update to the server.
916 response = self.send_request(self.url, username=self.username, password=self.password,
917 data=data)
918
919 # Get the full response message.
920 output = response.read()
921
922 # Handle success messages.
923 if "100" in output or "101" in output:
924 return
925
926 # Handle error codes.
927 if "401" or "402" in output:
928 raise DDNSAuthenticationError
929 elif "408" in output:
930 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
931 elif "409" in output:
932 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
933 elif "412" in output:
934 raise DDNSRequestError(_("No valid FQDN was given."))
935 elif "414" in output:
936 raise DDNSInternalServerError
937
938 # If we got here, some other update error happened.
939 raise DDNSUpdateError
940
941
942 class DDNSProviderSelfhost(DDNSProtocolDynDNS2, DDNSProvider):
943 handle = "selfhost.de"
944 name = "Selfhost.de"
945 website = "http://www.selfhost.de/"
946 protocols = ("ipv4",)
947
948 url = "https://carol.selfhost.de/nic/update"
949
950 def _prepare_request_data(self):
951 data = DDNSProtocolDynDNS2._prepare_request_data(self)
952 data.update({
953 "hostname" : "1",
954 })
955
956 return data
957
958
959 class DDNSProviderSPDNS(DDNSProtocolDynDNS2, DDNSProvider):
960 handle = "spdns.org"
961 name = "SPDNS"
962 website = "http://spdns.org/"
963 protocols = ("ipv4",)
964
965 # Detailed information about request and response codes are provided
966 # by the vendor. They are using almost the same mechanism and status
967 # codes as dyndns.org so we can inherit all those stuff.
968 #
969 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
970 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
971
972 url = "https://update.spdns.de/nic/update"
973
974
975 class DDNSProviderStrato(DDNSProtocolDynDNS2, DDNSProvider):
976 handle = "strato.com"
977 name = "Strato AG"
978 website = "http:/www.strato.com/"
979 protocols = ("ipv4",)
980
981 # Information about the request and response can be obtained here:
982 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
983
984 url = "https://dyndns.strato.com/nic/update"
985
986
987 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2, DDNSProvider):
988 handle = "twodns.de"
989 name = "TwoDNS"
990 website = "http://www.twodns.de"
991 protocols = ("ipv4",)
992
993 # Detailed information about the request can be found here
994 # http://twodns.de/en/faqs
995 # http://twodns.de/en/api
996
997 url = "https://update.twodns.de/update"
998
999 def _prepare_request_data(self):
1000 data = {
1001 "ip" : self.get_address("ipv4"),
1002 "hostname" : self.hostname
1003 }
1004
1005 return data
1006
1007
1008 class DDNSProviderUdmedia(DDNSProtocolDynDNS2, DDNSProvider):
1009 handle = "udmedia.de"
1010 name = "Udmedia GmbH"
1011 website = "http://www.udmedia.de"
1012 protocols = ("ipv4",)
1013
1014 # Information about the request can be found here
1015 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1016
1017 url = "https://www.udmedia.de/nic/update"
1018
1019
1020 class DDNSProviderVariomedia(DDNSProtocolDynDNS2, DDNSProvider):
1021 handle = "variomedia.de"
1022 name = "Variomedia"
1023 website = "http://www.variomedia.de/"
1024 protocols = ("ipv6", "ipv4",)
1025
1026 # Detailed information about the request can be found here
1027 # https://dyndns.variomedia.de/
1028
1029 url = "https://dyndns.variomedia.de/nic/update"
1030
1031 @property
1032 def proto(self):
1033 return self.get("proto")
1034
1035 def _prepare_request_data(self):
1036 data = {
1037 "hostname" : self.hostname,
1038 "myip" : self.get_address(self.proto)
1039 }
1040
1041 return data
1042
1043
1044 class DDNSProviderZoneedit(DDNSProtocolDynDNS2, DDNSProvider):
1045 handle = "zoneedit.com"
1046 name = "Zoneedit"
1047 website = "http://www.zoneedit.com"
1048 protocols = ("ipv4",)
1049
1050 # Detailed information about the request and the response codes can be
1051 # obtained here:
1052 # http://www.zoneedit.com/doc/api/other.html
1053 # http://www.zoneedit.com/faq.html
1054
1055 url = "https://dynamic.zoneedit.com/auth/dynamic.html"
1056
1057 @property
1058 def proto(self):
1059 return self.get("proto")
1060
1061 def update(self):
1062 data = {
1063 "dnsto" : self.get_address(self.proto),
1064 "host" : self.hostname
1065 }
1066
1067 # Send update to the server.
1068 response = self.send_request(self.url, username=self.username, password=self.password,
1069 data=data)
1070
1071 # Get the full response message.
1072 output = response.read()
1073
1074 # Handle success messages.
1075 if output.startswith("<SUCCESS"):
1076 return
1077
1078 # Handle error codes.
1079 if output.startswith("invalid login"):
1080 raise DDNSAuthenticationError
1081 elif output.startswith("<ERROR CODE=\"704\""):
1082 raise DDNSRequestError(_("No valid FQDN was given."))
1083 elif output.startswith("<ERROR CODE=\"702\""):
1084 raise DDNSInternalServerError
1085
1086 # If we got here, some other update error happened.
1087 raise DDNSUpdateError