]>
git.ipfire.org Git - people/stevee/ddns.git/blob - src/ddns/providers.py
2 ###############################################################################
4 # ddns - A dynamic DNS client for IPFire #
5 # Copyright (C) 2012-2017 IPFire development team #
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. #
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. #
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/>. #
20 ###############################################################################
27 import xml
.dom
.minidom
31 # Import all possible exception types.
34 logger
= logging
.getLogger("ddns.providers")
41 Returns a dict with all automatically registered providers.
43 return _providers
.copy()
45 class DDNSProvider(object):
46 # A short string that uniquely identifies
50 # The full name of the provider.
53 # A weburl to the homepage of the provider.
54 # (Where to register a new account?)
57 # A list of supported protocols.
58 protocols
= ("ipv6", "ipv4")
62 # holdoff time - Number of days no update is performed unless
63 # the IP address has changed.
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
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
74 # Automatically register all providers.
75 class __metaclass__(type):
76 def __init__(provider
, name
, bases
, dict):
77 type.__init
__(provider
, name
, bases
, dict)
79 # The main class from which is inherited is not registered
81 if name
== "DDNSProvider":
84 if not all((provider
.handle
, provider
.name
, provider
.website
)):
85 raise DDNSError(_("Provider is not properly configured"))
87 assert not _providers
.has_key(provider
.handle
), \
88 "Provider '%s' has already been registered" % provider
.handle
90 _providers
[provider
.handle
] = provider
95 Should be overwritten to check if the system the code is running
96 on has all the required tools to support this provider.
100 def __init__(self
, core
, **settings
):
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
)
109 return "<DDNS Provider %s (%s)>" % (self
.name
, self
.handle
)
111 def __cmp__(self
, other
):
112 return cmp(self
.hostname
, other
.hostname
)
118 def get(self
, key
, default
=None):
120 Get a setting from the settings dictionary.
122 return self
.settings
.get(key
, default
)
127 Fast access to the hostname.
129 return self
.get("hostname")
134 Fast access to the username.
136 return self
.get("username")
141 Fast access to the password.
143 return self
.get("password")
148 Fast access to the token.
150 return self
.get("token")
152 def __call__(self
, force
=False):
154 logger
.debug(_("Updating %s forced") % self
.hostname
)
156 # Do nothing if the last update has failed or no update is required
157 elif self
.has_failure
or not self
.requires_update
:
160 # Execute the update.
164 # 1) Catch network errors early, because we do not want to log
165 # them to the database. They are usually temporary and caused
166 # by the client side, so that we will retry quickly.
167 # 2) If there is an internet server error (HTTP code 500) on the
168 # provider's site, we will not log a failure and try again
170 except (DDNSNetworkError
, DDNSInternalServerError
):
173 # In case of any errors, log the failed request and
174 # raise the exception.
175 except DDNSError
as e
:
176 self
.core
.db
.log_failure(self
.hostname
, e
)
179 logger
.info(_("Dynamic DNS update for %(hostname)s (%(provider)s) successful") % \
180 { "hostname" : self
.hostname
, "provider" : self
.name
})
181 self
.core
.db
.log_success(self
.hostname
)
184 for protocol
in self
.protocols
:
185 if self
.have_address(protocol
):
186 self
.update_protocol(protocol
)
187 elif self
.can_remove_records
:
188 self
.remove_protocol(protocol
)
190 def update_protocol(self
, proto
):
191 raise NotImplementedError
193 def remove_protocol(self
, proto
):
194 if not self
.can_remove_records
:
195 raise RuntimeError, "can_remove_records is enabled, but remove_protocol() not implemented"
197 raise NotImplementedError
200 def requires_update(self
):
201 # If the IP addresses have changed, an update is required
202 if self
.ip_address_changed(self
.protocols
):
203 logger
.debug(_("An update for %(hostname)s (%(provider)s)"
204 " is performed because of an IP address change") % \
205 { "hostname" : self
.hostname
, "provider" : self
.name
})
209 # If the holdoff time has expired, an update is required, too
210 if self
.holdoff_time_expired():
211 logger
.debug(_("An update for %(hostname)s (%(provider)s)"
212 " is performed because the holdoff time has expired") % \
213 { "hostname" : self
.hostname
, "provider" : self
.name
})
217 # Otherwise, we don't need to perform an update
218 logger
.debug(_("No update required for %(hostname)s (%(provider)s)") % \
219 { "hostname" : self
.hostname
, "provider" : self
.name
})
224 def has_failure(self
):
226 Returns True when the last update has failed and no retry
227 should be performed, yet.
229 last_status
= self
.db
.last_update_status(self
.hostname
)
231 # Return False if the last update has not failed.
232 if not last_status
== "failure":
235 # If there is no holdoff time, we won't update ever again.
236 if self
.holdoff_failure_days
is None:
237 logger
.warning(_("An update has not been performed because earlier updates failed for %s") \
239 logger
.warning(_("There will be no retries"))
243 # Determine when the holdoff time ends
244 last_update
= self
.db
.last_update(self
.hostname
, status
=last_status
)
245 holdoff_end
= last_update
+ datetime
.timedelta(days
=self
.holdoff_failure_days
)
247 now
= datetime
.datetime
.utcnow()
248 if now
< holdoff_end
:
249 failure_message
= self
.db
.last_update_failure_message(self
.hostname
)
251 logger
.warning(_("An update has not been performed because earlier updates failed for %s") \
255 logger
.warning(_("Last failure message:"))
257 for line
in failure_message
.splitlines():
258 logger
.warning(" %s" % line
)
260 logger
.warning(_("Further updates will be withheld until %s") % holdoff_end
)
266 def ip_address_changed(self
, protos
):
268 Returns True if this host is already up to date
269 and does not need to change the IP address on the
273 addresses
= self
.core
.system
.resolve(self
.hostname
, proto
)
274 current_address
= self
.get_address(proto
)
276 # Handle if the system has not got any IP address from a protocol
277 # (i.e. had full dual-stack connectivity which it has not any more)
278 if current_address
is None:
279 # If addresses still exists in the DNS system and if this provider
280 # is able to remove records, we will do that.
281 if addresses
and self
.can_remove_records
:
284 # Otherwise, we cannot go on...
287 if not current_address
in addresses
:
292 def holdoff_time_expired(self
):
294 Returns true if the holdoff time has expired
295 and the host requires an update
297 # If no holdoff days is defined, we cannot go on
298 if not self
.holdoff_days
:
301 # Get the timestamp of the last successfull update
302 last_update
= self
.db
.last_update(self
.hostname
, status
="success")
304 # If no timestamp has been recorded, no update has been
305 # performed. An update should be performed now.
309 # Determine when the holdoff time ends
310 holdoff_end
= last_update
+ datetime
.timedelta(days
=self
.holdoff_days
)
312 now
= datetime
.datetime
.utcnow()
314 if now
>= holdoff_end
:
315 logger
.debug("The holdoff time has expired for %s" % self
.hostname
)
318 logger
.debug("Updates for %s are held off until %s" % \
319 (self
.hostname
, holdoff_end
))
322 def send_request(self
, *args
, **kwargs
):
324 Proxy connection to the send request
327 return self
.core
.system
.send_request(*args
, **kwargs
)
329 def get_address(self
, proto
, default
=None):
331 Proxy method to get the current IP address.
333 return self
.core
.system
.get_address(proto
) or default
335 def have_address(self
, proto
):
337 Returns True if an IP address for the given protocol
340 address
= self
.get_address(proto
)
348 class DDNSProtocolDynDNS2(object):
350 This is an abstract class that implements the DynDNS updater
351 protocol version 2. As this is a popular way to update dynamic
352 DNS records, this class is supposed make the provider classes
356 # Information about the format of the request is to be found
357 # http://dyn.com/support/developers/api/perform-update/
358 # http://dyn.com/support/developers/api/return-codes/
360 # The DynDNS protocol version 2 does not allow to remove records
361 can_remove_records
= False
363 def prepare_request_data(self
, proto
):
365 "hostname" : self
.hostname
,
366 "myip" : self
.get_address(proto
),
371 def update_protocol(self
, proto
):
372 data
= self
.prepare_request_data(proto
)
374 return self
.send_request(data
)
376 def send_request(self
, data
):
377 # Send update to the server.
378 response
= DDNSProvider
.send_request(self
, self
.url
, data
=data
,
379 username
=self
.username
, password
=self
.password
)
381 # Get the full response message.
382 output
= response
.read()
384 # Handle success messages.
385 if output
.startswith("good") or output
.startswith("nochg"):
388 # Handle error codes.
389 if output
== "badauth":
390 raise DDNSAuthenticationError
391 elif output
== "abuse":
393 elif output
== "notfqdn":
394 raise DDNSRequestError(_("No valid FQDN was given"))
395 elif output
== "nohost":
396 raise DDNSRequestError(_("Specified host does not exist"))
397 elif output
== "911":
398 raise DDNSInternalServerError
399 elif output
== "dnserr":
400 raise DDNSInternalServerError(_("DNS error encountered"))
401 elif output
== "badagent":
402 raise DDNSBlockedError
403 elif output
== "badip":
404 raise DDNSBlockedError
406 # If we got here, some other update error happened.
407 raise DDNSUpdateError(_("Server response: %s") % output
)
410 class DDNSResponseParserXML(object):
412 This class provides a parser for XML responses which
413 will be sent by various providers. This class uses the python
414 shipped XML minidom module to walk through the XML tree and return
418 def get_xml_tag_value(self
, document
, content
):
419 # Send input to the parser.
420 xmldoc
= xml
.dom
.minidom
.parseString(document
)
422 # Get XML elements by the given content.
423 element
= xmldoc
.getElementsByTagName(content
)
425 # If no element has been found, we directly can return None.
429 # Only get the first child from an element, even there are more than one.
430 firstchild
= element
[0].firstChild
432 # Get the value of the child.
433 value
= firstchild
.nodeValue
439 class DDNSProviderAllInkl(DDNSProvider
):
440 handle
= "all-inkl.com"
441 name
= "All-inkl.com"
442 website
= "http://all-inkl.com/"
443 protocols
= ("ipv4",)
445 # There are only information provided by the vendor how to
446 # perform an update on a FRITZ Box. Grab requried informations
448 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
450 url
= "http://dyndns.kasserver.com"
451 can_remove_records
= False
454 # There is no additional data required so we directly can
456 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
)
458 # Get the full response message.
459 output
= response
.read()
461 # Handle success messages.
462 if output
.startswith("good") or output
.startswith("nochg"):
465 # If we got here, some other update error happened.
466 raise DDNSUpdateError
469 class DDNSProviderBindNsupdate(DDNSProvider
):
471 name
= "BIND nsupdate utility"
472 website
= "http://en.wikipedia.org/wiki/Nsupdate"
478 # Search if the nsupdate utility is available
479 paths
= os
.environ
.get("PATH")
481 for path
in paths
.split(":"):
482 executable
= os
.path
.join(path
, "nsupdate")
484 if os
.path
.exists(executable
):
490 scriptlet
= self
.__make
_scriptlet
()
492 # -v enables TCP hence we transfer keys and other data that may
493 # exceed the size of one packet.
494 # -t sets the timeout
495 command
= ["nsupdate", "-v", "-t", "60"]
497 p
= subprocess
.Popen(command
, shell
=True,
498 stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
500 stdout
, stderr
= p
.communicate(scriptlet
)
502 if p
.returncode
== 0:
505 raise DDNSError("nsupdate terminated with error code: %s\n %s" % (p
.returncode
, stderr
))
507 def __make_scriptlet(self
):
510 # Set a different server the update is sent to.
511 server
= self
.get("server", None)
513 scriptlet
.append("server %s" % server
)
515 # Set the DNS zone the host should be added to.
516 zone
= self
.get("zone", None)
518 scriptlet
.append("zone %s" % zone
)
520 key
= self
.get("key", None)
522 secret
= self
.get("secret")
524 scriptlet
.append("key %s %s" % (key
, secret
))
526 ttl
= self
.get("ttl", self
.DEFAULT_TTL
)
528 # Perform an update for each supported protocol.
529 for rrtype
, proto
in (("AAAA", "ipv6"), ("A", "ipv4")):
530 address
= self
.get_address(proto
)
534 scriptlet
.append("update delete %s. %s" % (self
.hostname
, rrtype
))
535 scriptlet
.append("update add %s. %s %s %s" % \
536 (self
.hostname
, ttl
, rrtype
, address
))
538 # Send the actions to the server.
539 scriptlet
.append("send")
540 scriptlet
.append("quit")
542 logger
.debug(_("Scriptlet:"))
543 for line
in scriptlet
:
544 # Masquerade the line with the secret key.
545 if line
.startswith("key"):
546 line
= "key **** ****"
548 logger
.debug(" %s" % line
)
550 return "\n".join(scriptlet
)
553 class DDNSProviderChangeIP(DDNSProvider
):
554 handle
= "changeip.com"
555 name
= "ChangeIP.com"
556 website
= "https://changeip.com"
557 protocols
= ("ipv4",)
559 # Detailed information about the update api can be found here.
560 # http://www.changeip.com/accounts/knowledgebase.php?action=displayarticle&id=34
562 url
= "https://nic.changeip.com/nic/update"
563 can_remove_records
= False
565 def update_protocol(self
, proto
):
567 "hostname" : self
.hostname
,
568 "myip" : self
.get_address(proto
),
571 # Send update to the server.
573 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
576 # Handle error codes.
577 except urllib2
.HTTPError
, e
:
579 raise DDNSRequestError(_("Domain not found."))
583 # Handle success message.
584 if response
.code
== 200:
587 # If we got here, some other update error happened.
588 raise DDNSUpdateError(_("Server response: %s") % output
)
591 class DDNSProviderDesecIO(DDNSProtocolDynDNS2
, DDNSProvider
):
594 website
= "https://www.desec.io"
595 protocols
= ("ipv6", "ipv4",)
597 # ipv4 / ipv6 records are automatically removed when the update
598 # request originates from the respectively other protocol and no
599 # address is explicitly provided for the unused protocol.
601 url
= "https://update.dedyn.io"
603 # desec.io sends the IPv6 and IPv4 address in one request
606 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, "ipv4")
608 # This one supports IPv6
609 myipv6
= self
.get_address("ipv6")
611 # Add update information if we have an IPv6 address.
613 data
["myipv6"] = myipv6
615 self
.send_request(data
)
618 class DDNSProviderDDNSS(DDNSProvider
):
621 website
= "http://www.ddnss.de"
622 protocols
= ("ipv4",)
624 # Detailed information about how to send the update request and possible response
625 # codes can be obtained from here.
626 # http://www.ddnss.de/info.php
627 # http://www.megacomputing.de/2014/08/dyndns-service-response-time/#more-919
629 url
= "http://www.ddnss.de/upd.php"
630 can_remove_records
= False
632 def update_protocol(self
, proto
):
634 "ip" : self
.get_address(proto
),
635 "host" : self
.hostname
,
638 # Check if a token has been set.
640 data
["key"] = self
.token
642 # Check if username and hostname are given.
643 elif self
.username
and self
.password
:
645 "user" : self
.username
,
646 "pwd" : self
.password
,
649 # Raise an error if no auth details are given.
651 raise DDNSConfigurationError
653 # Send update to the server.
654 response
= self
.send_request(self
.url
, data
=data
)
656 # This provider sends the response code as part of the header.
657 header
= response
.info()
659 # Get status information from the header.
660 output
= header
.getheader('ddnss-response')
662 # Handle success messages.
663 if output
== "good" or output
== "nochg":
666 # Handle error codes.
667 if output
== "badauth":
668 raise DDNSAuthenticationError
669 elif output
== "notfqdn":
670 raise DDNSRequestError(_("No valid FQDN was given"))
671 elif output
== "nohost":
672 raise DDNSRequestError(_("Specified host does not exist"))
673 elif output
== "911":
674 raise DDNSInternalServerError
675 elif output
== "dnserr":
676 raise DDNSInternalServerError(_("DNS error encountered"))
677 elif output
== "disabled":
678 raise DDNSRequestError(_("Account disabled or locked"))
680 # If we got here, some other update error happened.
681 raise DDNSUpdateError
684 class DDNSProviderDHS(DDNSProvider
):
686 name
= "DHS International"
687 website
= "http://dhs.org/"
688 protocols
= ("ipv4",)
690 # No information about the used update api provided on webpage,
691 # grabed from source code of ez-ipudate.
693 url
= "http://members.dhs.org/nic/hosts"
694 can_remove_records
= False
696 def update_protocol(self
, proto
):
698 "domain" : self
.hostname
,
699 "ip" : self
.get_address(proto
),
701 "hostcmdstage" : "2",
705 # Send update to the server.
706 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
709 # Handle success messages.
710 if response
.code
== 200:
713 # If we got here, some other update error happened.
714 raise DDNSUpdateError
717 class DDNSProviderDNSpark(DDNSProvider
):
718 handle
= "dnspark.com"
720 website
= "http://dnspark.com/"
721 protocols
= ("ipv4",)
723 # Informations to the used api can be found here:
724 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
726 url
= "https://control.dnspark.com/api/dynamic/update.php"
727 can_remove_records
= False
729 def update_protocol(self
, proto
):
731 "domain" : self
.hostname
,
732 "ip" : self
.get_address(proto
),
735 # Send update to the server.
736 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
739 # Get the full response message.
740 output
= response
.read()
742 # Handle success messages.
743 if output
.startswith("ok") or output
.startswith("nochange"):
746 # Handle error codes.
747 if output
== "unauth":
748 raise DDNSAuthenticationError
749 elif output
== "abuse":
751 elif output
== "blocked":
752 raise DDNSBlockedError
753 elif output
== "nofqdn":
754 raise DDNSRequestError(_("No valid FQDN was given"))
755 elif output
== "nohost":
756 raise DDNSRequestError(_("Invalid hostname specified"))
757 elif output
== "notdyn":
758 raise DDNSRequestError(_("Hostname not marked as a dynamic host"))
759 elif output
== "invalid":
760 raise DDNSRequestError(_("Invalid IP address has been sent"))
762 # If we got here, some other update error happened.
763 raise DDNSUpdateError
766 class DDNSProviderDtDNS(DDNSProvider
):
769 website
= "http://dtdns.com/"
770 protocols
= ("ipv4",)
772 # Information about the format of the HTTPS request is to be found
773 # http://www.dtdns.com/dtsite/updatespec
775 url
= "https://www.dtdns.com/api/autodns.cfm"
776 can_remove_records
= False
778 def update_protocol(self
, proto
):
780 "ip" : self
.get_address(proto
),
781 "id" : self
.hostname
,
785 # Send update to the server.
786 response
= self
.send_request(self
.url
, data
=data
)
788 # Get the full response message.
789 output
= response
.read()
791 # Remove all leading and trailing whitespace.
792 output
= output
.strip()
794 # Handle success messages.
795 if "now points to" in output
:
798 # Handle error codes.
799 if output
== "No hostname to update was supplied.":
800 raise DDNSRequestError(_("No hostname specified"))
802 elif output
== "The hostname you supplied is not valid.":
803 raise DDNSRequestError(_("Invalid hostname specified"))
805 elif output
== "The password you supplied is not valid.":
806 raise DDNSAuthenticationError
808 elif output
== "Administration has disabled this account.":
809 raise DDNSRequestError(_("Account has been disabled"))
811 elif output
== "Illegal character in IP.":
812 raise DDNSRequestError(_("Invalid IP address has been sent"))
814 elif output
== "Too many failed requests.":
815 raise DDNSRequestError(_("Too many failed requests"))
817 # If we got here, some other update error happened.
818 raise DDNSUpdateError
821 class DDNSProviderDuckDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
822 handle
= "duckdns.org"
824 website
= "http://www.duckdns.org/"
825 protocols
= ("ipv4",)
827 # Information about the format of the request is to be found
828 # https://www.duckdns.org/install.jsp
830 url
= "https://www.duckdns.org/nic/update"
833 class DDNSProviderDyFi(DDNSProtocolDynDNS2
, DDNSProvider
):
836 website
= "https://www.dy.fi/"
837 protocols
= ("ipv4",)
839 # Information about the format of the request is to be found
840 # https://www.dy.fi/page/clients?lang=en
841 # https://www.dy.fi/page/specification?lang=en
843 url
= "http://www.dy.fi/nic/update"
845 # Please only send automatic updates when your IP address changes,
846 # or once per 5 to 6 days to refresh the address mapping (they will
847 # expire if not refreshed within 7 days).
851 class DDNSProviderDynDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
852 handle
= "dyndns.org"
854 website
= "http://dyn.com/dns/"
855 protocols
= ("ipv4",)
857 # Information about the format of the request is to be found
858 # http://http://dyn.com/support/developers/api/perform-update/
859 # http://dyn.com/support/developers/api/return-codes/
861 url
= "https://members.dyndns.org/nic/update"
864 class DDNSProviderDomainOffensive(DDNSProtocolDynDNS2
, DDNSProvider
):
866 name
= "Domain-Offensive"
867 website
= "https://www.do.de/"
868 protocols
= ("ipv6", "ipv4")
870 # Detailed information about the request and response codes
871 # are available on the providers webpage.
872 # https://www.do.de/wiki/FlexDNS_-_Entwickler
874 url
= "https://ddns.do.de/"
877 class DDNSProviderDynU(DDNSProtocolDynDNS2
, DDNSProvider
):
880 website
= "http://dynu.com/"
881 protocols
= ("ipv6", "ipv4",)
883 # Detailed information about the request and response codes
884 # are available on the providers webpage.
885 # http://dynu.com/Default.aspx?page=dnsapi
887 url
= "https://api.dynu.com/nic/update"
889 # DynU sends the IPv6 and IPv4 address in one request
892 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, "ipv4")
894 # This one supports IPv6
895 myipv6
= self
.get_address("ipv6")
897 # Add update information if we have an IPv6 address.
899 data
["myipv6"] = myipv6
901 self
.send_request(data
)
904 class DDNSProviderEasyDNS(DDNSProvider
):
905 handle
= "easydns.com"
907 website
= "http://www.easydns.com/"
908 protocols
= ("ipv4",)
910 # Detailed information about the request and response codes
911 # (API 1.3) are available on the providers webpage.
912 # https://fusion.easydns.com/index.php?/Knowledgebase/Article/View/102/7/dynamic-dns
914 url
= "http://api.cp.easydns.com/dyn/tomato.php"
916 def update_protocol(self
, proto
):
918 "myip" : self
.get_address(proto
, "-"),
919 "hostname" : self
.hostname
,
922 # Send update to the server.
923 response
= self
.send_request(self
.url
, data
=data
,
924 username
=self
.username
, password
=self
.password
)
926 # Get the full response message.
927 output
= response
.read()
929 # Remove all leading and trailing whitespace.
930 output
= output
.strip()
932 # Handle success messages.
933 if output
.startswith("NOERROR"):
936 # Handle error codes.
937 if output
.startswith("NOACCESS"):
938 raise DDNSAuthenticationError
940 elif output
.startswith("NOSERVICE"):
941 raise DDNSRequestError(_("Dynamic DNS is not turned on for this domain"))
943 elif output
.startswith("ILLEGAL INPUT"):
944 raise DDNSRequestError(_("Invalid data has been sent"))
946 elif output
.startswith("TOOSOON"):
947 raise DDNSRequestError(_("Too frequent update requests have been sent"))
949 # If we got here, some other update error happened.
950 raise DDNSUpdateError
953 class DDNSProviderDomopoli(DDNSProtocolDynDNS2
, DDNSProvider
):
954 handle
= "domopoli.de"
956 website
= "http://domopoli.de/"
957 protocols
= ("ipv4",)
959 # https://www.domopoli.de/?page=howto#DynDns_start
961 url
= "http://dyndns.domopoli.de/nic/update"
964 class DDNSProviderDynsNet(DDNSProvider
):
967 website
= "http://www.dyns.net/"
968 protocols
= ("ipv4",)
969 can_remove_records
= False
971 # There is very detailed informatio about how to send the update request and
972 # the possible response codes. (Currently we are using the v1.1 proto)
973 # http://www.dyns.net/documentation/technical/protocol/
975 url
= "http://www.dyns.net/postscript011.php"
977 def update_protocol(self
, proto
):
979 "ip" : self
.get_address(proto
),
980 "host" : self
.hostname
,
981 "username" : self
.username
,
982 "password" : self
.password
,
985 # Send update to the server.
986 response
= self
.send_request(self
.url
, data
=data
)
988 # Get the full response message.
989 output
= response
.read()
991 # Handle success messages.
992 if output
.startswith("200"):
995 # Handle error codes.
996 if output
.startswith("400"):
997 raise DDNSRequestError(_("Malformed request has been sent"))
998 elif output
.startswith("401"):
999 raise DDNSAuthenticationError
1000 elif output
.startswith("402"):
1001 raise DDNSRequestError(_("Too frequent update requests have been sent"))
1002 elif output
.startswith("403"):
1003 raise DDNSInternalServerError
1005 # If we got here, some other update error happened.
1006 raise DDNSUpdateError(_("Server response: %s") % output
)
1009 class DDNSProviderEnomCom(DDNSResponseParserXML
, DDNSProvider
):
1012 website
= "http://www.enom.com/"
1013 protocols
= ("ipv4",)
1015 # There are very detailed information about how to send an update request and
1016 # the respone codes.
1017 # http://www.enom.com/APICommandCatalog/
1019 url
= "https://dynamic.name-services.com/interface.asp"
1020 can_remove_records
= False
1022 def update_protocol(self
, proto
):
1024 "command" : "setdnshost",
1025 "responsetype" : "xml",
1026 "address" : self
.get_address(proto
),
1027 "domainpassword" : self
.password
,
1028 "zone" : self
.hostname
1031 # Send update to the server.
1032 response
= self
.send_request(self
.url
, data
=data
)
1034 # Get the full response message.
1035 output
= response
.read()
1037 # Handle success messages.
1038 if self
.get_xml_tag_value(output
, "ErrCount") == "0":
1041 # Handle error codes.
1042 errorcode
= self
.get_xml_tag_value(output
, "ResponseNumber")
1044 if errorcode
== "304155":
1045 raise DDNSAuthenticationError
1046 elif errorcode
== "304153":
1047 raise DDNSRequestError(_("Domain not found"))
1049 # If we got here, some other update error happened.
1050 raise DDNSUpdateError
1053 class DDNSProviderEntryDNS(DDNSProvider
):
1054 handle
= "entrydns.net"
1056 website
= "http://entrydns.net/"
1057 protocols
= ("ipv4",)
1059 # Some very tiny details about their so called "Simple API" can be found
1060 # here: https://entrydns.net/help
1061 url
= "https://entrydns.net/records/modify"
1062 can_remove_records
= False
1064 def update_protocol(self
, proto
):
1066 "ip" : self
.get_address(proto
),
1069 # Add auth token to the update url.
1070 url
= "%s/%s" % (self
.url
, self
.token
)
1072 # Send update to the server.
1074 response
= self
.send_request(url
, data
=data
)
1076 # Handle error codes
1077 except urllib2
.HTTPError
, e
:
1079 raise DDNSAuthenticationError
1082 raise DDNSRequestError(_("An invalid IP address was submitted"))
1086 # Handle success messages.
1087 if response
.code
== 200:
1090 # If we got here, some other update error happened.
1091 raise DDNSUpdateError
1094 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider
):
1095 handle
= "freedns.afraid.org"
1096 name
= "freedns.afraid.org"
1097 website
= "http://freedns.afraid.org/"
1099 # No information about the request or response could be found on the vendor
1100 # page. All used values have been collected by testing.
1101 url
= "https://freedns.afraid.org/dynamic/update.php"
1102 can_remove_records
= False
1104 def update_protocol(self
, proto
):
1106 "address" : self
.get_address(proto
),
1109 # Add auth token to the update url.
1110 url
= "%s?%s" % (self
.url
, self
.token
)
1112 # Send update to the server.
1113 response
= self
.send_request(url
, data
=data
)
1115 # Get the full response message.
1116 output
= response
.read()
1118 # Handle success messages.
1119 if output
.startswith("Updated") or "has not changed" in output
:
1122 # Handle error codes.
1123 if output
== "ERROR: Unable to locate this record":
1124 raise DDNSAuthenticationError
1125 elif "is an invalid IP address" in output
:
1126 raise DDNSRequestError(_("Invalid IP address has been sent"))
1128 # If we got here, some other update error happened.
1129 raise DDNSUpdateError
1132 class DDNSProviderItsdns(DDNSProtocolDynDNS2
, DDNSProvider
):
1133 handle
= "itsdns.de"
1135 website
= "http://www.itsdns.de/"
1136 protocols
= ("ipv6", "ipv4")
1138 # Information about the format of the HTTP request is to be found
1139 # here: https://www.itsdns.de/dynupdatehelp.htm
1141 url
= "https://www.itsdns.de/update.php"
1144 class DDNSProviderJoker(DDNSProtocolDynDNS2
, DDNSProvider
):
1145 handle
= "joker.com"
1146 name
= "Joker.com Dynamic DNS"
1147 website
= "https://joker.com/"
1148 protocols
= ("ipv4",)
1150 # Information about the request can be found here:
1151 # https://joker.com/faq/content/11/427/en/what-is-dynamic-dns-dyndns.html
1152 # Using DynDNS V2 protocol over HTTPS here
1154 url
= "https://svc.joker.com/nic/update"
1157 class DDNSProviderGoogle(DDNSProtocolDynDNS2
, DDNSProvider
):
1158 handle
= "domains.google.com"
1159 name
= "Google Domains"
1160 website
= "https://domains.google.com/"
1161 protocols
= ("ipv4",)
1163 # Information about the format of the HTTP request is to be found
1164 # here: https://support.google.com/domains/answer/6147083?hl=en
1166 url
= "https://domains.google.com/nic/update"
1169 class DDNSProviderLightningWireLabs(DDNSProvider
):
1170 handle
= "dns.lightningwirelabs.com"
1171 name
= "Lightning Wire Labs DNS Service"
1172 website
= "http://dns.lightningwirelabs.com/"
1174 # Information about the format of the HTTPS request is to be found
1175 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
1177 url
= "https://dns.lightningwirelabs.com/update"
1181 "hostname" : self
.hostname
,
1182 "address6" : self
.get_address("ipv6", "-"),
1183 "address4" : self
.get_address("ipv4", "-"),
1186 # Check if a token has been set.
1188 data
["token"] = self
.token
1190 # Check for username and password.
1191 elif self
.username
and self
.password
:
1193 "username" : self
.username
,
1194 "password" : self
.password
,
1197 # Raise an error if no auth details are given.
1199 raise DDNSConfigurationError
1201 # Send update to the server.
1202 response
= self
.send_request(self
.url
, data
=data
)
1204 # Handle success messages.
1205 if response
.code
== 200:
1208 # If we got here, some other update error happened.
1209 raise DDNSUpdateError
1212 class DDNSProviderLoopia(DDNSProtocolDynDNS2
, DDNSProvider
):
1213 handle
= "loopia.se"
1215 website
= "https://www.loopia.com"
1216 protocols
= ("ipv4",)
1218 # Information about the format of the HTTP request is to be found
1219 # here: https://support.loopia.com/wiki/About_the_DynDNS_support
1221 url
= "https://dns.loopia.se/XDynDNSServer/XDynDNS.php"
1224 class DDNSProviderMyOnlinePortal(DDNSProtocolDynDNS2
, DDNSProvider
):
1225 handle
= "myonlineportal.net"
1226 name
= "myonlineportal.net"
1227 website
= "https:/myonlineportal.net/"
1229 # Information about the request and response can be obtained here:
1230 # https://myonlineportal.net/howto_dyndns
1232 url
= "https://myonlineportal.net/updateddns"
1234 def prepare_request_data(self
, proto
):
1236 "hostname" : self
.hostname
,
1237 "ip" : self
.get_address(proto
),
1243 class DDNSProviderNamecheap(DDNSResponseParserXML
, DDNSProvider
):
1244 handle
= "namecheap.com"
1246 website
= "http://namecheap.com"
1247 protocols
= ("ipv4",)
1249 # Information about the format of the HTTP request is to be found
1250 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
1251 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
1253 url
= "https://dynamicdns.park-your-domain.com/update"
1254 can_remove_records
= False
1256 def update_protocol(self
, proto
):
1257 # Namecheap requires the hostname splitted into a host and domain part.
1258 host
, domain
= self
.hostname
.split(".", 1)
1260 # Get and store curent IP address.
1261 address
= self
.get_address(proto
)
1265 "password" : self
.password
,
1270 # Send update to the server.
1271 response
= self
.send_request(self
.url
, data
=data
)
1273 # Get the full response message.
1274 output
= response
.read()
1276 # Handle success messages.
1277 if self
.get_xml_tag_value(output
, "IP") == address
:
1280 # Handle error codes.
1281 errorcode
= self
.get_xml_tag_value(output
, "ResponseNumber")
1283 if errorcode
== "304156":
1284 raise DDNSAuthenticationError
1285 elif errorcode
== "316153":
1286 raise DDNSRequestError(_("Domain not found"))
1287 elif errorcode
== "316154":
1288 raise DDNSRequestError(_("Domain not active"))
1289 elif errorcode
in ("380098", "380099"):
1290 raise DDNSInternalServerError
1292 # If we got here, some other update error happened.
1293 raise DDNSUpdateError
1296 class DDNSProviderNOIP(DDNSProtocolDynDNS2
, DDNSProvider
):
1297 handle
= "no-ip.com"
1299 website
= "http://www.no-ip.com/"
1300 protocols
= ("ipv4",)
1302 # Information about the format of the HTTP request is to be found
1303 # here: http://www.no-ip.com/integrate/request and
1304 # here: http://www.no-ip.com/integrate/response
1306 url
= "http://dynupdate.no-ip.com/nic/update"
1308 def prepare_request_data(self
, proto
):
1309 assert proto
== "ipv4"
1312 "hostname" : self
.hostname
,
1313 "address" : self
.get_address(proto
),
1319 class DDNSProviderNowDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1320 handle
= "now-dns.com"
1322 website
= "http://now-dns.com/"
1323 protocols
= ("ipv6", "ipv4")
1325 # Information about the format of the request is to be found
1326 # but only can be accessed by register an account and login
1327 # https://now-dns.com/?m=api
1329 url
= "https://now-dns.com/update"
1332 class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2
, DDNSProvider
):
1333 handle
= "nsupdate.info"
1334 name
= "nsupdate.info"
1335 website
= "http://nsupdate.info/"
1336 protocols
= ("ipv6", "ipv4",)
1338 # Information about the format of the HTTP request can be found
1339 # after login on the provider user interface and here:
1340 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
1342 url
= "https://nsupdate.info/nic/update"
1344 # TODO nsupdate.info can actually do this, but the functionality
1345 # has not been implemented here, yet.
1346 can_remove_records
= False
1348 # After a failed update, there will be no retries
1349 # https://bugzilla.ipfire.org/show_bug.cgi?id=10603
1350 holdoff_failure_days
= None
1352 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
1353 # and for the password a so called secret.
1356 return self
.get("hostname")
1360 return self
.token
or self
.get("secret")
1362 def prepare_request_data(self
, proto
):
1364 "myip" : self
.get_address(proto
),
1370 class DDNSProviderOpenDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1371 handle
= "opendns.com"
1373 website
= "http://www.opendns.com"
1375 # Detailed information about the update request and possible
1376 # response codes can be obtained from here:
1377 # https://support.opendns.com/entries/23891440
1379 url
= "https://updates.opendns.com/nic/update"
1381 def prepare_request_data(self
, proto
):
1383 "hostname" : self
.hostname
,
1384 "myip" : self
.get_address(proto
),
1390 class DDNSProviderOVH(DDNSProtocolDynDNS2
, DDNSProvider
):
1393 website
= "http://www.ovh.com/"
1394 protocols
= ("ipv4",)
1396 # OVH only provides very limited information about how to
1397 # update a DynDNS host. They only provide the update url
1398 # on the their german subpage.
1400 # http://hilfe.ovh.de/DomainDynHost
1402 url
= "https://www.ovh.com/nic/update"
1404 def prepare_request_data(self
, proto
):
1405 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1407 "system" : "dyndns",
1413 class DDNSProviderRegfish(DDNSProvider
):
1414 handle
= "regfish.com"
1415 name
= "Regfish GmbH"
1416 website
= "http://www.regfish.com/"
1418 # A full documentation to the providers api can be found here
1419 # but is only available in german.
1420 # https://www.regfish.de/domains/dyndns/dokumentation
1422 url
= "https://dyndns.regfish.de/"
1423 can_remove_records
= False
1427 "fqdn" : self
.hostname
,
1430 # Check if we update an IPv6 address.
1431 address6
= self
.get_address("ipv6")
1433 data
["ipv6"] = address6
1435 # Check if we update an IPv4 address.
1436 address4
= self
.get_address("ipv4")
1438 data
["ipv4"] = address4
1440 # Raise an error if none address is given.
1441 if not data
.has_key("ipv6") and not data
.has_key("ipv4"):
1442 raise DDNSConfigurationError
1444 # Check if a token has been set.
1446 data
["token"] = self
.token
1448 # Raise an error if no token and no useranem and password
1450 elif not self
.username
and not self
.password
:
1451 raise DDNSConfigurationError(_("No Auth details specified"))
1453 # HTTP Basic Auth is only allowed if no token is used.
1455 # Send update to the server.
1456 response
= self
.send_request(self
.url
, data
=data
)
1458 # Send update to the server.
1459 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
1462 # Get the full response message.
1463 output
= response
.read()
1465 # Handle success messages.
1466 if "100" in output
or "101" in output
:
1469 # Handle error codes.
1470 if "401" or "402" in output
:
1471 raise DDNSAuthenticationError
1472 elif "408" in output
:
1473 raise DDNSRequestError(_("Invalid IPv4 address has been sent"))
1474 elif "409" in output
:
1475 raise DDNSRequestError(_("Invalid IPv6 address has been sent"))
1476 elif "412" in output
:
1477 raise DDNSRequestError(_("No valid FQDN was given"))
1478 elif "414" in output
:
1479 raise DDNSInternalServerError
1481 # If we got here, some other update error happened.
1482 raise DDNSUpdateError
1485 class DDNSProviderSchokokeksDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1486 handle
= "schokokeks.org"
1488 website
= "http://www.schokokeks.org/"
1489 protocols
= ("ipv4",)
1491 # Information about the format of the request is to be found
1492 # https://wiki.schokokeks.org/DynDNS
1493 url
= "https://dyndns.schokokeks.org/nic/update"
1496 class DDNSProviderSelfhost(DDNSProtocolDynDNS2
, DDNSProvider
):
1497 handle
= "selfhost.de"
1498 name
= "Selfhost.de"
1499 website
= "http://www.selfhost.de/"
1500 protocols
= ("ipv4",)
1502 url
= "https://carol.selfhost.de/nic/update"
1504 def prepare_request_data(self
, proto
):
1505 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1513 class DDNSProviderServercow(DDNSProvider
):
1514 handle
= "servercow.de"
1515 name
= "servercow.de"
1516 website
= "https://servercow.de/"
1517 protocols
= ("ipv4", "ipv6")
1519 url
= "https://www.servercow.de/dnsupdate/update.php"
1520 can_remove_records
= False
1522 def update_protocol(self
, proto
):
1524 "ipaddr" : self
.get_address(proto
),
1525 "hostname" : self
.hostname
,
1526 "username" : self
.username
,
1527 "pass" : self
.password
,
1530 # Send request to provider
1531 response
= self
.send_request(self
.url
, data
=data
)
1534 output
= response
.read()
1536 # Server responds with OK if update was successful
1537 if output
.startswith("OK"):
1541 elif output
.startswith("FAILED - Authentication failed"):
1542 raise DDNSAuthenticationError
1544 # If we got here, some other update error happened
1545 raise DDNSUpdateError(output
)
1548 class DDNSProviderSPDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1549 handle
= "spdns.org"
1551 website
= "https://www.spdyn.de/"
1553 # Detailed information about request and response codes are provided
1554 # by the vendor. They are using almost the same mechanism and status
1555 # codes as dyndns.org so we can inherit all those stuff.
1557 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
1558 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
1560 url
= "https://update.spdyn.de/nic/update"
1564 return self
.get("username") or self
.hostname
1568 return self
.get("password") or self
.token
1571 class DDNSProviderStrato(DDNSProtocolDynDNS2
, DDNSProvider
):
1572 handle
= "strato.com"
1574 website
= "http:/www.strato.com/"
1575 protocols
= ("ipv4",)
1577 # Information about the request and response can be obtained here:
1578 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
1580 url
= "https://dyndns.strato.com/nic/update"
1582 def prepare_request_data(self
, proto
):
1583 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1586 "backupmx" : "NOCHG"
1592 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1593 handle
= "twodns.de"
1595 website
= "http://www.twodns.de"
1596 protocols
= ("ipv4",)
1598 # Detailed information about the request can be found here
1599 # http://twodns.de/en/faqs
1600 # http://twodns.de/en/api
1602 url
= "https://update.twodns.de/update"
1604 def prepare_request_data(self
, proto
):
1605 assert proto
== "ipv4"
1608 "ip" : self
.get_address(proto
),
1609 "hostname" : self
.hostname
1615 class DDNSProviderUdmedia(DDNSProtocolDynDNS2
, DDNSProvider
):
1616 handle
= "udmedia.de"
1617 name
= "Udmedia GmbH"
1618 website
= "http://www.udmedia.de"
1619 protocols
= ("ipv4",)
1621 # Information about the request can be found here
1622 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1624 url
= "https://www.udmedia.de/nic/update"
1627 class DDNSProviderVariomedia(DDNSProtocolDynDNS2
, DDNSProvider
):
1628 handle
= "variomedia.de"
1630 website
= "http://www.variomedia.de/"
1631 protocols
= ("ipv6", "ipv4",)
1633 # Detailed information about the request can be found here
1634 # https://dyndns.variomedia.de/
1636 url
= "https://dyndns.variomedia.de/nic/update"
1638 def prepare_request_data(self
, proto
):
1640 "hostname" : self
.hostname
,
1641 "myip" : self
.get_address(proto
),
1647 class DDNSProviderXLhost(DDNSProtocolDynDNS2
, DDNSProvider
):
1648 handle
= "xlhost.de"
1650 website
= "http://xlhost.de/"
1651 protocols
= ("ipv4",)
1653 # Information about the format of the HTTP request is to be found
1654 # here: https://xlhost.de/faq/index_html?topicId=CQA2ELIPO4SQ
1656 url
= "https://nsupdate.xlhost.de/"
1659 class DDNSProviderZoneedit(DDNSProvider
):
1660 handle
= "zoneedit.com"
1662 website
= "http://www.zoneedit.com"
1663 protocols
= ("ipv4",)
1665 # Detailed information about the request and the response codes can be
1667 # http://www.zoneedit.com/doc/api/other.html
1668 # http://www.zoneedit.com/faq.html
1670 url
= "https://dynamic.zoneedit.com/auth/dynamic.html"
1672 def update_protocol(self
, proto
):
1674 "dnsto" : self
.get_address(proto
),
1675 "host" : self
.hostname
1678 # Send update to the server.
1679 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
1682 # Get the full response message.
1683 output
= response
.read()
1685 # Handle success messages.
1686 if output
.startswith("<SUCCESS"):
1689 # Handle error codes.
1690 if output
.startswith("invalid login"):
1691 raise DDNSAuthenticationError
1692 elif output
.startswith("<ERROR CODE=\"704\""):
1693 raise DDNSRequestError(_("No valid FQDN was given"))
1694 elif output
.startswith("<ERROR CODE=\"702\""):
1695 raise DDNSRequestError(_("Too frequent update requests have been sent"))
1697 # If we got here, some other update error happened.
1698 raise DDNSUpdateError
1701 class DDNSProviderDNSmadeEasy(DDNSProvider
):
1702 handle
= "dnsmadeeasy.com"
1703 name
= "DNSmadeEasy.com"
1704 website
= "http://www.dnsmadeeasy.com/"
1705 protocols
= ("ipv4",)
1707 # DNS Made Easy Nameserver Provider also offering Dynamic DNS
1708 # Documentation can be found here:
1709 # http://www.dnsmadeeasy.com/dynamic-dns/
1711 url
= "https://cp.dnsmadeeasy.com/servlet/updateip?"
1712 can_remove_records
= False
1714 def update_protocol(self
, proto
):
1716 "ip" : self
.get_address(proto
),
1717 "id" : self
.hostname
,
1718 "username" : self
.username
,
1719 "password" : self
.password
,
1722 # Send update to the server.
1723 response
= self
.send_request(self
.url
, data
=data
)
1725 # Get the full response message.
1726 output
= response
.read()
1728 # Handle success messages.
1729 if output
.startswith("success") or output
.startswith("error-record-ip-same"):
1732 # Handle error codes.
1733 if output
.startswith("error-auth-suspend"):
1734 raise DDNSRequestError(_("Account has been suspended"))
1736 elif output
.startswith("error-auth-voided"):
1737 raise DDNSRequestError(_("Account has been revoked"))
1739 elif output
.startswith("error-record-invalid"):
1740 raise DDNSRequestError(_("Specified host does not exist"))
1742 elif output
.startswith("error-auth"):
1743 raise DDNSAuthenticationError
1745 # If we got here, some other update error happened.
1746 raise DDNSUpdateError(_("Server response: %s") % output
)
1749 class DDNSProviderZZZZ(DDNSProvider
):
1752 website
= "https://zzzz.io"
1753 protocols
= ("ipv6", "ipv4",)
1755 # Detailed information about the update request can be found here:
1756 # https://zzzz.io/faq/
1758 # Details about the possible response codes have been provided in the bugtracker:
1759 # https://bugzilla.ipfire.org/show_bug.cgi?id=10584#c2
1761 url
= "https://zzzz.io/api/v1/update"
1762 can_remove_records
= False
1764 def update_protocol(self
, proto
):
1766 "ip" : self
.get_address(proto
),
1767 "token" : self
.token
,
1771 data
["type"] = "aaaa"
1773 # zzzz uses the host from the full hostname as part
1774 # of the update url.
1775 host
, domain
= self
.hostname
.split(".", 1)
1777 # Add host value to the update url.
1778 url
= "%s/%s" % (self
.url
, host
)
1780 # Send update to the server.
1782 response
= self
.send_request(url
, data
=data
)
1784 # Handle error codes.
1785 except DDNSNotFound
:
1786 raise DDNSRequestError(_("Invalid hostname specified"))
1788 # Handle success messages.
1789 if response
.code
== 200:
1792 # If we got here, some other update error happened.
1793 raise DDNSUpdateError