]> git.ipfire.org Git - ddns.git/blame - src/ddns/providers.py
Add opendns as new provider.
[ddns.git] / src / ddns / providers.py
CommitLineData
f22ab085 1#!/usr/bin/python
3fdcb9d1
MT
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###############################################################################
f22ab085 21
7399fc5b 22import logging
a892c594 23import subprocess
3b16fdb1 24import urllib2
d1cd57eb 25import xml.dom.minidom
7399fc5b
MT
26
27from i18n import _
28
f22ab085
MT
29# Import all possible exception types.
30from .errors import *
31
7399fc5b
MT
32logger = logging.getLogger("ddns.providers")
33logger.propagate = 1
34
adfe6272
MT
35_providers = {}
36
37def get():
38 """
39 Returns a dict with all automatically registered providers.
40 """
41 return _providers.copy()
42
f22ab085 43class DDNSProvider(object):
6a11646e
MT
44 # A short string that uniquely identifies
45 # this provider.
46 handle = None
f22ab085 47
6a11646e
MT
48 # The full name of the provider.
49 name = None
f22ab085 50
6a11646e
MT
51 # A weburl to the homepage of the provider.
52 # (Where to register a new account?)
53 website = None
f22ab085 54
6a11646e
MT
55 # A list of supported protocols.
56 protocols = ("ipv6", "ipv4")
f22ab085
MT
57
58 DEFAULT_SETTINGS = {}
59
adfe6272
MT
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
f22ab085
MT
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
f22ab085
MT
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
46687828
SS
119 @property
120 def token(self):
121 """
122 Fast access to the token.
123 """
124 return self.get("token")
125
9da3e685
MT
126 def __call__(self, force=False):
127 if force:
c3888f15 128 logger.debug(_("Updating %s forced") % self.hostname)
9da3e685 129
7399fc5b 130 # Check if we actually need to update this host.
9da3e685 131 elif self.is_uptodate(self.protocols):
c3888f15 132 logger.debug(_("%s is already up to date") % self.hostname)
7399fc5b
MT
133 return
134
135 # Execute the update.
5f402f36
MT
136 self.update()
137
138 def update(self):
f22ab085
MT
139 raise NotImplementedError
140
7399fc5b
MT
141 def is_uptodate(self, protos):
142 """
143 Returns True if this host is already up to date
144 and does not need to change the IP address on the
145 name server.
146 """
147 for proto in protos:
148 addresses = self.core.system.resolve(self.hostname, proto)
149
150 current_address = self.get_address(proto)
151
38d81db4
MT
152 # If no addresses for the given protocol exist, we
153 # are fine...
154 if current_address is None and not addresses:
155 continue
156
7399fc5b
MT
157 if not current_address in addresses:
158 return False
159
160 return True
161
f22ab085
MT
162 def send_request(self, *args, **kwargs):
163 """
164 Proxy connection to the send request
165 method.
166 """
167 return self.core.system.send_request(*args, **kwargs)
168
e3c70807 169 def get_address(self, proto, default=None):
f22ab085
MT
170 """
171 Proxy method to get the current IP address.
172 """
e3c70807 173 return self.core.system.get_address(proto) or default
f22ab085
MT
174
175
5d4bec40
MT
176class DDNSProtocolDynDNS2(object):
177 """
178 This is an abstract class that implements the DynDNS updater
179 protocol version 2. As this is a popular way to update dynamic
180 DNS records, this class is supposed make the provider classes
181 shorter and simpler.
182 """
183
184 # Information about the format of the request is to be found
486c1b9d 185 # http://dyn.com/support/developers/api/perform-update/
5d4bec40
MT
186 # http://dyn.com/support/developers/api/return-codes/
187
188 def _prepare_request_data(self):
189 data = {
190 "hostname" : self.hostname,
191 "myip" : self.get_address("ipv4"),
192 }
193
194 return data
195
196 def update(self):
197 data = self._prepare_request_data()
198
199 # Send update to the server.
200 response = self.send_request(self.url, data=data,
201 username=self.username, password=self.password)
202
203 # Get the full response message.
204 output = response.read()
205
206 # Handle success messages.
207 if output.startswith("good") or output.startswith("nochg"):
208 return
209
210 # Handle error codes.
211 if output == "badauth":
212 raise DDNSAuthenticationError
213 elif output == "aduse":
214 raise DDNSAbuseError
215 elif output == "notfqdn":
216 raise DDNSRequestError(_("No valid FQDN was given."))
217 elif output == "nohost":
218 raise DDNSRequestError(_("Specified host does not exist."))
219 elif output == "911":
220 raise DDNSInternalServerError
221 elif output == "dnserr":
222 raise DDNSInternalServerError(_("DNS error encountered."))
223
224 # If we got here, some other update error happened.
225 raise DDNSUpdateError(_("Server response: %s") % output)
226
227
78c9780b
SS
228class DDNSResponseParserXML(object):
229 """
230 This class provides a parser for XML responses which
231 will be sent by various providers. This class uses the python
232 shipped XML minidom module to walk through the XML tree and return
233 a requested element.
234 """
235
236 def get_xml_tag_value(self, document, content):
237 # Send input to the parser.
238 xmldoc = xml.dom.minidom.parseString(document)
239
240 # Get XML elements by the given content.
241 element = xmldoc.getElementsByTagName(content)
242
243 # If no element has been found, we directly can return None.
244 if not element:
245 return None
246
247 # Only get the first child from an element, even there are more than one.
248 firstchild = element[0].firstChild
249
250 # Get the value of the child.
251 value = firstchild.nodeValue
252
253 # Return the value.
254 return value
255
256
3b16fdb1 257class DDNSProviderAllInkl(DDNSProvider):
6a11646e
MT
258 handle = "all-inkl.com"
259 name = "All-inkl.com"
260 website = "http://all-inkl.com/"
261 protocols = ("ipv4",)
3b16fdb1
SS
262
263 # There are only information provided by the vendor how to
264 # perform an update on a FRITZ Box. Grab requried informations
265 # from the net.
266 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
267
268 url = "http://dyndns.kasserver.com"
269
270 def update(self):
3b16fdb1
SS
271 # There is no additional data required so we directly can
272 # send our request.
536e87d1 273 response = self.send_request(self.url, username=self.username, password=self.password)
3b16fdb1
SS
274
275 # Get the full response message.
276 output = response.read()
277
278 # Handle success messages.
279 if output.startswith("good") or output.startswith("nochg"):
280 return
281
282 # If we got here, some other update error happened.
283 raise DDNSUpdateError
284
285
a892c594
MT
286class DDNSProviderBindNsupdate(DDNSProvider):
287 handle = "nsupdate"
288 name = "BIND nsupdate utility"
289 website = "http://en.wikipedia.org/wiki/Nsupdate"
290
291 DEFAULT_TTL = 60
292
293 def update(self):
294 scriptlet = self.__make_scriptlet()
295
296 # -v enables TCP hence we transfer keys and other data that may
297 # exceed the size of one packet.
298 # -t sets the timeout
299 command = ["nsupdate", "-v", "-t", "60"]
300
301 p = subprocess.Popen(command, shell=True,
302 stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
303 )
304 stdout, stderr = p.communicate(scriptlet)
305
306 if p.returncode == 0:
307 return
308
309 raise DDNSError("nsupdate terminated with error code: %s\n %s" % (p.returncode, stderr))
310
311 def __make_scriptlet(self):
312 scriptlet = []
313
314 # Set a different server the update is sent to.
315 server = self.get("server", None)
316 if server:
317 scriptlet.append("server %s" % server)
318
319 key = self.get("key", None)
320 if key:
321 secret = self.get("secret")
322
323 scriptlet.append("key %s %s" % (key, secret))
324
325 ttl = self.get("ttl", self.DEFAULT_TTL)
326
327 # Perform an update for each supported protocol.
328 for rrtype, proto in (("AAAA", "ipv6"), ("A", "ipv4")):
329 address = self.get_address(proto)
330 if not address:
331 continue
332
333 scriptlet.append("update delete %s. %s" % (self.hostname, rrtype))
334 scriptlet.append("update add %s. %s %s %s" % \
335 (self.hostname, ttl, rrtype, address))
336
337 # Send the actions to the server.
338 scriptlet.append("send")
339 scriptlet.append("quit")
340
341 logger.debug(_("Scriptlet:"))
342 for line in scriptlet:
343 # Masquerade the line with the secret key.
344 if line.startswith("key"):
345 line = "key **** ****"
346
347 logger.debug(" %s" % line)
348
349 return "\n".join(scriptlet)
350
351
f3cf1f70 352class DDNSProviderDHS(DDNSProvider):
6a11646e
MT
353 handle = "dhs.org"
354 name = "DHS International"
355 website = "http://dhs.org/"
356 protocols = ("ipv4",)
f3cf1f70
SS
357
358 # No information about the used update api provided on webpage,
359 # grabed from source code of ez-ipudate.
b2b05ef3 360
f3cf1f70
SS
361 url = "http://members.dhs.org/nic/hosts"
362
5f402f36 363 def update(self):
f3cf1f70
SS
364 data = {
365 "domain" : self.hostname,
366 "ip" : self.get_address("ipv4"),
367 "hostcmd" : "edit",
368 "hostcmdstage" : "2",
369 "type" : "4",
370 }
371
372 # Send update to the server.
175c9b80 373 response = self.send_request(self.url, username=self.username, password=self.password,
f3cf1f70
SS
374 data=data)
375
376 # Handle success messages.
377 if response.code == 200:
378 return
379
f3cf1f70
SS
380 # If we got here, some other update error happened.
381 raise DDNSUpdateError
382
383
39301272 384class DDNSProviderDNSpark(DDNSProvider):
6a11646e
MT
385 handle = "dnspark.com"
386 name = "DNS Park"
387 website = "http://dnspark.com/"
388 protocols = ("ipv4",)
39301272
SS
389
390 # Informations to the used api can be found here:
391 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
b2b05ef3 392
39301272
SS
393 url = "https://control.dnspark.com/api/dynamic/update.php"
394
5f402f36 395 def update(self):
39301272
SS
396 data = {
397 "domain" : self.hostname,
398 "ip" : self.get_address("ipv4"),
399 }
400
401 # Send update to the server.
175c9b80 402 response = self.send_request(self.url, username=self.username, password=self.password,
39301272
SS
403 data=data)
404
405 # Get the full response message.
406 output = response.read()
407
408 # Handle success messages.
409 if output.startswith("ok") or output.startswith("nochange"):
410 return
411
412 # Handle error codes.
413 if output == "unauth":
414 raise DDNSAuthenticationError
415 elif output == "abuse":
416 raise DDNSAbuseError
417 elif output == "blocked":
418 raise DDNSBlockedError
419 elif output == "nofqdn":
420 raise DDNSRequestError(_("No valid FQDN was given."))
421 elif output == "nohost":
422 raise DDNSRequestError(_("Invalid hostname specified."))
423 elif output == "notdyn":
424 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
425 elif output == "invalid":
426 raise DDNSRequestError(_("Invalid IP address has been sent."))
427
428 # If we got here, some other update error happened.
429 raise DDNSUpdateError
430
43b2cd59
SS
431
432class DDNSProviderDtDNS(DDNSProvider):
6a11646e
MT
433 handle = "dtdns.com"
434 name = "DtDNS"
435 website = "http://dtdns.com/"
436 protocols = ("ipv4",)
43b2cd59
SS
437
438 # Information about the format of the HTTPS request is to be found
439 # http://www.dtdns.com/dtsite/updatespec
b2b05ef3 440
43b2cd59
SS
441 url = "https://www.dtdns.com/api/autodns.cfm"
442
43b2cd59
SS
443 def update(self):
444 data = {
445 "ip" : self.get_address("ipv4"),
446 "id" : self.hostname,
447 "pw" : self.password
448 }
449
450 # Send update to the server.
451 response = self.send_request(self.url, data=data)
452
453 # Get the full response message.
454 output = response.read()
455
456 # Remove all leading and trailing whitespace.
457 output = output.strip()
458
459 # Handle success messages.
460 if "now points to" in output:
461 return
462
463 # Handle error codes.
464 if output == "No hostname to update was supplied.":
465 raise DDNSRequestError(_("No hostname specified."))
466
467 elif output == "The hostname you supplied is not valid.":
468 raise DDNSRequestError(_("Invalid hostname specified."))
469
470 elif output == "The password you supplied is not valid.":
471 raise DDNSAuthenticationError
472
473 elif output == "Administration has disabled this account.":
474 raise DDNSRequestError(_("Account has been disabled."))
475
476 elif output == "Illegal character in IP.":
477 raise DDNSRequestError(_("Invalid IP address has been sent."))
478
479 elif output == "Too many failed requests.":
480 raise DDNSRequestError(_("Too many failed requests."))
481
482 # If we got here, some other update error happened.
483 raise DDNSUpdateError
484
485
5d4bec40 486class DDNSProviderDynDNS(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
487 handle = "dyndns.org"
488 name = "Dyn"
489 website = "http://dyn.com/dns/"
490 protocols = ("ipv4",)
bfed6701
SS
491
492 # Information about the format of the request is to be found
493 # http://http://dyn.com/support/developers/api/perform-update/
494 # http://dyn.com/support/developers/api/return-codes/
b2b05ef3 495
bfed6701
SS
496 url = "https://members.dyndns.org/nic/update"
497
bfed6701 498
5d4bec40 499class DDNSProviderDynU(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
500 handle = "dynu.com"
501 name = "Dynu"
502 website = "http://dynu.com/"
503 protocols = ("ipv6", "ipv4",)
3a8407fa
SS
504
505 # Detailed information about the request and response codes
506 # are available on the providers webpage.
507 # http://dynu.com/Default.aspx?page=dnsapi
508
509 url = "https://api.dynu.com/nic/update"
510
511 def _prepare_request_data(self):
54d3efc8
MT
512 data = DDNSProviderDynDNS._prepare_request_data(self)
513
514 # This one supports IPv6
515 data.update({
3a8407fa 516 "myipv6" : self.get_address("ipv6"),
54d3efc8
MT
517 })
518
519 return data
3a8407fa
SS
520
521
5d4bec40
MT
522class DDNSProviderEasyDNS(DDNSProtocolDynDNS2, DDNSProvider):
523 handle = "easydns.com"
524 name = "EasyDNS"
525 website = "http://www.easydns.com/"
526 protocols = ("ipv4",)
ee071271
SS
527
528 # There is only some basic documentation provided by the vendor,
529 # also searching the web gain very poor results.
530 # http://mediawiki.easydns.com/index.php/Dynamic_DNS
531
532 url = "http://api.cp.easydns.com/dyn/tomato.php"
533
534
35216523
SS
535class DDNSProviderEnomCom(DDNSResponseParserXML, DDNSProvider):
536 handle = "enom.com"
537 name = "eNom Inc."
538 website = "http://www.enom.com/"
539
540 # There are very detailed information about how to send an update request and
541 # the respone codes.
542 # http://www.enom.com/APICommandCatalog/
543
544 url = "https://dynamic.name-services.com/interface.asp"
545
546 def update(self):
547 data = {
548 "command" : "setdnshost",
549 "responsetype" : "xml",
550 "address" : self.get_address("ipv4"),
551 "domainpassword" : self.password,
552 "zone" : self.hostname
553 }
554
555 # Send update to the server.
556 response = self.send_request(self.url, data=data)
557
558 # Get the full response message.
559 output = response.read()
560
561 # Handle success messages.
562 if self.get_xml_tag_value(output, "ErrCount") == "0":
563 return
564
565 # Handle error codes.
566 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
567
568 if errorcode == "304155":
569 raise DDNSAuthenticationError
570 elif errorcode == "304153":
571 raise DDNSRequestError(_("Domain not found."))
572
573 # If we got here, some other update error happened.
574 raise DDNSUpdateError
575
576
aa21a4c6 577class DDNSProviderFreeDNSAfraidOrg(DDNSProvider):
6a11646e
MT
578 handle = "freedns.afraid.org"
579 name = "freedns.afraid.org"
580 website = "http://freedns.afraid.org/"
aa21a4c6
SS
581
582 # No information about the request or response could be found on the vendor
583 # page. All used values have been collected by testing.
584 url = "https://freedns.afraid.org/dynamic/update.php"
585
586 @property
587 def proto(self):
588 return self.get("proto")
589
590 def update(self):
591 address = self.get_address(self.proto)
592
593 data = {
594 "address" : address,
595 }
596
597 # Add auth token to the update url.
598 url = "%s?%s" % (self.url, self.token)
599
600 # Send update to the server.
601 response = self.send_request(url, data=data)
602
a204b107
SS
603 # Get the full response message.
604 output = response.read()
605
606 # Handle success messages.
aa21a4c6
SS
607 if output.startswith("Updated") or "has not changed" in output:
608 return
609
610 # Handle error codes.
611 if output == "ERROR: Unable to locate this record":
612 raise DDNSAuthenticationError
613 elif "is an invalid IP address" in output:
614 raise DDNSRequestError(_("Invalid IP address has been sent."))
615
3b524cf2
SS
616 # If we got here, some other update error happened.
617 raise DDNSUpdateError
618
aa21a4c6 619
a08c1b72 620class DDNSProviderLightningWireLabs(DDNSProvider):
6a11646e 621 handle = "dns.lightningwirelabs.com"
fb115fdc 622 name = "Lightning Wire Labs DNS Service"
6a11646e 623 website = "http://dns.lightningwirelabs.com/"
a08c1b72
SS
624
625 # Information about the format of the HTTPS request is to be found
626 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
b2b05ef3 627
a08c1b72
SS
628 url = "https://dns.lightningwirelabs.com/update"
629
5f402f36 630 def update(self):
a08c1b72
SS
631 data = {
632 "hostname" : self.hostname,
e3c70807
MT
633 "address6" : self.get_address("ipv6", "-"),
634 "address4" : self.get_address("ipv4", "-"),
a08c1b72
SS
635 }
636
a08c1b72
SS
637 # Check if a token has been set.
638 if self.token:
639 data["token"] = self.token
640
641 # Check for username and password.
642 elif self.username and self.password:
643 data.update({
644 "username" : self.username,
645 "password" : self.password,
646 })
647
648 # Raise an error if no auth details are given.
649 else:
650 raise DDNSConfigurationError
651
652 # Send update to the server.
cb455540 653 response = self.send_request(self.url, data=data)
a08c1b72
SS
654
655 # Handle success messages.
656 if response.code == 200:
657 return
658
a08c1b72
SS
659 # If we got here, some other update error happened.
660 raise DDNSUpdateError
661
662
78c9780b 663class DDNSProviderNamecheap(DDNSResponseParserXML, DDNSProvider):
6a11646e
MT
664 handle = "namecheap.com"
665 name = "Namecheap"
666 website = "http://namecheap.com"
667 protocols = ("ipv4",)
d1cd57eb
SS
668
669 # Information about the format of the HTTP request is to be found
670 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
671 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
672
673 url = "https://dynamicdns.park-your-domain.com/update"
674
d1cd57eb
SS
675 def update(self):
676 # Namecheap requires the hostname splitted into a host and domain part.
677 host, domain = self.hostname.split(".", 1)
678
679 data = {
680 "ip" : self.get_address("ipv4"),
681 "password" : self.password,
682 "host" : host,
683 "domain" : domain
684 }
685
686 # Send update to the server.
687 response = self.send_request(self.url, data=data)
688
689 # Get the full response message.
690 output = response.read()
691
692 # Handle success messages.
78c9780b 693 if self.get_xml_tag_value(output, "IP") == self.get_address("ipv4"):
d1cd57eb
SS
694 return
695
696 # Handle error codes.
78c9780b 697 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
d1cd57eb
SS
698
699 if errorcode == "304156":
700 raise DDNSAuthenticationError
701 elif errorcode == "316153":
702 raise DDNSRequestError(_("Domain not found."))
703 elif errorcode == "316154":
704 raise DDNSRequestError(_("Domain not active."))
705 elif errorcode in ("380098", "380099"):
706 raise DDNSInternalServerError
707
708 # If we got here, some other update error happened.
709 raise DDNSUpdateError
710
711
5d4bec40
MT
712class DDNSProviderNOIP(DDNSProtocolDynDNS2, DDNSProvider):
713 handle = "no-ip.com"
714 name = "No-IP"
715 website = "http://www.no-ip.com/"
716 protocols = ("ipv4",)
f22ab085
MT
717
718 # Information about the format of the HTTP request is to be found
719 # here: http://www.no-ip.com/integrate/request and
720 # here: http://www.no-ip.com/integrate/response
721
88f39629 722 url = "http://dynupdate.no-ip.com/nic/update"
2de06f59 723
88f39629 724 def _prepare_request_data(self):
2de06f59
MT
725 data = {
726 "hostname" : self.hostname,
f22ab085
MT
727 "address" : self.get_address("ipv4"),
728 }
729
88f39629 730 return data
f22ab085
MT
731
732
31c95e4b
SS
733class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2, DDNSProvider):
734 handle = "nsupdate.info"
735 name = "nsupdate.info"
736 website = "http://www.nsupdate.info/"
737 protocols = ("ipv6", "ipv4",)
738
739 # Information about the format of the HTTP request can be found
740 # after login on the provider user intrface and here:
741 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
742
743 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
744 # and for the password a so called secret.
745 @property
746 def username(self):
747 return self.get("hostname")
748
749 @property
750 def password(self):
751 return self.get("secret")
752
753 @property
754 def proto(self):
755 return self.get("proto")
756
757 @property
758 def url(self):
759 # The update URL is different by the used protocol.
760 if self.proto == "ipv4":
761 return "https://ipv4.nsupdate.info/nic/update"
762 elif self.proto == "ipv6":
763 return "https://ipv6.nsupdate.info/nic/update"
764 else:
765 raise DDNSUpdateError(_("Invalid protocol has been given"))
766
767 def _prepare_request_data(self):
768 data = {
769 "myip" : self.get_address(self.proto),
770 }
771
772 return data
773
774
90663439
SS
775class DDNSProviderOpenDNS(DDNSProtocolDynDNS2, DDNSProvider):
776 handle = "opendns.com"
777 name = "OpenDNS"
778 website = "http://www.opendns.com"
779
780 # Detailed information about the update request and possible
781 # response codes can be obtained from here:
782 # https://support.opendns.com/entries/23891440
783
784 url = "https://updates.opendns.com/nic/update"
785
786 @property
787 def proto(self):
788 return self.get("proto")
789
790 def _prepare_request_data(self):
791 data = {
792 "hostname" : self.hostname,
793 "myip" : self.get_address(self.proto)
794 }
795
796 return data
797
798
5d4bec40
MT
799class DDNSProviderOVH(DDNSProtocolDynDNS2, DDNSProvider):
800 handle = "ovh.com"
801 name = "OVH"
802 website = "http://www.ovh.com/"
803 protocols = ("ipv4",)
a508bda6
SS
804
805 # OVH only provides very limited information about how to
806 # update a DynDNS host. They only provide the update url
807 # on the their german subpage.
808 #
809 # http://hilfe.ovh.de/DomainDynHost
810
811 url = "https://www.ovh.com/nic/update"
812
813 def _prepare_request_data(self):
5d4bec40 814 data = DDNSProtocolDynDNS2._prepare_request_data(self)
54d3efc8
MT
815 data.update({
816 "system" : "dyndns",
817 })
818
819 return data
a508bda6
SS
820
821
ef33455e 822class DDNSProviderRegfish(DDNSProvider):
6a11646e
MT
823 handle = "regfish.com"
824 name = "Regfish GmbH"
825 website = "http://www.regfish.com/"
ef33455e
SS
826
827 # A full documentation to the providers api can be found here
828 # but is only available in german.
829 # https://www.regfish.de/domains/dyndns/dokumentation
830
831 url = "https://dyndns.regfish.de/"
832
833 def update(self):
834 data = {
835 "fqdn" : self.hostname,
836 }
837
838 # Check if we update an IPv6 address.
839 address6 = self.get_address("ipv6")
840 if address6:
841 data["ipv6"] = address6
842
843 # Check if we update an IPv4 address.
844 address4 = self.get_address("ipv4")
845 if address4:
846 data["ipv4"] = address4
847
848 # Raise an error if none address is given.
849 if not data.has_key("ipv6") and not data.has_key("ipv4"):
850 raise DDNSConfigurationError
851
852 # Check if a token has been set.
853 if self.token:
854 data["token"] = self.token
855
856 # Raise an error if no token and no useranem and password
857 # are given.
858 elif not self.username and not self.password:
859 raise DDNSConfigurationError(_("No Auth details specified."))
860
861 # HTTP Basic Auth is only allowed if no token is used.
862 if self.token:
863 # Send update to the server.
864 response = self.send_request(self.url, data=data)
865 else:
866 # Send update to the server.
867 response = self.send_request(self.url, username=self.username, password=self.password,
868 data=data)
869
870 # Get the full response message.
871 output = response.read()
872
873 # Handle success messages.
874 if "100" in output or "101" in output:
875 return
876
877 # Handle error codes.
878 if "401" or "402" in output:
879 raise DDNSAuthenticationError
880 elif "408" in output:
881 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
882 elif "409" in output:
883 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
884 elif "412" in output:
885 raise DDNSRequestError(_("No valid FQDN was given."))
886 elif "414" in output:
887 raise DDNSInternalServerError
888
889 # If we got here, some other update error happened.
890 raise DDNSUpdateError
891
892
5d4bec40 893class DDNSProviderSelfhost(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
894 handle = "selfhost.de"
895 name = "Selfhost.de"
896 website = "http://www.selfhost.de/"
5d4bec40 897 protocols = ("ipv4",)
f22ab085 898
04db1862 899 url = "https://carol.selfhost.de/nic/update"
f22ab085 900
04db1862
MT
901 def _prepare_request_data(self):
902 data = DDNSProviderDynDNS._prepare_request_data(self)
903 data.update({
904 "hostname" : "1",
905 })
f22ab085 906
04db1862 907 return data
b09b1545
SS
908
909
5d4bec40
MT
910class DDNSProviderSPDNS(DDNSProtocolDynDNS2, DDNSProvider):
911 handle = "spdns.org"
912 name = "SPDNS"
913 website = "http://spdns.org/"
914 protocols = ("ipv4",)
b09b1545
SS
915
916 # Detailed information about request and response codes are provided
917 # by the vendor. They are using almost the same mechanism and status
918 # codes as dyndns.org so we can inherit all those stuff.
919 #
920 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
921 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
922
923 url = "https://update.spdns.de/nic/update"
4ec90b93
MT
924
925
5d4bec40
MT
926class DDNSProviderStrato(DDNSProtocolDynDNS2, DDNSProvider):
927 handle = "strato.com"
928 name = "Strato AG"
929 website = "http:/www.strato.com/"
930 protocols = ("ipv4",)
7488825c
SS
931
932 # Information about the request and response can be obtained here:
933 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
934
935 url = "https://dyndns.strato.com/nic/update"
936
937
5d4bec40
MT
938class DDNSProviderTwoDNS(DDNSProtocolDynDNS2, DDNSProvider):
939 handle = "twodns.de"
940 name = "TwoDNS"
941 website = "http://www.twodns.de"
942 protocols = ("ipv4",)
a6183090
SS
943
944 # Detailed information about the request can be found here
945 # http://twodns.de/en/faqs
946 # http://twodns.de/en/api
947
948 url = "https://update.twodns.de/update"
949
950 def _prepare_request_data(self):
951 data = {
952 "ip" : self.get_address("ipv4"),
953 "hostname" : self.hostname
954 }
955
956 return data
957
958
5d4bec40
MT
959class DDNSProviderUdmedia(DDNSProtocolDynDNS2, DDNSProvider):
960 handle = "udmedia.de"
961 name = "Udmedia GmbH"
962 website = "http://www.udmedia.de"
963 protocols = ("ipv4",)
03bdd188
SS
964
965 # Information about the request can be found here
966 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
967
968 url = "https://www.udmedia.de/nic/update"
969
970
5d4bec40 971class DDNSProviderVariomedia(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
972 handle = "variomedia.de"
973 name = "Variomedia"
974 website = "http://www.variomedia.de/"
975 protocols = ("ipv6", "ipv4",)
c8c7ca8f
SS
976
977 # Detailed information about the request can be found here
978 # https://dyndns.variomedia.de/
979
980 url = "https://dyndns.variomedia.de/nic/update"
981
982 @property
983 def proto(self):
984 return self.get("proto")
985
986 def _prepare_request_data(self):
987 data = {
988 "hostname" : self.hostname,
989 "myip" : self.get_address(self.proto)
990 }
54d3efc8
MT
991
992 return data
98fbe467
SS
993
994
5d4bec40
MT
995class DDNSProviderZoneedit(DDNSProtocolDynDNS2, DDNSProvider):
996 handle = "zoneedit.com"
997 name = "Zoneedit"
998 website = "http://www.zoneedit.com"
999 protocols = ("ipv4",)
98fbe467
SS
1000
1001 # Detailed information about the request and the response codes can be
1002 # obtained here:
1003 # http://www.zoneedit.com/doc/api/other.html
1004 # http://www.zoneedit.com/faq.html
1005
1006 url = "https://dynamic.zoneedit.com/auth/dynamic.html"
1007
1008 @property
1009 def proto(self):
1010 return self.get("proto")
1011
1012 def update(self):
1013 data = {
1014 "dnsto" : self.get_address(self.proto),
1015 "host" : self.hostname
1016 }
1017
1018 # Send update to the server.
1019 response = self.send_request(self.url, username=self.username, password=self.password,
1020 data=data)
1021
1022 # Get the full response message.
1023 output = response.read()
1024
1025 # Handle success messages.
1026 if output.startswith("<SUCCESS"):
1027 return
1028
1029 # Handle error codes.
1030 if output.startswith("invalid login"):
1031 raise DDNSAuthenticationError
1032 elif output.startswith("<ERROR CODE=\"704\""):
1033 raise DDNSRequestError(_("No valid FQDN was given."))
1034 elif output.startswith("<ERROR CODE=\"702\""):
1035 raise DDNSInternalServerError
1036
1037 # If we got here, some other update error happened.
1038 raise DDNSUpdateError