]> git.ipfire.org Git - people/ms/ddns.git/blob - src/ddns/providers.py
Handle HTTP errors as early as possible.
[people/ms/ddns.git] / src / ddns / providers.py
1 #!/usr/bin/python
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 ###############################################################################
21
22 import logging
23 import urllib2
24 import xml.dom.minidom
25
26 from i18n import _
27
28 # Import all possible exception types.
29 from .errors import *
30
31 logger = logging.getLogger("ddns.providers")
32 logger.propagate = 1
33
34 class DDNSProvider(object):
35 INFO = {
36 # A short string that uniquely identifies
37 # this provider.
38 "handle" : None,
39
40 # The full name of the provider.
41 "name" : None,
42
43 # A weburl to the homepage of the provider.
44 # (Where to register a new account?)
45 "website" : None,
46
47 # A list of supported protocols.
48 "protocols" : ["ipv6", "ipv4"],
49 }
50
51 DEFAULT_SETTINGS = {}
52
53 def __init__(self, core, **settings):
54 self.core = core
55
56 # Copy a set of default settings and
57 # update them by those from the configuration file.
58 self.settings = self.DEFAULT_SETTINGS.copy()
59 self.settings.update(settings)
60
61 def __repr__(self):
62 return "<DDNS Provider %s (%s)>" % (self.name, self.handle)
63
64 def __cmp__(self, other):
65 return cmp(self.hostname, other.hostname)
66
67 @property
68 def name(self):
69 """
70 Returns the name of the provider.
71 """
72 return self.INFO.get("name")
73
74 @property
75 def website(self):
76 """
77 Returns the website URL of the provider
78 or None if that is not available.
79 """
80 return self.INFO.get("website", None)
81
82 @property
83 def handle(self):
84 """
85 Returns the handle of this provider.
86 """
87 return self.INFO.get("handle")
88
89 def get(self, key, default=None):
90 """
91 Get a setting from the settings dictionary.
92 """
93 return self.settings.get(key, default)
94
95 @property
96 def hostname(self):
97 """
98 Fast access to the hostname.
99 """
100 return self.get("hostname")
101
102 @property
103 def username(self):
104 """
105 Fast access to the username.
106 """
107 return self.get("username")
108
109 @property
110 def password(self):
111 """
112 Fast access to the password.
113 """
114 return self.get("password")
115
116 @property
117 def protocols(self):
118 return self.INFO.get("protocols")
119
120 @property
121 def token(self):
122 """
123 Fast access to the token.
124 """
125 return self.get("token")
126
127 def __call__(self, force=False):
128 if force:
129 logger.info(_("Updating %s forced") % self.hostname)
130
131 # Check if we actually need to update this host.
132 elif self.is_uptodate(self.protocols):
133 logger.info(_("%s is already up to date") % self.hostname)
134 return
135
136 # Execute the update.
137 self.update()
138
139 def update(self):
140 raise NotImplementedError
141
142 def is_uptodate(self, protos):
143 """
144 Returns True if this host is already up to date
145 and does not need to change the IP address on the
146 name server.
147 """
148 for proto in protos:
149 addresses = self.core.system.resolve(self.hostname, proto)
150
151 current_address = self.get_address(proto)
152
153 # If no addresses for the given protocol exist, we
154 # are fine...
155 if current_address is None and not addresses:
156 continue
157
158 if not current_address in addresses:
159 return False
160
161 return True
162
163 def send_request(self, *args, **kwargs):
164 """
165 Proxy connection to the send request
166 method.
167 """
168 return self.core.system.send_request(*args, **kwargs)
169
170 def get_address(self, proto):
171 """
172 Proxy method to get the current IP address.
173 """
174 return self.core.system.get_address(proto)
175
176
177 class DDNSProviderAllInkl(DDNSProvider):
178 INFO = {
179 "handle" : "all-inkl.com",
180 "name" : "All-inkl.com",
181 "website" : "http://all-inkl.com/",
182 "protocols" : ["ipv4",]
183 }
184
185 # There are only information provided by the vendor how to
186 # perform an update on a FRITZ Box. Grab requried informations
187 # from the net.
188 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
189
190 url = "http://dyndns.kasserver.com"
191
192 def update(self):
193 # There is no additional data required so we directly can
194 # send our request.
195 response = self.send_request(self.url, username=self.username, password=self.password)
196
197 # Get the full response message.
198 output = response.read()
199
200 # Handle success messages.
201 if output.startswith("good") or output.startswith("nochg"):
202 return
203
204 # If we got here, some other update error happened.
205 raise DDNSUpdateError
206
207
208 class DDNSProviderDHS(DDNSProvider):
209 INFO = {
210 "handle" : "dhs.org",
211 "name" : "DHS International",
212 "website" : "http://dhs.org/",
213 "protocols" : ["ipv4",]
214 }
215
216 # No information about the used update api provided on webpage,
217 # grabed from source code of ez-ipudate.
218 url = "http://members.dhs.org/nic/hosts"
219
220 def update(self):
221 data = {
222 "domain" : self.hostname,
223 "ip" : self.get_address("ipv4"),
224 "hostcmd" : "edit",
225 "hostcmdstage" : "2",
226 "type" : "4",
227 }
228
229 # Send update to the server.
230 response = self.send_request(self.url, username=self.username, password=self.password,
231 data=data)
232
233 # Handle success messages.
234 if response.code == 200:
235 return
236
237 # If we got here, some other update error happened.
238 raise DDNSUpdateError
239
240
241 class DDNSProviderDNSpark(DDNSProvider):
242 INFO = {
243 "handle" : "dnspark.com",
244 "name" : "DNS Park",
245 "website" : "http://dnspark.com/",
246 "protocols" : ["ipv4",]
247 }
248
249 # Informations to the used api can be found here:
250 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
251 url = "https://control.dnspark.com/api/dynamic/update.php"
252
253 def update(self):
254 data = {
255 "domain" : self.hostname,
256 "ip" : self.get_address("ipv4"),
257 }
258
259 # Send update to the server.
260 response = self.send_request(self.url, username=self.username, password=self.password,
261 data=data)
262
263 # Get the full response message.
264 output = response.read()
265
266 # Handle success messages.
267 if output.startswith("ok") or output.startswith("nochange"):
268 return
269
270 # Handle error codes.
271 if output == "unauth":
272 raise DDNSAuthenticationError
273 elif output == "abuse":
274 raise DDNSAbuseError
275 elif output == "blocked":
276 raise DDNSBlockedError
277 elif output == "nofqdn":
278 raise DDNSRequestError(_("No valid FQDN was given."))
279 elif output == "nohost":
280 raise DDNSRequestError(_("Invalid hostname specified."))
281 elif output == "notdyn":
282 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
283 elif output == "invalid":
284 raise DDNSRequestError(_("Invalid IP address has been sent."))
285
286 # If we got here, some other update error happened.
287 raise DDNSUpdateError
288
289
290 class DDNSProviderDtDNS(DDNSProvider):
291 INFO = {
292 "handle" : "dtdns.com",
293 "name" : "DtDNS",
294 "website" : "http://dtdns.com/",
295 "protocols" : ["ipv4",]
296 }
297
298 # Information about the format of the HTTPS request is to be found
299 # http://www.dtdns.com/dtsite/updatespec
300 url = "https://www.dtdns.com/api/autodns.cfm"
301
302 def update(self):
303 data = {
304 "ip" : self.get_address("ipv4"),
305 "id" : self.hostname,
306 "pw" : self.password
307 }
308
309 # Send update to the server.
310 response = self.send_request(self.url, data=data)
311
312 # Get the full response message.
313 output = response.read()
314
315 # Remove all leading and trailing whitespace.
316 output = output.strip()
317
318 # Handle success messages.
319 if "now points to" in output:
320 return
321
322 # Handle error codes.
323 if output == "No hostname to update was supplied.":
324 raise DDNSRequestError(_("No hostname specified."))
325
326 elif output == "The hostname you supplied is not valid.":
327 raise DDNSRequestError(_("Invalid hostname specified."))
328
329 elif output == "The password you supplied is not valid.":
330 raise DDNSAuthenticationError
331
332 elif output == "Administration has disabled this account.":
333 raise DDNSRequestError(_("Account has been disabled."))
334
335 elif output == "Illegal character in IP.":
336 raise DDNSRequestError(_("Invalid IP address has been sent."))
337
338 elif output == "Too many failed requests.":
339 raise DDNSRequestError(_("Too many failed requests."))
340
341 # If we got here, some other update error happened.
342 raise DDNSUpdateError
343
344
345 class DDNSProviderDynDNS(DDNSProvider):
346 INFO = {
347 "handle" : "dyndns.org",
348 "name" : "Dyn",
349 "website" : "http://dyn.com/dns/",
350 "protocols" : ["ipv4",]
351 }
352
353 # Information about the format of the request is to be found
354 # http://http://dyn.com/support/developers/api/perform-update/
355 # http://dyn.com/support/developers/api/return-codes/
356 url = "https://members.dyndns.org/nic/update"
357
358 def _prepare_request_data(self):
359 data = {
360 "hostname" : self.hostname,
361 "myip" : self.get_address("ipv4"),
362 }
363
364 return data
365
366 def update(self):
367 data = self._prepare_request_data()
368
369 # Send update to the server.
370 response = self.send_request(self.url, data=data,
371 username=self.username, password=self.password)
372
373 # Get the full response message.
374 output = response.read()
375
376 # Handle success messages.
377 if output.startswith("good") or output.startswith("nochg"):
378 return
379
380 # Handle error codes.
381 if output == "badauth":
382 raise DDNSAuthenticationError
383 elif output == "aduse":
384 raise DDNSAbuseError
385 elif output == "notfqdn":
386 raise DDNSRequestError(_("No valid FQDN was given."))
387 elif output == "nohost":
388 raise DDNSRequestError(_("Specified host does not exist."))
389 elif output == "911":
390 raise DDNSInternalServerError
391 elif output == "dnserr":
392 raise DDNSInternalServerError(_("DNS error encountered."))
393
394 # If we got here, some other update error happened.
395 raise DDNSUpdateError(_("Server response: %s") % output)
396
397
398 class DDNSProviderDynU(DDNSProviderDynDNS):
399 INFO = {
400 "handle" : "dynu.com",
401 "name" : "Dynu",
402 "website" : "http://dynu.com/",
403 "protocols" : ["ipv6", "ipv4",]
404 }
405
406
407 # Detailed information about the request and response codes
408 # are available on the providers webpage.
409 # http://dynu.com/Default.aspx?page=dnsapi
410
411 url = "https://api.dynu.com/nic/update"
412
413 def _prepare_request_data(self):
414 data = DDNSProviderDynDNS._prepare_request_data(self)
415
416 # This one supports IPv6
417 data.update({
418 "myipv6" : self.get_address("ipv6"),
419 })
420
421 return data
422
423
424 class DDNSProviderEasyDNS(DDNSProviderDynDNS):
425 INFO = {
426 "handle" : "easydns.com",
427 "name" : "EasyDNS",
428 "website" : "http://www.easydns.com/",
429 "protocols" : ["ipv4",]
430 }
431
432 # There is only some basic documentation provided by the vendor,
433 # also searching the web gain very poor results.
434 # http://mediawiki.easydns.com/index.php/Dynamic_DNS
435
436 url = "http://api.cp.easydns.com/dyn/tomato.php"
437
438
439 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider):
440 INFO = {
441 "handle" : "freedns.afraid.org",
442 "name" : "freedns.afraid.org",
443 "website" : "http://freedns.afraid.org/",
444 "protocols" : ["ipv6", "ipv4",]
445 }
446
447 # No information about the request or response could be found on the vendor
448 # page. All used values have been collected by testing.
449 url = "https://freedns.afraid.org/dynamic/update.php"
450
451 @property
452 def proto(self):
453 return self.get("proto")
454
455 def update(self):
456 address = self.get_address(self.proto)
457
458 data = {
459 "address" : address,
460 }
461
462 # Add auth token to the update url.
463 url = "%s?%s" % (self.url, self.token)
464
465 # Send update to the server.
466 response = self.send_request(url, data=data)
467
468 if output.startswith("Updated") or "has not changed" in output:
469 return
470
471 # Handle error codes.
472 if output == "ERROR: Unable to locate this record":
473 raise DDNSAuthenticationError
474 elif "is an invalid IP address" in output:
475 raise DDNSRequestError(_("Invalid IP address has been sent."))
476
477
478 class DDNSProviderLightningWireLabs(DDNSProvider):
479 INFO = {
480 "handle" : "dns.lightningwirelabs.com",
481 "name" : "Lightning Wire Labs",
482 "website" : "http://dns.lightningwirelabs.com/",
483 "protocols" : ["ipv6", "ipv4",]
484 }
485
486 # Information about the format of the HTTPS request is to be found
487 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
488 url = "https://dns.lightningwirelabs.com/update"
489
490 def update(self):
491 data = {
492 "hostname" : self.hostname,
493 }
494
495 # Check if we update an IPv6 address.
496 address6 = self.get_address("ipv6")
497 if address6:
498 data["address6"] = address6
499
500 # Check if we update an IPv4 address.
501 address4 = self.get_address("ipv4")
502 if address4:
503 data["address4"] = address4
504
505 # Raise an error if none address is given.
506 if not data.has_key("address6") and not data.has_key("address4"):
507 raise DDNSConfigurationError
508
509 # Check if a token has been set.
510 if self.token:
511 data["token"] = self.token
512
513 # Check for username and password.
514 elif self.username and self.password:
515 data.update({
516 "username" : self.username,
517 "password" : self.password,
518 })
519
520 # Raise an error if no auth details are given.
521 else:
522 raise DDNSConfigurationError
523
524 # Send update to the server.
525 response = self.send_request(self.url, data=data)
526
527 # Handle success messages.
528 if response.code == 200:
529 return
530
531 # If we got here, some other update error happened.
532 raise DDNSUpdateError
533
534
535 class DDNSProviderNamecheap(DDNSProvider):
536 INFO = {
537 "handle" : "namecheap.com",
538 "name" : "Namecheap",
539 "website" : "http://namecheap.com",
540 "protocols" : ["ipv4",]
541 }
542
543 # Information about the format of the HTTP request is to be found
544 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
545 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
546
547 url = "https://dynamicdns.park-your-domain.com/update"
548
549 def parse_xml(self, document, content):
550 # Send input to the parser.
551 xmldoc = xml.dom.minidom.parseString(document)
552
553 # Get XML elements by the given content.
554 element = xmldoc.getElementsByTagName(content)
555
556 # If no element has been found, we directly can return None.
557 if not element:
558 return None
559
560 # Only get the first child from an element, even there are more than one.
561 firstchild = element[0].firstChild
562
563 # Get the value of the child.
564 value = firstchild.nodeValue
565
566 # Return the value.
567 return value
568
569 def update(self):
570 # Namecheap requires the hostname splitted into a host and domain part.
571 host, domain = self.hostname.split(".", 1)
572
573 data = {
574 "ip" : self.get_address("ipv4"),
575 "password" : self.password,
576 "host" : host,
577 "domain" : domain
578 }
579
580 # Send update to the server.
581 response = self.send_request(self.url, data=data)
582
583 # Get the full response message.
584 output = response.read()
585
586 # Handle success messages.
587 if self.parse_xml(output, "IP") == self.get_address("ipv4"):
588 return
589
590 # Handle error codes.
591 errorcode = self.parse_xml(output, "ResponseNumber")
592
593 if errorcode == "304156":
594 raise DDNSAuthenticationError
595 elif errorcode == "316153":
596 raise DDNSRequestError(_("Domain not found."))
597 elif errorcode == "316154":
598 raise DDNSRequestError(_("Domain not active."))
599 elif errorcode in ("380098", "380099"):
600 raise DDNSInternalServerError
601
602 # If we got here, some other update error happened.
603 raise DDNSUpdateError
604
605
606 class DDNSProviderNOIP(DDNSProviderDynDNS):
607 INFO = {
608 "handle" : "no-ip.com",
609 "name" : "No-IP",
610 "website" : "http://www.no-ip.com/",
611 "protocols" : ["ipv4",]
612 }
613
614 # Information about the format of the HTTP request is to be found
615 # here: http://www.no-ip.com/integrate/request and
616 # here: http://www.no-ip.com/integrate/response
617
618 url = "http://dynupdate.no-ip.com/nic/update"
619
620 def _prepare_request_data(self):
621 data = {
622 "hostname" : self.hostname,
623 "address" : self.get_address("ipv4"),
624 }
625
626 return data
627
628
629 class DDNSProviderOVH(DDNSProviderDynDNS):
630 INFO = {
631 "handle" : "ovh.com",
632 "name" : "OVH",
633 "website" : "http://www.ovh.com/",
634 "protocols" : ["ipv4",]
635 }
636
637 # OVH only provides very limited information about how to
638 # update a DynDNS host. They only provide the update url
639 # on the their german subpage.
640 #
641 # http://hilfe.ovh.de/DomainDynHost
642
643 url = "https://www.ovh.com/nic/update"
644
645 def _prepare_request_data(self):
646 data = DDNSProviderDynDNS._prepare_request_data(self)
647 data.update({
648 "system" : "dyndns",
649 })
650
651 return data
652
653
654 class DDNSProviderRegfish(DDNSProvider):
655 INFO = {
656 "handle" : "regfish.com",
657 "name" : "Regfish GmbH",
658 "website" : "http://www.regfish.com/",
659 "protocols" : ["ipv6", "ipv4",]
660 }
661
662 # A full documentation to the providers api can be found here
663 # but is only available in german.
664 # https://www.regfish.de/domains/dyndns/dokumentation
665
666 url = "https://dyndns.regfish.de/"
667
668 def update(self):
669 data = {
670 "fqdn" : self.hostname,
671 }
672
673 # Check if we update an IPv6 address.
674 address6 = self.get_address("ipv6")
675 if address6:
676 data["ipv6"] = address6
677
678 # Check if we update an IPv4 address.
679 address4 = self.get_address("ipv4")
680 if address4:
681 data["ipv4"] = address4
682
683 # Raise an error if none address is given.
684 if not data.has_key("ipv6") and not data.has_key("ipv4"):
685 raise DDNSConfigurationError
686
687 # Check if a token has been set.
688 if self.token:
689 data["token"] = self.token
690
691 # Raise an error if no token and no useranem and password
692 # are given.
693 elif not self.username and not self.password:
694 raise DDNSConfigurationError(_("No Auth details specified."))
695
696 # HTTP Basic Auth is only allowed if no token is used.
697 if self.token:
698 # Send update to the server.
699 response = self.send_request(self.url, data=data)
700 else:
701 # Send update to the server.
702 response = self.send_request(self.url, username=self.username, password=self.password,
703 data=data)
704
705 # Get the full response message.
706 output = response.read()
707
708 # Handle success messages.
709 if "100" in output or "101" in output:
710 return
711
712 # Handle error codes.
713 if "401" or "402" in output:
714 raise DDNSAuthenticationError
715 elif "408" in output:
716 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
717 elif "409" in output:
718 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
719 elif "412" in output:
720 raise DDNSRequestError(_("No valid FQDN was given."))
721 elif "414" in output:
722 raise DDNSInternalServerError
723
724 # If we got here, some other update error happened.
725 raise DDNSUpdateError
726
727
728 class DDNSProviderSelfhost(DDNSProviderDynDNS):
729 INFO = {
730 "handle" : "selfhost.de",
731 "name" : "Selfhost.de",
732 "website" : "http://www.selfhost.de/",
733 "protocols" : ["ipv4",],
734 }
735
736 url = "https://carol.selfhost.de/nic/update"
737
738 def _prepare_request_data(self):
739 data = DDNSProviderDynDNS._prepare_request_data(self)
740 data.update({
741 "hostname" : "1",
742 })
743
744 return data
745
746
747 class DDNSProviderSPDNS(DDNSProviderDynDNS):
748 INFO = {
749 "handle" : "spdns.org",
750 "name" : "SPDNS",
751 "website" : "http://spdns.org/",
752 "protocols" : ["ipv4",]
753 }
754
755 # Detailed information about request and response codes are provided
756 # by the vendor. They are using almost the same mechanism and status
757 # codes as dyndns.org so we can inherit all those stuff.
758 #
759 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
760 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
761
762 url = "https://update.spdns.de/nic/update"
763
764
765 class DDNSProviderStrato(DDNSProviderDynDNS):
766 INFO = {
767 "handle" : "strato.com",
768 "name" : "Strato AG",
769 "website" : "http:/www.strato.com/",
770 "protocols" : ["ipv4",]
771 }
772
773 # Information about the request and response can be obtained here:
774 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
775
776 url = "https://dyndns.strato.com/nic/update"
777
778
779 class DDNSProviderTwoDNS(DDNSProviderDynDNS):
780 INFO = {
781 "handle" : "twodns.de",
782 "name" : "TwoDNS",
783 "website" : "http://www.twodns.de",
784 "protocols" : ["ipv4",]
785 }
786
787 # Detailed information about the request can be found here
788 # http://twodns.de/en/faqs
789 # http://twodns.de/en/api
790
791 url = "https://update.twodns.de/update"
792
793 def _prepare_request_data(self):
794 data = {
795 "ip" : self.get_address("ipv4"),
796 "hostname" : self.hostname
797 }
798
799 return data
800
801
802 class DDNSProviderUdmedia(DDNSProviderDynDNS):
803 INFO = {
804 "handle" : "udmedia.de",
805 "name" : "Udmedia GmbH",
806 "website" : "http://www.udmedia.de",
807 "protocols" : ["ipv4",]
808 }
809
810 # Information about the request can be found here
811 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
812
813 url = "https://www.udmedia.de/nic/update"
814
815
816 class DDNSProviderVariomedia(DDNSProviderDynDNS):
817 INFO = {
818 "handle" : "variomedia.de",
819 "name" : "Variomedia",
820 "website" : "http://www.variomedia.de/",
821 "protocols" : ["ipv6", "ipv4",]
822 }
823
824 # Detailed information about the request can be found here
825 # https://dyndns.variomedia.de/
826
827 url = "https://dyndns.variomedia.de/nic/update"
828
829 @property
830 def proto(self):
831 return self.get("proto")
832
833 def _prepare_request_data(self):
834 data = {
835 "hostname" : self.hostname,
836 "myip" : self.get_address(self.proto)
837 }
838
839 return data
840
841
842 class DDNSProviderZoneedit(DDNSProvider):
843 INFO = {
844 "handle" : "zoneedit.com",
845 "name" : "Zoneedit",
846 "website" : "http://www.zoneedit.com",
847 "protocols" : ["ipv6", "ipv4",]
848 }
849
850 # Detailed information about the request and the response codes can be
851 # obtained here:
852 # http://www.zoneedit.com/doc/api/other.html
853 # http://www.zoneedit.com/faq.html
854
855 url = "https://dynamic.zoneedit.com/auth/dynamic.html"
856
857 @property
858 def proto(self):
859 return self.get("proto")
860
861 def update(self):
862 data = {
863 "dnsto" : self.get_address(self.proto),
864 "host" : self.hostname
865 }
866
867 # Send update to the server.
868 response = self.send_request(self.url, username=self.username, password=self.password,
869 data=data)
870
871 # Get the full response message.
872 output = response.read()
873
874 # Handle success messages.
875 if output.startswith("<SUCCESS"):
876 return
877
878 # Handle error codes.
879 if output.startswith("invalid login"):
880 raise DDNSAuthenticationError
881 elif output.startswith("<ERROR CODE=\"704\""):
882 raise DDNSRequestError(_("No valid FQDN was given."))
883 elif output.startswith("<ERROR CODE=\"702\""):
884 raise DDNSInternalServerError
885
886 # If we got here, some other update error happened.
887 raise DDNSUpdateError