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