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