]> git.ipfire.org Git - oddments/ddns.git/blame - src/ddns/providers.py
Merge branch 'zoneedit-10645'
[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
37e24fbf 22import datetime
7399fc5b 23import logging
64d3fad4 24import os
a892c594 25import subprocess
3b16fdb1 26import urllib2
d1cd57eb 27import xml.dom.minidom
7399fc5b
MT
28
29from i18n import _
30
f22ab085
MT
31# Import all possible exception types.
32from .errors import *
33
7399fc5b
MT
34logger = logging.getLogger("ddns.providers")
35logger.propagate = 1
36
adfe6272
MT
37_providers = {}
38
39def get():
40 """
41 Returns a dict with all automatically registered providers.
42 """
43 return _providers.copy()
44
f22ab085 45class DDNSProvider(object):
6a11646e
MT
46 # A short string that uniquely identifies
47 # this provider.
48 handle = None
f22ab085 49
6a11646e
MT
50 # The full name of the provider.
51 name = None
f22ab085 52
6a11646e
MT
53 # A weburl to the homepage of the provider.
54 # (Where to register a new account?)
55 website = None
f22ab085 56
6a11646e
MT
57 # A list of supported protocols.
58 protocols = ("ipv6", "ipv4")
f22ab085
MT
59
60 DEFAULT_SETTINGS = {}
61
37e24fbf
MT
62 # holdoff time - Number of days no update is performed unless
63 # the IP address has changed.
64 holdoff_days = 30
65
112d3fb8
MT
66 # holdoff time for update failures - Number of days no update
67 # is tried after the last one has failed.
68 holdoff_failure_days = 0.5
69
29c8c9c6
MT
70 # True if the provider is able to remove records, too.
71 # Required to remove AAAA records if IPv6 is absent again.
72 can_remove_records = True
73
adfe6272
MT
74 # Automatically register all providers.
75 class __metaclass__(type):
76 def __init__(provider, name, bases, dict):
77 type.__init__(provider, name, bases, dict)
78
79 # The main class from which is inherited is not registered
80 # as a provider.
81 if name == "DDNSProvider":
82 return
83
84 if not all((provider.handle, provider.name, provider.website)):
85 raise DDNSError(_("Provider is not properly configured"))
86
87 assert not _providers.has_key(provider.handle), \
88 "Provider '%s' has already been registered" % provider.handle
89
90 _providers[provider.handle] = provider
91
64d3fad4
MT
92 @staticmethod
93 def supported():
94 """
95 Should be overwritten to check if the system the code is running
96 on has all the required tools to support this provider.
97 """
98 return True
99
f22ab085
MT
100 def __init__(self, core, **settings):
101 self.core = core
102
103 # Copy a set of default settings and
104 # update them by those from the configuration file.
105 self.settings = self.DEFAULT_SETTINGS.copy()
106 self.settings.update(settings)
107
108 def __repr__(self):
109 return "<DDNS Provider %s (%s)>" % (self.name, self.handle)
110
111 def __cmp__(self, other):
112 return cmp(self.hostname, other.hostname)
113
37e24fbf
MT
114 @property
115 def db(self):
116 return self.core.db
117
f22ab085
MT
118 def get(self, key, default=None):
119 """
120 Get a setting from the settings dictionary.
121 """
122 return self.settings.get(key, default)
123
124 @property
125 def hostname(self):
126 """
127 Fast access to the hostname.
128 """
129 return self.get("hostname")
130
131 @property
132 def username(self):
133 """
134 Fast access to the username.
135 """
136 return self.get("username")
137
138 @property
139 def password(self):
140 """
141 Fast access to the password.
142 """
143 return self.get("password")
144
46687828
SS
145 @property
146 def token(self):
147 """
148 Fast access to the token.
149 """
150 return self.get("token")
151
9da3e685
MT
152 def __call__(self, force=False):
153 if force:
c3888f15 154 logger.debug(_("Updating %s forced") % self.hostname)
9da3e685 155
112d3fb8
MT
156 # Do nothing if the last update has failed or no update is required
157 elif self.has_failure or not self.requires_update:
7399fc5b
MT
158 return
159
160 # Execute the update.
37e24fbf
MT
161 try:
162 self.update()
163
164 # In case of any errors, log the failed request and
165 # raise the exception.
166 except DDNSError as e:
167 self.core.db.log_failure(self.hostname, e)
168 raise
5f402f36 169
12b3818b
MT
170 logger.info(_("Dynamic DNS update for %(hostname)s (%(provider)s) successful") % \
171 { "hostname" : self.hostname, "provider" : self.name })
37e24fbf 172 self.core.db.log_success(self.hostname)
12b3818b 173
5f402f36 174 def update(self):
d45139f6
MT
175 for protocol in self.protocols:
176 if self.have_address(protocol):
177 self.update_protocol(protocol)
29c8c9c6 178 elif self.can_remove_records:
d45139f6
MT
179 self.remove_protocol(protocol)
180
181 def update_protocol(self, proto):
f22ab085
MT
182 raise NotImplementedError
183
d45139f6 184 def remove_protocol(self, proto):
29c8c9c6
MT
185 if not self.can_remove_records:
186 raise RuntimeError, "can_remove_records is enabled, but remove_protocol() not implemented"
d45139f6 187
29c8c9c6 188 raise NotImplementedError
d45139f6 189
37e24fbf
MT
190 @property
191 def requires_update(self):
192 # If the IP addresses have changed, an update is required
193 if self.ip_address_changed(self.protocols):
194 logger.debug(_("An update for %(hostname)s (%(provider)s)"
195 " is performed because of an IP address change") % \
196 { "hostname" : self.hostname, "provider" : self.name })
197
198 return True
199
200 # If the holdoff time has expired, an update is required, too
201 if self.holdoff_time_expired():
202 logger.debug(_("An update for %(hostname)s (%(provider)s)"
203 " is performed because the holdoff time has expired") % \
204 { "hostname" : self.hostname, "provider" : self.name })
205
206 return True
207
208 # Otherwise, we don't need to perform an update
209 logger.debug(_("No update required for %(hostname)s (%(provider)s)") % \
210 { "hostname" : self.hostname, "provider" : self.name })
211
212 return False
213
112d3fb8
MT
214 @property
215 def has_failure(self):
216 """
217 Returns True when the last update has failed and no retry
218 should be performed, yet.
219 """
220 last_status = self.db.last_update_status(self.hostname)
221
222 # Return False if the last update has not failed.
223 if not last_status == "failure":
224 return False
225
64018439
MT
226 # If there is no holdoff time, we won't update ever again.
227 if self.holdoff_failure_days is None:
228 logger.warning(_("An update has not been performed because earlier updates failed for %s") \
229 % self.hostname)
230 logger.warning(_("There will be no retries"))
231
232 return True
233
112d3fb8
MT
234 # Determine when the holdoff time ends
235 last_update = self.db.last_update(self.hostname, status=last_status)
236 holdoff_end = last_update + datetime.timedelta(days=self.holdoff_failure_days)
237
238 now = datetime.datetime.utcnow()
239 if now < holdoff_end:
240 failure_message = self.db.last_update_failure_message(self.hostname)
241
242 logger.warning(_("An update has not been performed because earlier updates failed for %s") \
243 % self.hostname)
244
245 if failure_message:
246 logger.warning(_("Last failure message:"))
247
248 for line in failure_message.splitlines():
249 logger.warning(" %s" % line)
250
251 logger.warning(_("Further updates will be withheld until %s") % holdoff_end)
252
253 return True
254
255 return False
256
37e24fbf 257 def ip_address_changed(self, protos):
7399fc5b
MT
258 """
259 Returns True if this host is already up to date
260 and does not need to change the IP address on the
261 name server.
262 """
263 for proto in protos:
264 addresses = self.core.system.resolve(self.hostname, proto)
7399fc5b
MT
265 current_address = self.get_address(proto)
266
29c8c9c6
MT
267 # Handle if the system has not got any IP address from a protocol
268 # (i.e. had full dual-stack connectivity which it has not any more)
269 if current_address is None:
270 # If addresses still exists in the DNS system and if this provider
271 # is able to remove records, we will do that.
272 if addresses and self.can_remove_records:
273 return True
274
275 # Otherwise, we cannot go on...
38d81db4
MT
276 continue
277
7399fc5b 278 if not current_address in addresses:
37e24fbf
MT
279 return True
280
281 return False
7399fc5b 282
37e24fbf
MT
283 def holdoff_time_expired(self):
284 """
285 Returns true if the holdoff time has expired
286 and the host requires an update
287 """
288 # If no holdoff days is defined, we cannot go on
289 if not self.holdoff_days:
290 return False
291
292 # Get the timestamp of the last successfull update
112d3fb8 293 last_update = self.db.last_update(self.hostname, status="success")
37e24fbf
MT
294
295 # If no timestamp has been recorded, no update has been
296 # performed. An update should be performed now.
297 if not last_update:
298 return True
7399fc5b 299
37e24fbf
MT
300 # Determine when the holdoff time ends
301 holdoff_end = last_update + datetime.timedelta(days=self.holdoff_days)
302
303 now = datetime.datetime.utcnow()
304
305 if now >= holdoff_end:
306 logger.debug("The holdoff time has expired for %s" % self.hostname)
307 return True
308 else:
309 logger.debug("Updates for %s are held off until %s" % \
310 (self.hostname, holdoff_end))
311 return False
7399fc5b 312
f22ab085
MT
313 def send_request(self, *args, **kwargs):
314 """
315 Proxy connection to the send request
316 method.
317 """
318 return self.core.system.send_request(*args, **kwargs)
319
e3c70807 320 def get_address(self, proto, default=None):
f22ab085
MT
321 """
322 Proxy method to get the current IP address.
323 """
e3c70807 324 return self.core.system.get_address(proto) or default
f22ab085 325
d45139f6
MT
326 def have_address(self, proto):
327 """
328 Returns True if an IP address for the given protocol
329 is known and usable.
330 """
331 address = self.get_address(proto)
332
333 if address:
334 return True
335
336 return False
337
f22ab085 338
5d4bec40
MT
339class DDNSProtocolDynDNS2(object):
340 """
341 This is an abstract class that implements the DynDNS updater
342 protocol version 2. As this is a popular way to update dynamic
343 DNS records, this class is supposed make the provider classes
344 shorter and simpler.
345 """
346
347 # Information about the format of the request is to be found
486c1b9d 348 # http://dyn.com/support/developers/api/perform-update/
5d4bec40
MT
349 # http://dyn.com/support/developers/api/return-codes/
350
29c8c9c6
MT
351 # The DynDNS protocol version 2 does not allow to remove records
352 can_remove_records = False
353
d45139f6 354 def prepare_request_data(self, proto):
5d4bec40
MT
355 data = {
356 "hostname" : self.hostname,
d45139f6 357 "myip" : self.get_address(proto),
5d4bec40
MT
358 }
359
360 return data
361
d45139f6
MT
362 def update_protocol(self, proto):
363 data = self.prepare_request_data(proto)
364
365 return self.send_request(data)
5d4bec40 366
d45139f6 367 def send_request(self, data):
5d4bec40 368 # Send update to the server.
d45139f6 369 response = DDNSProvider.send_request(self, self.url, data=data,
5d4bec40
MT
370 username=self.username, password=self.password)
371
372 # Get the full response message.
373 output = response.read()
374
375 # Handle success messages.
376 if output.startswith("good") or output.startswith("nochg"):
377 return
378
379 # Handle error codes.
380 if output == "badauth":
381 raise DDNSAuthenticationError
af97e369 382 elif output == "abuse":
5d4bec40
MT
383 raise DDNSAbuseError
384 elif output == "notfqdn":
385 raise DDNSRequestError(_("No valid FQDN was given."))
386 elif output == "nohost":
387 raise DDNSRequestError(_("Specified host does not exist."))
388 elif output == "911":
389 raise DDNSInternalServerError
390 elif output == "dnserr":
391 raise DDNSInternalServerError(_("DNS error encountered."))
6ddfd5c7
SS
392 elif output == "badagent":
393 raise DDNSBlockedError
5d4bec40
MT
394
395 # If we got here, some other update error happened.
396 raise DDNSUpdateError(_("Server response: %s") % output)
397
398
78c9780b
SS
399class DDNSResponseParserXML(object):
400 """
401 This class provides a parser for XML responses which
402 will be sent by various providers. This class uses the python
403 shipped XML minidom module to walk through the XML tree and return
404 a requested element.
405 """
406
407 def get_xml_tag_value(self, document, content):
408 # Send input to the parser.
409 xmldoc = xml.dom.minidom.parseString(document)
410
411 # Get XML elements by the given content.
412 element = xmldoc.getElementsByTagName(content)
413
414 # If no element has been found, we directly can return None.
415 if not element:
416 return None
417
418 # Only get the first child from an element, even there are more than one.
419 firstchild = element[0].firstChild
420
421 # Get the value of the child.
422 value = firstchild.nodeValue
423
424 # Return the value.
425 return value
426
427
3b16fdb1 428class DDNSProviderAllInkl(DDNSProvider):
6a11646e
MT
429 handle = "all-inkl.com"
430 name = "All-inkl.com"
431 website = "http://all-inkl.com/"
432 protocols = ("ipv4",)
3b16fdb1
SS
433
434 # There are only information provided by the vendor how to
435 # perform an update on a FRITZ Box. Grab requried informations
436 # from the net.
437 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
438
439 url = "http://dyndns.kasserver.com"
29c8c9c6 440 can_remove_records = False
3b16fdb1
SS
441
442 def update(self):
3b16fdb1
SS
443 # There is no additional data required so we directly can
444 # send our request.
536e87d1 445 response = self.send_request(self.url, username=self.username, password=self.password)
3b16fdb1
SS
446
447 # Get the full response message.
448 output = response.read()
449
450 # Handle success messages.
451 if output.startswith("good") or output.startswith("nochg"):
452 return
453
454 # If we got here, some other update error happened.
455 raise DDNSUpdateError
456
457
a892c594
MT
458class DDNSProviderBindNsupdate(DDNSProvider):
459 handle = "nsupdate"
460 name = "BIND nsupdate utility"
461 website = "http://en.wikipedia.org/wiki/Nsupdate"
462
463 DEFAULT_TTL = 60
464
64d3fad4
MT
465 @staticmethod
466 def supported():
467 # Search if the nsupdate utility is available
468 paths = os.environ.get("PATH")
469
470 for path in paths.split(":"):
471 executable = os.path.join(path, "nsupdate")
472
473 if os.path.exists(executable):
474 return True
475
476 return False
477
a892c594
MT
478 def update(self):
479 scriptlet = self.__make_scriptlet()
480
481 # -v enables TCP hence we transfer keys and other data that may
482 # exceed the size of one packet.
483 # -t sets the timeout
484 command = ["nsupdate", "-v", "-t", "60"]
485
486 p = subprocess.Popen(command, shell=True,
487 stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
488 )
489 stdout, stderr = p.communicate(scriptlet)
490
491 if p.returncode == 0:
492 return
493
494 raise DDNSError("nsupdate terminated with error code: %s\n %s" % (p.returncode, stderr))
495
496 def __make_scriptlet(self):
497 scriptlet = []
498
499 # Set a different server the update is sent to.
500 server = self.get("server", None)
501 if server:
502 scriptlet.append("server %s" % server)
503
97998aac
MW
504 # Set the DNS zone the host should be added to.
505 zone = self.get("zone", None)
506 if zone:
507 scriptlet.append("zone %s" % zone)
508
a892c594
MT
509 key = self.get("key", None)
510 if key:
511 secret = self.get("secret")
512
513 scriptlet.append("key %s %s" % (key, secret))
514
515 ttl = self.get("ttl", self.DEFAULT_TTL)
516
517 # Perform an update for each supported protocol.
518 for rrtype, proto in (("AAAA", "ipv6"), ("A", "ipv4")):
519 address = self.get_address(proto)
520 if not address:
521 continue
522
523 scriptlet.append("update delete %s. %s" % (self.hostname, rrtype))
524 scriptlet.append("update add %s. %s %s %s" % \
525 (self.hostname, ttl, rrtype, address))
526
527 # Send the actions to the server.
528 scriptlet.append("send")
529 scriptlet.append("quit")
530
531 logger.debug(_("Scriptlet:"))
532 for line in scriptlet:
533 # Masquerade the line with the secret key.
534 if line.startswith("key"):
535 line = "key **** ****"
536
537 logger.debug(" %s" % line)
538
539 return "\n".join(scriptlet)
540
541
78046ffe
SS
542class DDNSProviderChangeIP(DDNSProvider):
543 handle = "changeip.com"
544 name = "ChangeIP.com"
545 website = "https://changeip.com"
546 protocols = ("ipv4",)
547
548 # Detailed information about the update api can be found here.
549 # http://www.changeip.com/accounts/knowledgebase.php?action=displayarticle&id=34
550
551 url = "https://nic.changeip.com/nic/update"
552 can_remove_records = False
553
554 def update_protocol(self, proto):
555 data = {
556 "hostname" : self.hostname,
557 "myip" : self.get_address(proto),
558 }
559
560 # Send update to the server.
561 try:
562 response = self.send_request(self.url, username=self.username, password=self.password,
563 data=data)
564
565 # Handle error codes.
566 except urllib2.HTTPError, e:
567 if e.code == 422:
568 raise DDNSRequestError(_("Domain not found."))
569
570 raise
571
572 # Handle success message.
573 if response.code == 200:
574 return
575
576 # If we got here, some other update error happened.
577 raise DDNSUpdateError(_("Server response: %s") % output)
578
579
f3cf1f70 580class DDNSProviderDHS(DDNSProvider):
6a11646e
MT
581 handle = "dhs.org"
582 name = "DHS International"
583 website = "http://dhs.org/"
584 protocols = ("ipv4",)
f3cf1f70
SS
585
586 # No information about the used update api provided on webpage,
587 # grabed from source code of ez-ipudate.
b2b05ef3 588
f3cf1f70 589 url = "http://members.dhs.org/nic/hosts"
29c8c9c6 590 can_remove_records = False
f3cf1f70 591
d45139f6 592 def update_protocol(self, proto):
f3cf1f70
SS
593 data = {
594 "domain" : self.hostname,
d45139f6 595 "ip" : self.get_address(proto),
f3cf1f70
SS
596 "hostcmd" : "edit",
597 "hostcmdstage" : "2",
598 "type" : "4",
599 }
600
601 # Send update to the server.
175c9b80 602 response = self.send_request(self.url, username=self.username, password=self.password,
f3cf1f70
SS
603 data=data)
604
605 # Handle success messages.
606 if response.code == 200:
607 return
608
f3cf1f70
SS
609 # If we got here, some other update error happened.
610 raise DDNSUpdateError
611
612
39301272 613class DDNSProviderDNSpark(DDNSProvider):
6a11646e
MT
614 handle = "dnspark.com"
615 name = "DNS Park"
616 website = "http://dnspark.com/"
617 protocols = ("ipv4",)
39301272
SS
618
619 # Informations to the used api can be found here:
620 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
b2b05ef3 621
39301272 622 url = "https://control.dnspark.com/api/dynamic/update.php"
29c8c9c6 623 can_remove_records = False
39301272 624
d45139f6 625 def update_protocol(self, proto):
39301272
SS
626 data = {
627 "domain" : self.hostname,
d45139f6 628 "ip" : self.get_address(proto),
39301272
SS
629 }
630
631 # Send update to the server.
175c9b80 632 response = self.send_request(self.url, username=self.username, password=self.password,
39301272
SS
633 data=data)
634
635 # Get the full response message.
636 output = response.read()
637
638 # Handle success messages.
639 if output.startswith("ok") or output.startswith("nochange"):
640 return
641
642 # Handle error codes.
643 if output == "unauth":
644 raise DDNSAuthenticationError
645 elif output == "abuse":
646 raise DDNSAbuseError
647 elif output == "blocked":
648 raise DDNSBlockedError
649 elif output == "nofqdn":
650 raise DDNSRequestError(_("No valid FQDN was given."))
651 elif output == "nohost":
652 raise DDNSRequestError(_("Invalid hostname specified."))
653 elif output == "notdyn":
654 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
655 elif output == "invalid":
656 raise DDNSRequestError(_("Invalid IP address has been sent."))
657
658 # If we got here, some other update error happened.
659 raise DDNSUpdateError
660
43b2cd59
SS
661
662class DDNSProviderDtDNS(DDNSProvider):
6a11646e
MT
663 handle = "dtdns.com"
664 name = "DtDNS"
665 website = "http://dtdns.com/"
666 protocols = ("ipv4",)
43b2cd59
SS
667
668 # Information about the format of the HTTPS request is to be found
669 # http://www.dtdns.com/dtsite/updatespec
b2b05ef3 670
43b2cd59 671 url = "https://www.dtdns.com/api/autodns.cfm"
29c8c9c6 672 can_remove_records = False
43b2cd59 673
d45139f6 674 def update_protocol(self, proto):
43b2cd59 675 data = {
d45139f6 676 "ip" : self.get_address(proto),
43b2cd59
SS
677 "id" : self.hostname,
678 "pw" : self.password
679 }
680
681 # Send update to the server.
682 response = self.send_request(self.url, data=data)
683
684 # Get the full response message.
685 output = response.read()
686
687 # Remove all leading and trailing whitespace.
688 output = output.strip()
689
690 # Handle success messages.
691 if "now points to" in output:
692 return
693
694 # Handle error codes.
695 if output == "No hostname to update was supplied.":
696 raise DDNSRequestError(_("No hostname specified."))
697
698 elif output == "The hostname you supplied is not valid.":
699 raise DDNSRequestError(_("Invalid hostname specified."))
700
701 elif output == "The password you supplied is not valid.":
702 raise DDNSAuthenticationError
703
704 elif output == "Administration has disabled this account.":
705 raise DDNSRequestError(_("Account has been disabled."))
706
707 elif output == "Illegal character in IP.":
708 raise DDNSRequestError(_("Invalid IP address has been sent."))
709
710 elif output == "Too many failed requests.":
711 raise DDNSRequestError(_("Too many failed requests."))
712
713 # If we got here, some other update error happened.
714 raise DDNSUpdateError
715
716
5d4bec40 717class DDNSProviderDynDNS(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
718 handle = "dyndns.org"
719 name = "Dyn"
720 website = "http://dyn.com/dns/"
721 protocols = ("ipv4",)
bfed6701
SS
722
723 # Information about the format of the request is to be found
724 # http://http://dyn.com/support/developers/api/perform-update/
725 # http://dyn.com/support/developers/api/return-codes/
b2b05ef3 726
bfed6701
SS
727 url = "https://members.dyndns.org/nic/update"
728
bfed6701 729
5d4bec40 730class DDNSProviderDynU(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
731 handle = "dynu.com"
732 name = "Dynu"
733 website = "http://dynu.com/"
734 protocols = ("ipv6", "ipv4",)
3a8407fa
SS
735
736 # Detailed information about the request and response codes
737 # are available on the providers webpage.
738 # http://dynu.com/Default.aspx?page=dnsapi
739
740 url = "https://api.dynu.com/nic/update"
741
d45139f6
MT
742 # DynU sends the IPv6 and IPv4 address in one request
743
744 def update(self):
745 data = DDNSProtocolDynDNS2.prepare_request_data(self, "ipv4")
54d3efc8
MT
746
747 # This one supports IPv6
cdc078dc
SS
748 myipv6 = self.get_address("ipv6")
749
750 # Add update information if we have an IPv6 address.
751 if myipv6:
752 data["myipv6"] = myipv6
54d3efc8 753
304026a3 754 self.send_request(data)
3a8407fa
SS
755
756
5420343a 757class DDNSProviderEasyDNS(DDNSProvider):
5d4bec40
MT
758 handle = "easydns.com"
759 name = "EasyDNS"
760 website = "http://www.easydns.com/"
761 protocols = ("ipv4",)
ee071271 762
5420343a
SS
763 # Detailed information about the request and response codes
764 # (API 1.3) are available on the providers webpage.
765 # https://fusion.easydns.com/index.php?/Knowledgebase/Article/View/102/7/dynamic-dns
ee071271
SS
766
767 url = "http://api.cp.easydns.com/dyn/tomato.php"
768
5420343a
SS
769 def update_protocol(self, proto):
770 data = {
771 "myip" : self.get_address(proto, "-"),
772 "hostname" : self.hostname,
773 }
774
775 # Send update to the server.
776 response = self.send_request(self.url, data=data,
777 username=self.username, password=self.password)
778
779 # Get the full response message.
780 output = response.read()
781
782 # Remove all leading and trailing whitespace.
783 output = output.strip()
784
785 # Handle success messages.
786 if output.startswith("NOERROR"):
787 return
788
789 # Handle error codes.
790 if output.startswith("NOACCESS"):
791 raise DDNSAuthenticationError
792
793 elif output.startswith("NOSERVICE"):
794 raise DDNSRequestError(_("Dynamic DNS is not turned on for this domain."))
795
796 elif output.startswith("ILLEGAL INPUT"):
797 raise DDNSRequestError(_("Invalid data has been sent."))
798
799 elif output.startswith("TOOSOON"):
800 raise DDNSRequestError(_("Too frequent update requests have been sent."))
801
802 # If we got here, some other update error happened.
803 raise DDNSUpdateError
804
ee071271 805
90fe8843
CE
806class DDNSProviderDomopoli(DDNSProtocolDynDNS2, DDNSProvider):
807 handle = "domopoli.de"
808 name = "domopoli.de"
809 website = "http://domopoli.de/"
810 protocols = ("ipv4",)
811
812 # https://www.domopoli.de/?page=howto#DynDns_start
813
814 url = "http://dyndns.domopoli.de/nic/update"
815
816
a197d1a6
SS
817class DDNSProviderDynsNet(DDNSProvider):
818 handle = "dyns.net"
819 name = "DyNS"
820 website = "http://www.dyns.net/"
821 protocols = ("ipv4",)
29c8c9c6 822 can_remove_records = False
a197d1a6
SS
823
824 # There is very detailed informatio about how to send the update request and
825 # the possible response codes. (Currently we are using the v1.1 proto)
826 # http://www.dyns.net/documentation/technical/protocol/
827
828 url = "http://www.dyns.net/postscript011.php"
829
d45139f6 830 def update_protocol(self, proto):
a197d1a6 831 data = {
d45139f6 832 "ip" : self.get_address(proto),
a197d1a6
SS
833 "host" : self.hostname,
834 "username" : self.username,
835 "password" : self.password,
836 }
837
838 # Send update to the server.
839 response = self.send_request(self.url, data=data)
840
841 # Get the full response message.
842 output = response.read()
843
844 # Handle success messages.
845 if output.startswith("200"):
846 return
847
848 # Handle error codes.
849 if output.startswith("400"):
850 raise DDNSRequestError(_("Malformed request has been sent."))
851 elif output.startswith("401"):
852 raise DDNSAuthenticationError
853 elif output.startswith("402"):
854 raise DDNSRequestError(_("Too frequent update requests have been sent."))
855 elif output.startswith("403"):
856 raise DDNSInternalServerError
857
858 # If we got here, some other update error happened.
859 raise DDNSUpdateError(_("Server response: %s") % output)
860
861
35216523
SS
862class DDNSProviderEnomCom(DDNSResponseParserXML, DDNSProvider):
863 handle = "enom.com"
864 name = "eNom Inc."
865 website = "http://www.enom.com/"
d45139f6 866 protocols = ("ipv4",)
35216523
SS
867
868 # There are very detailed information about how to send an update request and
869 # the respone codes.
870 # http://www.enom.com/APICommandCatalog/
871
872 url = "https://dynamic.name-services.com/interface.asp"
29c8c9c6 873 can_remove_records = False
35216523 874
d45139f6 875 def update_protocol(self, proto):
35216523
SS
876 data = {
877 "command" : "setdnshost",
878 "responsetype" : "xml",
d45139f6 879 "address" : self.get_address(proto),
35216523
SS
880 "domainpassword" : self.password,
881 "zone" : self.hostname
882 }
883
884 # Send update to the server.
885 response = self.send_request(self.url, data=data)
886
887 # Get the full response message.
888 output = response.read()
889
890 # Handle success messages.
891 if self.get_xml_tag_value(output, "ErrCount") == "0":
892 return
893
894 # Handle error codes.
895 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
896
897 if errorcode == "304155":
898 raise DDNSAuthenticationError
899 elif errorcode == "304153":
900 raise DDNSRequestError(_("Domain not found."))
901
902 # If we got here, some other update error happened.
903 raise DDNSUpdateError
904
905
ab4e352e
SS
906class DDNSProviderEntryDNS(DDNSProvider):
907 handle = "entrydns.net"
908 name = "EntryDNS"
909 website = "http://entrydns.net/"
910 protocols = ("ipv4",)
911
912 # Some very tiny details about their so called "Simple API" can be found
913 # here: https://entrydns.net/help
914 url = "https://entrydns.net/records/modify"
29c8c9c6 915 can_remove_records = False
ab4e352e 916
d45139f6 917 def update_protocol(self, proto):
ab4e352e 918 data = {
d45139f6 919 "ip" : self.get_address(proto),
ab4e352e
SS
920 }
921
922 # Add auth token to the update url.
923 url = "%s/%s" % (self.url, self.token)
924
925 # Send update to the server.
926 try:
babc5e6d 927 response = self.send_request(url, data=data)
ab4e352e
SS
928
929 # Handle error codes
930 except urllib2.HTTPError, e:
931 if e.code == 404:
932 raise DDNSAuthenticationError
933
934 elif e.code == 422:
935 raise DDNSRequestError(_("An invalid IP address was submitted"))
936
937 raise
938
939 # Handle success messages.
940 if response.code == 200:
941 return
942
943 # If we got here, some other update error happened.
944 raise DDNSUpdateError
945
946
aa21a4c6 947class DDNSProviderFreeDNSAfraidOrg(DDNSProvider):
6a11646e
MT
948 handle = "freedns.afraid.org"
949 name = "freedns.afraid.org"
950 website = "http://freedns.afraid.org/"
aa21a4c6
SS
951
952 # No information about the request or response could be found on the vendor
953 # page. All used values have been collected by testing.
954 url = "https://freedns.afraid.org/dynamic/update.php"
29c8c9c6 955 can_remove_records = False
aa21a4c6 956
d45139f6 957 def update_protocol(self, proto):
aa21a4c6 958 data = {
d45139f6 959 "address" : self.get_address(proto),
aa21a4c6
SS
960 }
961
962 # Add auth token to the update url.
963 url = "%s?%s" % (self.url, self.token)
964
965 # Send update to the server.
966 response = self.send_request(url, data=data)
967
a204b107
SS
968 # Get the full response message.
969 output = response.read()
970
971 # Handle success messages.
aa21a4c6
SS
972 if output.startswith("Updated") or "has not changed" in output:
973 return
974
975 # Handle error codes.
976 if output == "ERROR: Unable to locate this record":
977 raise DDNSAuthenticationError
978 elif "is an invalid IP address" in output:
979 raise DDNSRequestError(_("Invalid IP address has been sent."))
980
3b524cf2
SS
981 # If we got here, some other update error happened.
982 raise DDNSUpdateError
983
aa21a4c6 984
a08c1b72 985class DDNSProviderLightningWireLabs(DDNSProvider):
6a11646e 986 handle = "dns.lightningwirelabs.com"
fb115fdc 987 name = "Lightning Wire Labs DNS Service"
6a11646e 988 website = "http://dns.lightningwirelabs.com/"
a08c1b72
SS
989
990 # Information about the format of the HTTPS request is to be found
991 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
b2b05ef3 992
a08c1b72
SS
993 url = "https://dns.lightningwirelabs.com/update"
994
5f402f36 995 def update(self):
a08c1b72
SS
996 data = {
997 "hostname" : self.hostname,
e3c70807
MT
998 "address6" : self.get_address("ipv6", "-"),
999 "address4" : self.get_address("ipv4", "-"),
a08c1b72
SS
1000 }
1001
a08c1b72
SS
1002 # Check if a token has been set.
1003 if self.token:
1004 data["token"] = self.token
1005
1006 # Check for username and password.
1007 elif self.username and self.password:
1008 data.update({
1009 "username" : self.username,
1010 "password" : self.password,
1011 })
1012
1013 # Raise an error if no auth details are given.
1014 else:
1015 raise DDNSConfigurationError
1016
1017 # Send update to the server.
cb455540 1018 response = self.send_request(self.url, data=data)
a08c1b72
SS
1019
1020 # Handle success messages.
1021 if response.code == 200:
1022 return
1023
a08c1b72
SS
1024 # If we got here, some other update error happened.
1025 raise DDNSUpdateError
1026
1027
446e42af
GH
1028class DDNSProviderMyOnlinePortal(DDNSProtocolDynDNS2, DDNSProvider):
1029 handle = "myonlineportal.net"
1030 name = "myonlineportal.net"
1031 website = "https:/myonlineportal.net/"
1032
1033 # Information about the request and response can be obtained here:
1034 # https://myonlineportal.net/howto_dyndns
1035
1036 url = "https://myonlineportal.net/updateddns"
1037
1038 def prepare_request_data(self, proto):
1039 data = {
1040 "hostname" : self.hostname,
1041 "ip" : self.get_address(proto),
1042 }
1043
1044 return data
1045
1046
78c9780b 1047class DDNSProviderNamecheap(DDNSResponseParserXML, DDNSProvider):
6a11646e
MT
1048 handle = "namecheap.com"
1049 name = "Namecheap"
1050 website = "http://namecheap.com"
1051 protocols = ("ipv4",)
d1cd57eb
SS
1052
1053 # Information about the format of the HTTP request is to be found
1054 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
1055 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
1056
1057 url = "https://dynamicdns.park-your-domain.com/update"
29c8c9c6 1058 can_remove_records = False
d1cd57eb 1059
d45139f6 1060 def update_protocol(self, proto):
d1cd57eb
SS
1061 # Namecheap requires the hostname splitted into a host and domain part.
1062 host, domain = self.hostname.split(".", 1)
1063
1064 data = {
d45139f6 1065 "ip" : self.get_address(proto),
d1cd57eb
SS
1066 "password" : self.password,
1067 "host" : host,
1068 "domain" : domain
1069 }
1070
1071 # Send update to the server.
1072 response = self.send_request(self.url, data=data)
1073
1074 # Get the full response message.
1075 output = response.read()
1076
1077 # Handle success messages.
d45139f6 1078 if self.get_xml_tag_value(output, "IP") == address:
d1cd57eb
SS
1079 return
1080
1081 # Handle error codes.
78c9780b 1082 errorcode = self.get_xml_tag_value(output, "ResponseNumber")
d1cd57eb
SS
1083
1084 if errorcode == "304156":
1085 raise DDNSAuthenticationError
1086 elif errorcode == "316153":
1087 raise DDNSRequestError(_("Domain not found."))
1088 elif errorcode == "316154":
1089 raise DDNSRequestError(_("Domain not active."))
1090 elif errorcode in ("380098", "380099"):
1091 raise DDNSInternalServerError
1092
1093 # If we got here, some other update error happened.
1094 raise DDNSUpdateError
1095
1096
5d4bec40
MT
1097class DDNSProviderNOIP(DDNSProtocolDynDNS2, DDNSProvider):
1098 handle = "no-ip.com"
1099 name = "No-IP"
1100 website = "http://www.no-ip.com/"
1101 protocols = ("ipv4",)
f22ab085
MT
1102
1103 # Information about the format of the HTTP request is to be found
1104 # here: http://www.no-ip.com/integrate/request and
1105 # here: http://www.no-ip.com/integrate/response
1106
88f39629 1107 url = "http://dynupdate.no-ip.com/nic/update"
2de06f59 1108
d45139f6
MT
1109 def prepare_request_data(self, proto):
1110 assert proto == "ipv4"
1111
2de06f59
MT
1112 data = {
1113 "hostname" : self.hostname,
d45139f6 1114 "address" : self.get_address(proto),
f22ab085
MT
1115 }
1116
88f39629 1117 return data
f22ab085
MT
1118
1119
31c95e4b
SS
1120class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2, DDNSProvider):
1121 handle = "nsupdate.info"
1122 name = "nsupdate.info"
b9221322 1123 website = "http://nsupdate.info/"
31c95e4b
SS
1124 protocols = ("ipv6", "ipv4",)
1125
1126 # Information about the format of the HTTP request can be found
b9221322 1127 # after login on the provider user interface and here:
31c95e4b
SS
1128 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
1129
7b5d382e
SS
1130 url = "https://nsupdate.info/nic/update"
1131
29c8c9c6
MT
1132 # TODO nsupdate.info can actually do this, but the functionality
1133 # has not been implemented here, yet.
1134 can_remove_records = False
1135
64018439
MT
1136 # After a failed update, there will be no retries
1137 # https://bugzilla.ipfire.org/show_bug.cgi?id=10603
1138 holdoff_failure_days = None
1139
31c95e4b
SS
1140 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
1141 # and for the password a so called secret.
1142 @property
1143 def username(self):
1144 return self.get("hostname")
1145
1146 @property
1147 def password(self):
9c777232 1148 return self.token or self.get("secret")
31c95e4b 1149
d45139f6 1150 def prepare_request_data(self, proto):
31c95e4b 1151 data = {
d45139f6 1152 "myip" : self.get_address(proto),
31c95e4b
SS
1153 }
1154
1155 return data
1156
1157
90663439
SS
1158class DDNSProviderOpenDNS(DDNSProtocolDynDNS2, DDNSProvider):
1159 handle = "opendns.com"
1160 name = "OpenDNS"
1161 website = "http://www.opendns.com"
1162
1163 # Detailed information about the update request and possible
1164 # response codes can be obtained from here:
1165 # https://support.opendns.com/entries/23891440
1166
1167 url = "https://updates.opendns.com/nic/update"
1168
d45139f6 1169 def prepare_request_data(self, proto):
90663439
SS
1170 data = {
1171 "hostname" : self.hostname,
d45139f6 1172 "myip" : self.get_address(proto),
90663439
SS
1173 }
1174
1175 return data
1176
1177
5d4bec40
MT
1178class DDNSProviderOVH(DDNSProtocolDynDNS2, DDNSProvider):
1179 handle = "ovh.com"
1180 name = "OVH"
1181 website = "http://www.ovh.com/"
1182 protocols = ("ipv4",)
a508bda6
SS
1183
1184 # OVH only provides very limited information about how to
1185 # update a DynDNS host. They only provide the update url
1186 # on the their german subpage.
1187 #
1188 # http://hilfe.ovh.de/DomainDynHost
1189
1190 url = "https://www.ovh.com/nic/update"
1191
d45139f6
MT
1192 def prepare_request_data(self, proto):
1193 data = DDNSProtocolDynDNS2.prepare_request_data(self, proto)
54d3efc8
MT
1194 data.update({
1195 "system" : "dyndns",
1196 })
1197
1198 return data
a508bda6
SS
1199
1200
ef33455e 1201class DDNSProviderRegfish(DDNSProvider):
6a11646e
MT
1202 handle = "regfish.com"
1203 name = "Regfish GmbH"
1204 website = "http://www.regfish.com/"
ef33455e
SS
1205
1206 # A full documentation to the providers api can be found here
1207 # but is only available in german.
1208 # https://www.regfish.de/domains/dyndns/dokumentation
1209
1210 url = "https://dyndns.regfish.de/"
29c8c9c6 1211 can_remove_records = False
ef33455e
SS
1212
1213 def update(self):
1214 data = {
1215 "fqdn" : self.hostname,
1216 }
1217
1218 # Check if we update an IPv6 address.
1219 address6 = self.get_address("ipv6")
1220 if address6:
1221 data["ipv6"] = address6
1222
1223 # Check if we update an IPv4 address.
1224 address4 = self.get_address("ipv4")
1225 if address4:
1226 data["ipv4"] = address4
1227
1228 # Raise an error if none address is given.
1229 if not data.has_key("ipv6") and not data.has_key("ipv4"):
1230 raise DDNSConfigurationError
1231
1232 # Check if a token has been set.
1233 if self.token:
1234 data["token"] = self.token
1235
1236 # Raise an error if no token and no useranem and password
1237 # are given.
1238 elif not self.username and not self.password:
1239 raise DDNSConfigurationError(_("No Auth details specified."))
1240
1241 # HTTP Basic Auth is only allowed if no token is used.
1242 if self.token:
1243 # Send update to the server.
1244 response = self.send_request(self.url, data=data)
1245 else:
1246 # Send update to the server.
1247 response = self.send_request(self.url, username=self.username, password=self.password,
1248 data=data)
1249
1250 # Get the full response message.
1251 output = response.read()
1252
1253 # Handle success messages.
1254 if "100" in output or "101" in output:
1255 return
1256
1257 # Handle error codes.
1258 if "401" or "402" in output:
1259 raise DDNSAuthenticationError
1260 elif "408" in output:
1261 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
1262 elif "409" in output:
1263 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
1264 elif "412" in output:
1265 raise DDNSRequestError(_("No valid FQDN was given."))
1266 elif "414" in output:
1267 raise DDNSInternalServerError
1268
1269 # If we got here, some other update error happened.
1270 raise DDNSUpdateError
1271
1272
5d4bec40 1273class DDNSProviderSelfhost(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
1274 handle = "selfhost.de"
1275 name = "Selfhost.de"
1276 website = "http://www.selfhost.de/"
5d4bec40 1277 protocols = ("ipv4",)
f22ab085 1278
04db1862 1279 url = "https://carol.selfhost.de/nic/update"
f22ab085 1280
d45139f6
MT
1281 def prepare_request_data(self, proto):
1282 data = DDNSProtocolDynDNS2.prepare_request_data(self, proto)
04db1862
MT
1283 data.update({
1284 "hostname" : "1",
1285 })
f22ab085 1286
04db1862 1287 return data
b09b1545
SS
1288
1289
5d4bec40
MT
1290class DDNSProviderSPDNS(DDNSProtocolDynDNS2, DDNSProvider):
1291 handle = "spdns.org"
1292 name = "SPDNS"
1293 website = "http://spdns.org/"
b09b1545
SS
1294
1295 # Detailed information about request and response codes are provided
1296 # by the vendor. They are using almost the same mechanism and status
1297 # codes as dyndns.org so we can inherit all those stuff.
1298 #
1299 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
1300 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
1301
1302 url = "https://update.spdns.de/nic/update"
4ec90b93 1303
94ab4379
SS
1304 @property
1305 def username(self):
1306 return self.get("username") or self.hostname
1307
1308 @property
1309 def password(self):
25f39b4e 1310 return self.get("password") or self.token
94ab4379 1311
4ec90b93 1312
5d4bec40
MT
1313class DDNSProviderStrato(DDNSProtocolDynDNS2, DDNSProvider):
1314 handle = "strato.com"
1315 name = "Strato AG"
1316 website = "http:/www.strato.com/"
1317 protocols = ("ipv4",)
7488825c
SS
1318
1319 # Information about the request and response can be obtained here:
1320 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
1321
1322 url = "https://dyndns.strato.com/nic/update"
1323
1324
5d4bec40
MT
1325class DDNSProviderTwoDNS(DDNSProtocolDynDNS2, DDNSProvider):
1326 handle = "twodns.de"
1327 name = "TwoDNS"
1328 website = "http://www.twodns.de"
1329 protocols = ("ipv4",)
a6183090
SS
1330
1331 # Detailed information about the request can be found here
1332 # http://twodns.de/en/faqs
1333 # http://twodns.de/en/api
1334
1335 url = "https://update.twodns.de/update"
1336
d45139f6
MT
1337 def prepare_request_data(self, proto):
1338 assert proto == "ipv4"
1339
a6183090 1340 data = {
d45139f6 1341 "ip" : self.get_address(proto),
a6183090
SS
1342 "hostname" : self.hostname
1343 }
1344
1345 return data
1346
1347
5d4bec40
MT
1348class DDNSProviderUdmedia(DDNSProtocolDynDNS2, DDNSProvider):
1349 handle = "udmedia.de"
1350 name = "Udmedia GmbH"
1351 website = "http://www.udmedia.de"
1352 protocols = ("ipv4",)
03bdd188
SS
1353
1354 # Information about the request can be found here
1355 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1356
1357 url = "https://www.udmedia.de/nic/update"
1358
1359
5d4bec40 1360class DDNSProviderVariomedia(DDNSProtocolDynDNS2, DDNSProvider):
6a11646e
MT
1361 handle = "variomedia.de"
1362 name = "Variomedia"
1363 website = "http://www.variomedia.de/"
1364 protocols = ("ipv6", "ipv4",)
c8c7ca8f
SS
1365
1366 # Detailed information about the request can be found here
1367 # https://dyndns.variomedia.de/
1368
1369 url = "https://dyndns.variomedia.de/nic/update"
1370
d45139f6 1371 def prepare_request_data(self, proto):
c8c7ca8f
SS
1372 data = {
1373 "hostname" : self.hostname,
d45139f6 1374 "myip" : self.get_address(proto),
c8c7ca8f 1375 }
54d3efc8
MT
1376
1377 return data
98fbe467
SS
1378
1379
f0554226 1380class DDNSProviderZoneedit(DDNSProvider):
5d4bec40
MT
1381 handle = "zoneedit.com"
1382 name = "Zoneedit"
1383 website = "http://www.zoneedit.com"
1384 protocols = ("ipv4",)
98fbe467
SS
1385
1386 # Detailed information about the request and the response codes can be
1387 # obtained here:
1388 # http://www.zoneedit.com/doc/api/other.html
1389 # http://www.zoneedit.com/faq.html
1390
1391 url = "https://dynamic.zoneedit.com/auth/dynamic.html"
1392
d45139f6 1393 def update_protocol(self, proto):
98fbe467 1394 data = {
d45139f6 1395 "dnsto" : self.get_address(proto),
98fbe467
SS
1396 "host" : self.hostname
1397 }
1398
1399 # Send update to the server.
1400 response = self.send_request(self.url, username=self.username, password=self.password,
1401 data=data)
1402
1403 # Get the full response message.
1404 output = response.read()
1405
1406 # Handle success messages.
1407 if output.startswith("<SUCCESS"):
1408 return
1409
1410 # Handle error codes.
1411 if output.startswith("invalid login"):
1412 raise DDNSAuthenticationError
1413 elif output.startswith("<ERROR CODE=\"704\""):
1414 raise DDNSRequestError(_("No valid FQDN was given."))
1415 elif output.startswith("<ERROR CODE=\"702\""):
1416 raise DDNSInternalServerError
1417
1418 # If we got here, some other update error happened.
1419 raise DDNSUpdateError
e53d3225
SS
1420
1421
1422class DDNSProviderZZZZ(DDNSProvider):
1423 handle = "zzzz.io"
1424 name = "zzzz"
1425 website = "https://zzzz.io"
fbdff678 1426 protocols = ("ipv6", "ipv4",)
e53d3225
SS
1427
1428 # Detailed information about the update request can be found here:
1429 # https://zzzz.io/faq/
1430
1431 # Details about the possible response codes have been provided in the bugtracker:
1432 # https://bugzilla.ipfire.org/show_bug.cgi?id=10584#c2
1433
1434 url = "https://zzzz.io/api/v1/update"
29c8c9c6 1435 can_remove_records = False
e53d3225 1436
d45139f6 1437 def update_protocol(self, proto):
e53d3225 1438 data = {
d45139f6 1439 "ip" : self.get_address(proto),
e53d3225
SS
1440 "token" : self.token,
1441 }
1442
fbdff678
MT
1443 if proto == "ipv6":
1444 data["type"] = "aaaa"
1445
e53d3225
SS
1446 # zzzz uses the host from the full hostname as part
1447 # of the update url.
1448 host, domain = self.hostname.split(".", 1)
1449
1450 # Add host value to the update url.
1451 url = "%s/%s" % (self.url, host)
1452
1453 # Send update to the server.
1454 try:
1455 response = self.send_request(url, data=data)
1456
1457 # Handle error codes.
ff43fa70
MT
1458 except DDNSNotFound:
1459 raise DDNSRequestError(_("Invalid hostname specified"))
e53d3225
SS
1460
1461 # Handle success messages.
1462 if response.code == 200:
1463 return
1464
1465 # If we got here, some other update error happened.
1466 raise DDNSUpdateError