]> git.ipfire.org Git - ddns.git/blame - src/ddns/providers.py
Add nsupdate.info 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
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
31c95e4b
SS
678class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2, DDNSProvider):
679 handle = "nsupdate.info"
680 name = "nsupdate.info"
681 website = "http://www.nsupdate.info/"
682 protocols = ("ipv6", "ipv4",)
683
684 # Information about the format of the HTTP request can be found
685 # after login on the provider user intrface and here:
686 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
687
688 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
689 # and for the password a so called secret.
690 @property
691 def username(self):
692 return self.get("hostname")
693
694 @property
695 def password(self):
696 return self.get("secret")
697
698 @property
699 def proto(self):
700 return self.get("proto")
701
702 @property
703 def url(self):
704 # The update URL is different by the used protocol.
705 if self.proto == "ipv4":
706 return "https://ipv4.nsupdate.info/nic/update"
707 elif self.proto == "ipv6":
708 return "https://ipv6.nsupdate.info/nic/update"
709 else:
710 raise DDNSUpdateError(_("Invalid protocol has been given"))
711
712 def _prepare_request_data(self):
713 data = {
714 "myip" : self.get_address(self.proto),
715 }
716
717 return data
718
719
5d4bec40
MT
720class DDNSProviderOVH(DDNSProtocolDynDNS2, DDNSProvider):
721 handle = "ovh.com"
722 name = "OVH"
723 website = "http://www.ovh.com/"
724 protocols = ("ipv4",)
a508bda6
SS
725
726 # OVH only provides very limited information about how to
727 # update a DynDNS host. They only provide the update url
728 # on the their german subpage.
729 #
730 # http://hilfe.ovh.de/DomainDynHost
731
732 url = "https://www.ovh.com/nic/update"
733
734 def _prepare_request_data(self):
5d4bec40 735 data = DDNSProtocolDynDNS2._prepare_request_data(self)
54d3efc8
MT
736 data.update({
737 "system" : "dyndns",
738 })
739
740 return data
a508bda6
SS
741
742
ef33455e 743class DDNSProviderRegfish(DDNSProvider):
6a11646e
MT
744 handle = "regfish.com"
745 name = "Regfish GmbH"
746 website = "http://www.regfish.com/"
ef33455e
SS
747
748 # A full documentation to the providers api can be found here
749 # but is only available in german.
750 # https://www.regfish.de/domains/dyndns/dokumentation
751
752 url = "https://dyndns.regfish.de/"
753
754 def update(self):
755 data = {
756 "fqdn" : self.hostname,
757 }
758
759 # Check if we update an IPv6 address.
760 address6 = self.get_address("ipv6")
761 if address6:
762 data["ipv6"] = address6
763
764 # Check if we update an IPv4 address.
765 address4 = self.get_address("ipv4")
766 if address4:
767 data["ipv4"] = address4
768
769 # Raise an error if none address is given.
770 if not data.has_key("ipv6") and not data.has_key("ipv4"):
771 raise DDNSConfigurationError
772
773 # Check if a token has been set.
774 if self.token:
775 data["token"] = self.token
776
777 # Raise an error if no token and no useranem and password
778 # are given.
779 elif not self.username and not self.password:
780 raise DDNSConfigurationError(_("No Auth details specified."))
781
782 # HTTP Basic Auth is only allowed if no token is used.
783 if self.token:
784 # Send update to the server.
785 response = self.send_request(self.url, data=data)
786 else:
787 # Send update to the server.
788 response = self.send_request(self.url, username=self.username, password=self.password,
789 data=data)
790
791 # Get the full response message.
792 output = response.read()
793
794 # Handle success messages.
795 if "100" in output or "101" in output:
796 return
797
798 # Handle error codes.
799 if "401" or "402" in output:
800 raise DDNSAuthenticationError
801 elif "408" in output:
802 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
803 elif "409" in output:
804 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
805 elif "412" in output:
806 raise DDNSRequestError(_("No valid FQDN was given."))
807 elif "414" in output:
808 raise DDNSInternalServerError
809
810 # If we got here, some other update error happened.
811 raise DDNSUpdateError
812
813
5d4bec40 814class DDNSProviderSelfhost(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
815 handle = "selfhost.de"
816 name = "Selfhost.de"
817 website = "http://www.selfhost.de/"
5d4bec40 818 protocols = ("ipv4",)
f22ab085 819
04db1862 820 url = "https://carol.selfhost.de/nic/update"
f22ab085 821
04db1862
MT
822 def _prepare_request_data(self):
823 data = DDNSProviderDynDNS._prepare_request_data(self)
824 data.update({
825 "hostname" : "1",
826 })
f22ab085 827
04db1862 828 return data
b09b1545
SS
829
830
5d4bec40
MT
831class DDNSProviderSPDNS(DDNSProtocolDynDNS2, DDNSProvider):
832 handle = "spdns.org"
833 name = "SPDNS"
834 website = "http://spdns.org/"
835 protocols = ("ipv4",)
b09b1545
SS
836
837 # Detailed information about request and response codes are provided
838 # by the vendor. They are using almost the same mechanism and status
839 # codes as dyndns.org so we can inherit all those stuff.
840 #
841 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
842 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
843
844 url = "https://update.spdns.de/nic/update"
4ec90b93
MT
845
846
5d4bec40
MT
847class DDNSProviderStrato(DDNSProtocolDynDNS2, DDNSProvider):
848 handle = "strato.com"
849 name = "Strato AG"
850 website = "http:/www.strato.com/"
851 protocols = ("ipv4",)
7488825c
SS
852
853 # Information about the request and response can be obtained here:
854 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
855
856 url = "https://dyndns.strato.com/nic/update"
857
858
5d4bec40
MT
859class DDNSProviderTwoDNS(DDNSProtocolDynDNS2, DDNSProvider):
860 handle = "twodns.de"
861 name = "TwoDNS"
862 website = "http://www.twodns.de"
863 protocols = ("ipv4",)
a6183090
SS
864
865 # Detailed information about the request can be found here
866 # http://twodns.de/en/faqs
867 # http://twodns.de/en/api
868
869 url = "https://update.twodns.de/update"
870
871 def _prepare_request_data(self):
872 data = {
873 "ip" : self.get_address("ipv4"),
874 "hostname" : self.hostname
875 }
876
877 return data
878
879
5d4bec40
MT
880class DDNSProviderUdmedia(DDNSProtocolDynDNS2, DDNSProvider):
881 handle = "udmedia.de"
882 name = "Udmedia GmbH"
883 website = "http://www.udmedia.de"
884 protocols = ("ipv4",)
03bdd188
SS
885
886 # Information about the request can be found here
887 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
888
889 url = "https://www.udmedia.de/nic/update"
890
891
5d4bec40 892class DDNSProviderVariomedia(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
893 handle = "variomedia.de"
894 name = "Variomedia"
895 website = "http://www.variomedia.de/"
896 protocols = ("ipv6", "ipv4",)
c8c7ca8f
SS
897
898 # Detailed information about the request can be found here
899 # https://dyndns.variomedia.de/
900
901 url = "https://dyndns.variomedia.de/nic/update"
902
903 @property
904 def proto(self):
905 return self.get("proto")
906
907 def _prepare_request_data(self):
908 data = {
909 "hostname" : self.hostname,
910 "myip" : self.get_address(self.proto)
911 }
54d3efc8
MT
912
913 return data
98fbe467
SS
914
915
5d4bec40
MT
916class DDNSProviderZoneedit(DDNSProtocolDynDNS2, DDNSProvider):
917 handle = "zoneedit.com"
918 name = "Zoneedit"
919 website = "http://www.zoneedit.com"
920 protocols = ("ipv4",)
98fbe467
SS
921
922 # Detailed information about the request and the response codes can be
923 # obtained here:
924 # http://www.zoneedit.com/doc/api/other.html
925 # http://www.zoneedit.com/faq.html
926
927 url = "https://dynamic.zoneedit.com/auth/dynamic.html"
928
929 @property
930 def proto(self):
931 return self.get("proto")
932
933 def update(self):
934 data = {
935 "dnsto" : self.get_address(self.proto),
936 "host" : self.hostname
937 }
938
939 # Send update to the server.
940 response = self.send_request(self.url, username=self.username, password=self.password,
941 data=data)
942
943 # Get the full response message.
944 output = response.read()
945
946 # Handle success messages.
947 if output.startswith("<SUCCESS"):
948 return
949
950 # Handle error codes.
951 if output.startswith("invalid login"):
952 raise DDNSAuthenticationError
953 elif output.startswith("<ERROR CODE=\"704\""):
954 raise DDNSRequestError(_("No valid FQDN was given."))
955 elif output.startswith("<ERROR CODE=\"702\""):
956 raise DDNSInternalServerError
957
958 # If we got here, some other update error happened.
959 raise DDNSUpdateError