]>
git.ipfire.org Git - people/ms/ddns.git/blob - src/ddns/providers.py
2 ###############################################################################
4 # ddns - A dynamic DNS client for IPFire #
5 # Copyright (C) 2012 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 ###############################################################################
26 import xml
.dom
.minidom
30 # Import all possible exception types.
33 logger
= logging
.getLogger("ddns.providers")
40 Returns a dict with all automatically registered providers.
42 return _providers
.copy()
44 class DDNSProvider(object):
45 # A short string that uniquely identifies
49 # The full name of the provider.
52 # A weburl to the homepage of the provider.
53 # (Where to register a new account?)
56 # A list of supported protocols.
57 protocols
= ("ipv6", "ipv4")
61 # holdoff time - Number of days no update is performed unless
62 # the IP address has changed.
65 # True if the provider is able to remove records, too.
66 # Required to remove AAAA records if IPv6 is absent again.
67 can_remove_records
= True
69 # Automatically register all providers.
70 class __metaclass__(type):
71 def __init__(provider
, name
, bases
, dict):
72 type.__init
__(provider
, name
, bases
, dict)
74 # The main class from which is inherited is not registered
76 if name
== "DDNSProvider":
79 if not all((provider
.handle
, provider
.name
, provider
.website
)):
80 raise DDNSError(_("Provider is not properly configured"))
82 assert not _providers
.has_key(provider
.handle
), \
83 "Provider '%s' has already been registered" % provider
.handle
85 _providers
[provider
.handle
] = provider
87 def __init__(self
, core
, **settings
):
90 # Copy a set of default settings and
91 # update them by those from the configuration file.
92 self
.settings
= self
.DEFAULT_SETTINGS
.copy()
93 self
.settings
.update(settings
)
96 return "<DDNS Provider %s (%s)>" % (self
.name
, self
.handle
)
98 def __cmp__(self
, other
):
99 return cmp(self
.hostname
, other
.hostname
)
105 def get(self
, key
, default
=None):
107 Get a setting from the settings dictionary.
109 return self
.settings
.get(key
, default
)
114 Fast access to the hostname.
116 return self
.get("hostname")
121 Fast access to the username.
123 return self
.get("username")
128 Fast access to the password.
130 return self
.get("password")
135 Fast access to the token.
137 return self
.get("token")
139 def __call__(self
, force
=False):
141 logger
.debug(_("Updating %s forced") % self
.hostname
)
143 # Do nothing if no update is required
144 elif not self
.requires_update
:
147 # Execute the update.
151 # In case of any errors, log the failed request and
152 # raise the exception.
153 except DDNSError
as e
:
154 self
.core
.db
.log_failure(self
.hostname
, e
)
157 logger
.info(_("Dynamic DNS update for %(hostname)s (%(provider)s) successful") % \
158 { "hostname" : self
.hostname
, "provider" : self
.name
})
159 self
.core
.db
.log_success(self
.hostname
)
162 for protocol
in self
.protocols
:
163 if self
.have_address(protocol
):
164 self
.update_protocol(protocol
)
165 elif self
.can_remove_records
:
166 self
.remove_protocol(protocol
)
168 def update_protocol(self
, proto
):
169 raise NotImplementedError
171 def remove_protocol(self
, proto
):
172 if not self
.can_remove_records
:
173 raise RuntimeError, "can_remove_records is enabled, but remove_protocol() not implemented"
175 raise NotImplementedError
178 def requires_update(self
):
179 # If the IP addresses have changed, an update is required
180 if self
.ip_address_changed(self
.protocols
):
181 logger
.debug(_("An update for %(hostname)s (%(provider)s)"
182 " is performed because of an IP address change") % \
183 { "hostname" : self
.hostname
, "provider" : self
.name
})
187 # If the holdoff time has expired, an update is required, too
188 if self
.holdoff_time_expired():
189 logger
.debug(_("An update for %(hostname)s (%(provider)s)"
190 " is performed because the holdoff time has expired") % \
191 { "hostname" : self
.hostname
, "provider" : self
.name
})
195 # Otherwise, we don't need to perform an update
196 logger
.debug(_("No update required for %(hostname)s (%(provider)s)") % \
197 { "hostname" : self
.hostname
, "provider" : self
.name
})
201 def ip_address_changed(self
, protos
):
203 Returns True if this host is already up to date
204 and does not need to change the IP address on the
208 addresses
= self
.core
.system
.resolve(self
.hostname
, proto
)
209 current_address
= self
.get_address(proto
)
211 # Handle if the system has not got any IP address from a protocol
212 # (i.e. had full dual-stack connectivity which it has not any more)
213 if current_address
is None:
214 # If addresses still exists in the DNS system and if this provider
215 # is able to remove records, we will do that.
216 if addresses
and self
.can_remove_records
:
219 # Otherwise, we cannot go on...
222 if not current_address
in addresses
:
227 def holdoff_time_expired(self
):
229 Returns true if the holdoff time has expired
230 and the host requires an update
232 # If no holdoff days is defined, we cannot go on
233 if not self
.holdoff_days
:
236 # Get the timestamp of the last successfull update
237 last_update
= self
.db
.last_update(self
.hostname
)
239 # If no timestamp has been recorded, no update has been
240 # performed. An update should be performed now.
244 # Determine when the holdoff time ends
245 holdoff_end
= last_update
+ datetime
.timedelta(days
=self
.holdoff_days
)
247 now
= datetime
.datetime
.utcnow()
249 if now
>= holdoff_end
:
250 logger
.debug("The holdoff time has expired for %s" % self
.hostname
)
253 logger
.debug("Updates for %s are held off until %s" % \
254 (self
.hostname
, holdoff_end
))
257 def send_request(self
, *args
, **kwargs
):
259 Proxy connection to the send request
262 return self
.core
.system
.send_request(*args
, **kwargs
)
264 def get_address(self
, proto
, default
=None):
266 Proxy method to get the current IP address.
268 return self
.core
.system
.get_address(proto
) or default
270 def have_address(self
, proto
):
272 Returns True if an IP address for the given protocol
275 address
= self
.get_address(proto
)
283 class DDNSProtocolDynDNS2(object):
285 This is an abstract class that implements the DynDNS updater
286 protocol version 2. As this is a popular way to update dynamic
287 DNS records, this class is supposed make the provider classes
291 # Information about the format of the request is to be found
292 # http://dyn.com/support/developers/api/perform-update/
293 # http://dyn.com/support/developers/api/return-codes/
295 # The DynDNS protocol version 2 does not allow to remove records
296 can_remove_records
= False
298 def prepare_request_data(self
, proto
):
300 "hostname" : self
.hostname
,
301 "myip" : self
.get_address(proto
),
306 def update_protocol(self
, proto
):
307 data
= self
.prepare_request_data(proto
)
309 return self
.send_request(data
)
311 def send_request(self
, data
):
312 # Send update to the server.
313 response
= DDNSProvider
.send_request(self
, self
.url
, data
=data
,
314 username
=self
.username
, password
=self
.password
)
316 # Get the full response message.
317 output
= response
.read()
319 # Handle success messages.
320 if output
.startswith("good") or output
.startswith("nochg"):
323 # Handle error codes.
324 if output
== "badauth":
325 raise DDNSAuthenticationError
326 elif output
== "abuse":
328 elif output
== "notfqdn":
329 raise DDNSRequestError(_("No valid FQDN was given."))
330 elif output
== "nohost":
331 raise DDNSRequestError(_("Specified host does not exist."))
332 elif output
== "911":
333 raise DDNSInternalServerError
334 elif output
== "dnserr":
335 raise DDNSInternalServerError(_("DNS error encountered."))
336 elif output
== "badagent":
337 raise DDNSBlockedError
339 # If we got here, some other update error happened.
340 raise DDNSUpdateError(_("Server response: %s") % output
)
343 class DDNSResponseParserXML(object):
345 This class provides a parser for XML responses which
346 will be sent by various providers. This class uses the python
347 shipped XML minidom module to walk through the XML tree and return
351 def get_xml_tag_value(self
, document
, content
):
352 # Send input to the parser.
353 xmldoc
= xml
.dom
.minidom
.parseString(document
)
355 # Get XML elements by the given content.
356 element
= xmldoc
.getElementsByTagName(content
)
358 # If no element has been found, we directly can return None.
362 # Only get the first child from an element, even there are more than one.
363 firstchild
= element
[0].firstChild
365 # Get the value of the child.
366 value
= firstchild
.nodeValue
372 class DDNSProviderAllInkl(DDNSProvider
):
373 handle
= "all-inkl.com"
374 name
= "All-inkl.com"
375 website
= "http://all-inkl.com/"
376 protocols
= ("ipv4",)
378 # There are only information provided by the vendor how to
379 # perform an update on a FRITZ Box. Grab requried informations
381 # http://all-inkl.goetze.it/v01/ddns-mit-einfachen-mitteln/
383 url
= "http://dyndns.kasserver.com"
384 can_remove_records
= False
387 # There is no additional data required so we directly can
389 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
)
391 # Get the full response message.
392 output
= response
.read()
394 # Handle success messages.
395 if output
.startswith("good") or output
.startswith("nochg"):
398 # If we got here, some other update error happened.
399 raise DDNSUpdateError
402 class DDNSProviderBindNsupdate(DDNSProvider
):
404 name
= "BIND nsupdate utility"
405 website
= "http://en.wikipedia.org/wiki/Nsupdate"
410 scriptlet
= self
.__make
_scriptlet
()
412 # -v enables TCP hence we transfer keys and other data that may
413 # exceed the size of one packet.
414 # -t sets the timeout
415 command
= ["nsupdate", "-v", "-t", "60"]
417 p
= subprocess
.Popen(command
, shell
=True,
418 stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
420 stdout
, stderr
= p
.communicate(scriptlet
)
422 if p
.returncode
== 0:
425 raise DDNSError("nsupdate terminated with error code: %s\n %s" % (p
.returncode
, stderr
))
427 def __make_scriptlet(self
):
430 # Set a different server the update is sent to.
431 server
= self
.get("server", None)
433 scriptlet
.append("server %s" % server
)
435 # Set the DNS zone the host should be added to.
436 zone
= self
.get("zone", None)
438 scriptlet
.append("zone %s" % zone
)
440 key
= self
.get("key", None)
442 secret
= self
.get("secret")
444 scriptlet
.append("key %s %s" % (key
, secret
))
446 ttl
= self
.get("ttl", self
.DEFAULT_TTL
)
448 # Perform an update for each supported protocol.
449 for rrtype
, proto
in (("AAAA", "ipv6"), ("A", "ipv4")):
450 address
= self
.get_address(proto
)
454 scriptlet
.append("update delete %s. %s" % (self
.hostname
, rrtype
))
455 scriptlet
.append("update add %s. %s %s %s" % \
456 (self
.hostname
, ttl
, rrtype
, address
))
458 # Send the actions to the server.
459 scriptlet
.append("send")
460 scriptlet
.append("quit")
462 logger
.debug(_("Scriptlet:"))
463 for line
in scriptlet
:
464 # Masquerade the line with the secret key.
465 if line
.startswith("key"):
466 line
= "key **** ****"
468 logger
.debug(" %s" % line
)
470 return "\n".join(scriptlet
)
473 class DDNSProviderDHS(DDNSProvider
):
475 name
= "DHS International"
476 website
= "http://dhs.org/"
477 protocols
= ("ipv4",)
479 # No information about the used update api provided on webpage,
480 # grabed from source code of ez-ipudate.
482 url
= "http://members.dhs.org/nic/hosts"
483 can_remove_records
= False
485 def update_protocol(self
, proto
):
487 "domain" : self
.hostname
,
488 "ip" : self
.get_address(proto
),
490 "hostcmdstage" : "2",
494 # Send update to the server.
495 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
498 # Handle success messages.
499 if response
.code
== 200:
502 # If we got here, some other update error happened.
503 raise DDNSUpdateError
506 class DDNSProviderDNSpark(DDNSProvider
):
507 handle
= "dnspark.com"
509 website
= "http://dnspark.com/"
510 protocols
= ("ipv4",)
512 # Informations to the used api can be found here:
513 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
515 url
= "https://control.dnspark.com/api/dynamic/update.php"
516 can_remove_records
= False
518 def update_protocol(self
, proto
):
520 "domain" : self
.hostname
,
521 "ip" : self
.get_address(proto
),
524 # Send update to the server.
525 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
528 # Get the full response message.
529 output
= response
.read()
531 # Handle success messages.
532 if output
.startswith("ok") or output
.startswith("nochange"):
535 # Handle error codes.
536 if output
== "unauth":
537 raise DDNSAuthenticationError
538 elif output
== "abuse":
540 elif output
== "blocked":
541 raise DDNSBlockedError
542 elif output
== "nofqdn":
543 raise DDNSRequestError(_("No valid FQDN was given."))
544 elif output
== "nohost":
545 raise DDNSRequestError(_("Invalid hostname specified."))
546 elif output
== "notdyn":
547 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
548 elif output
== "invalid":
549 raise DDNSRequestError(_("Invalid IP address has been sent."))
551 # If we got here, some other update error happened.
552 raise DDNSUpdateError
555 class DDNSProviderDtDNS(DDNSProvider
):
558 website
= "http://dtdns.com/"
559 protocols
= ("ipv4",)
561 # Information about the format of the HTTPS request is to be found
562 # http://www.dtdns.com/dtsite/updatespec
564 url
= "https://www.dtdns.com/api/autodns.cfm"
565 can_remove_records
= False
567 def update_protocol(self
, proto
):
569 "ip" : self
.get_address(proto
),
570 "id" : self
.hostname
,
574 # Send update to the server.
575 response
= self
.send_request(self
.url
, data
=data
)
577 # Get the full response message.
578 output
= response
.read()
580 # Remove all leading and trailing whitespace.
581 output
= output
.strip()
583 # Handle success messages.
584 if "now points to" in output
:
587 # Handle error codes.
588 if output
== "No hostname to update was supplied.":
589 raise DDNSRequestError(_("No hostname specified."))
591 elif output
== "The hostname you supplied is not valid.":
592 raise DDNSRequestError(_("Invalid hostname specified."))
594 elif output
== "The password you supplied is not valid.":
595 raise DDNSAuthenticationError
597 elif output
== "Administration has disabled this account.":
598 raise DDNSRequestError(_("Account has been disabled."))
600 elif output
== "Illegal character in IP.":
601 raise DDNSRequestError(_("Invalid IP address has been sent."))
603 elif output
== "Too many failed requests.":
604 raise DDNSRequestError(_("Too many failed requests."))
606 # If we got here, some other update error happened.
607 raise DDNSUpdateError
610 class DDNSProviderDynDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
611 handle
= "dyndns.org"
613 website
= "http://dyn.com/dns/"
614 protocols
= ("ipv4",)
616 # Information about the format of the request is to be found
617 # http://http://dyn.com/support/developers/api/perform-update/
618 # http://dyn.com/support/developers/api/return-codes/
620 url
= "https://members.dyndns.org/nic/update"
623 class DDNSProviderDynU(DDNSProtocolDynDNS2
, DDNSProvider
):
626 website
= "http://dynu.com/"
627 protocols
= ("ipv6", "ipv4",)
629 # Detailed information about the request and response codes
630 # are available on the providers webpage.
631 # http://dynu.com/Default.aspx?page=dnsapi
633 url
= "https://api.dynu.com/nic/update"
635 # DynU sends the IPv6 and IPv4 address in one request
638 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, "ipv4")
640 # This one supports IPv6
641 myipv6
= self
.get_address("ipv6")
643 # Add update information if we have an IPv6 address.
645 data
["myipv6"] = myipv6
647 self
._send
_request
(data
)
650 class DDNSProviderEasyDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
651 handle
= "easydns.com"
653 website
= "http://www.easydns.com/"
654 protocols
= ("ipv4",)
656 # There is only some basic documentation provided by the vendor,
657 # also searching the web gain very poor results.
658 # http://mediawiki.easydns.com/index.php/Dynamic_DNS
660 url
= "http://api.cp.easydns.com/dyn/tomato.php"
663 class DDNSProviderDomopoli(DDNSProtocolDynDNS2
, DDNSProvider
):
664 handle
= "domopoli.de"
666 website
= "http://domopoli.de/"
667 protocols
= ("ipv4",)
669 # https://www.domopoli.de/?page=howto#DynDns_start
671 url
= "http://dyndns.domopoli.de/nic/update"
674 class DDNSProviderDynsNet(DDNSProvider
):
677 website
= "http://www.dyns.net/"
678 protocols
= ("ipv4",)
679 can_remove_records
= False
681 # There is very detailed informatio about how to send the update request and
682 # the possible response codes. (Currently we are using the v1.1 proto)
683 # http://www.dyns.net/documentation/technical/protocol/
685 url
= "http://www.dyns.net/postscript011.php"
687 def update_protocol(self
, proto
):
689 "ip" : self
.get_address(proto
),
690 "host" : self
.hostname
,
691 "username" : self
.username
,
692 "password" : self
.password
,
695 # Send update to the server.
696 response
= self
.send_request(self
.url
, data
=data
)
698 # Get the full response message.
699 output
= response
.read()
701 # Handle success messages.
702 if output
.startswith("200"):
705 # Handle error codes.
706 if output
.startswith("400"):
707 raise DDNSRequestError(_("Malformed request has been sent."))
708 elif output
.startswith("401"):
709 raise DDNSAuthenticationError
710 elif output
.startswith("402"):
711 raise DDNSRequestError(_("Too frequent update requests have been sent."))
712 elif output
.startswith("403"):
713 raise DDNSInternalServerError
715 # If we got here, some other update error happened.
716 raise DDNSUpdateError(_("Server response: %s") % output
)
719 class DDNSProviderEnomCom(DDNSResponseParserXML
, DDNSProvider
):
722 website
= "http://www.enom.com/"
723 protocols
= ("ipv4",)
725 # There are very detailed information about how to send an update request and
727 # http://www.enom.com/APICommandCatalog/
729 url
= "https://dynamic.name-services.com/interface.asp"
730 can_remove_records
= False
732 def update_protocol(self
, proto
):
734 "command" : "setdnshost",
735 "responsetype" : "xml",
736 "address" : self
.get_address(proto
),
737 "domainpassword" : self
.password
,
738 "zone" : self
.hostname
741 # Send update to the server.
742 response
= self
.send_request(self
.url
, data
=data
)
744 # Get the full response message.
745 output
= response
.read()
747 # Handle success messages.
748 if self
.get_xml_tag_value(output
, "ErrCount") == "0":
751 # Handle error codes.
752 errorcode
= self
.get_xml_tag_value(output
, "ResponseNumber")
754 if errorcode
== "304155":
755 raise DDNSAuthenticationError
756 elif errorcode
== "304153":
757 raise DDNSRequestError(_("Domain not found."))
759 # If we got here, some other update error happened.
760 raise DDNSUpdateError
763 class DDNSProviderEntryDNS(DDNSProvider
):
764 handle
= "entrydns.net"
766 website
= "http://entrydns.net/"
767 protocols
= ("ipv4",)
769 # Some very tiny details about their so called "Simple API" can be found
770 # here: https://entrydns.net/help
771 url
= "https://entrydns.net/records/modify"
772 can_remove_records
= False
774 def update_protocol(self
, proto
):
776 "ip" : self
.get_address(proto
),
779 # Add auth token to the update url.
780 url
= "%s/%s" % (self
.url
, self
.token
)
782 # Send update to the server.
784 response
= self
.send_request(url
, data
=data
)
787 except urllib2
.HTTPError
, e
:
789 raise DDNSAuthenticationError
792 raise DDNSRequestError(_("An invalid IP address was submitted"))
796 # Handle success messages.
797 if response
.code
== 200:
800 # If we got here, some other update error happened.
801 raise DDNSUpdateError
804 class DDNSProviderFreeDNSAfraidOrg(DDNSProvider
):
805 handle
= "freedns.afraid.org"
806 name
= "freedns.afraid.org"
807 website
= "http://freedns.afraid.org/"
809 # No information about the request or response could be found on the vendor
810 # page. All used values have been collected by testing.
811 url
= "https://freedns.afraid.org/dynamic/update.php"
812 can_remove_records
= False
814 def update_protocol(self
, proto
):
816 "address" : self
.get_address(proto
),
819 # Add auth token to the update url.
820 url
= "%s?%s" % (self
.url
, self
.token
)
822 # Send update to the server.
823 response
= self
.send_request(url
, data
=data
)
825 # Get the full response message.
826 output
= response
.read()
828 # Handle success messages.
829 if output
.startswith("Updated") or "has not changed" in output
:
832 # Handle error codes.
833 if output
== "ERROR: Unable to locate this record":
834 raise DDNSAuthenticationError
835 elif "is an invalid IP address" in output
:
836 raise DDNSRequestError(_("Invalid IP address has been sent."))
838 # If we got here, some other update error happened.
839 raise DDNSUpdateError
842 class DDNSProviderLightningWireLabs(DDNSProvider
):
843 handle
= "dns.lightningwirelabs.com"
844 name
= "Lightning Wire Labs DNS Service"
845 website
= "http://dns.lightningwirelabs.com/"
847 # Information about the format of the HTTPS request is to be found
848 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
850 url
= "https://dns.lightningwirelabs.com/update"
854 "hostname" : self
.hostname
,
855 "address6" : self
.get_address("ipv6", "-"),
856 "address4" : self
.get_address("ipv4", "-"),
859 # Check if a token has been set.
861 data
["token"] = self
.token
863 # Check for username and password.
864 elif self
.username
and self
.password
:
866 "username" : self
.username
,
867 "password" : self
.password
,
870 # Raise an error if no auth details are given.
872 raise DDNSConfigurationError
874 # Send update to the server.
875 response
= self
.send_request(self
.url
, data
=data
)
877 # Handle success messages.
878 if response
.code
== 200:
881 # If we got here, some other update error happened.
882 raise DDNSUpdateError
885 class DDNSProviderMyOnlinePortal(DDNSProtocolDynDNS2
, DDNSProvider
):
886 handle
= "myonlineportal.net"
887 name
= "myonlineportal.net"
888 website
= "https:/myonlineportal.net/"
890 # Information about the request and response can be obtained here:
891 # https://myonlineportal.net/howto_dyndns
893 url
= "https://myonlineportal.net/updateddns"
895 def prepare_request_data(self
, proto
):
897 "hostname" : self
.hostname
,
898 "ip" : self
.get_address(proto
),
904 class DDNSProviderNamecheap(DDNSResponseParserXML
, DDNSProvider
):
905 handle
= "namecheap.com"
907 website
= "http://namecheap.com"
908 protocols
= ("ipv4",)
910 # Information about the format of the HTTP request is to be found
911 # https://www.namecheap.com/support/knowledgebase/article.aspx/9249/0/nc-dynamic-dns-to-dyndns-adapter
912 # https://community.namecheap.com/forums/viewtopic.php?f=6&t=6772
914 url
= "https://dynamicdns.park-your-domain.com/update"
915 can_remove_records
= False
917 def update_protocol(self
, proto
):
918 # Namecheap requires the hostname splitted into a host and domain part.
919 host
, domain
= self
.hostname
.split(".", 1)
922 "ip" : self
.get_address(proto
),
923 "password" : self
.password
,
928 # Send update to the server.
929 response
= self
.send_request(self
.url
, data
=data
)
931 # Get the full response message.
932 output
= response
.read()
934 # Handle success messages.
935 if self
.get_xml_tag_value(output
, "IP") == address
:
938 # Handle error codes.
939 errorcode
= self
.get_xml_tag_value(output
, "ResponseNumber")
941 if errorcode
== "304156":
942 raise DDNSAuthenticationError
943 elif errorcode
== "316153":
944 raise DDNSRequestError(_("Domain not found."))
945 elif errorcode
== "316154":
946 raise DDNSRequestError(_("Domain not active."))
947 elif errorcode
in ("380098", "380099"):
948 raise DDNSInternalServerError
950 # If we got here, some other update error happened.
951 raise DDNSUpdateError
954 class DDNSProviderNOIP(DDNSProtocolDynDNS2
, DDNSProvider
):
957 website
= "http://www.no-ip.com/"
958 protocols
= ("ipv4",)
960 # Information about the format of the HTTP request is to be found
961 # here: http://www.no-ip.com/integrate/request and
962 # here: http://www.no-ip.com/integrate/response
964 url
= "http://dynupdate.no-ip.com/nic/update"
966 def prepare_request_data(self
, proto
):
967 assert proto
== "ipv4"
970 "hostname" : self
.hostname
,
971 "address" : self
.get_address(proto
),
977 class DDNSProviderNsupdateINFO(DDNSProtocolDynDNS2
, DDNSProvider
):
978 handle
= "nsupdate.info"
979 name
= "nsupdate.info"
980 website
= "http://nsupdate.info/"
981 protocols
= ("ipv6", "ipv4",)
983 # Information about the format of the HTTP request can be found
984 # after login on the provider user interface and here:
985 # http://nsupdateinfo.readthedocs.org/en/latest/user.html
987 # TODO nsupdate.info can actually do this, but the functionality
988 # has not been implemented here, yet.
989 can_remove_records
= False
991 # Nsupdate.info uses the hostname as user part for the HTTP basic auth,
992 # and for the password a so called secret.
995 return self
.get("hostname")
999 return self
.token
or self
.get("secret")
1003 # The update URL is different by the used protocol.
1004 if self
.proto
== "ipv4":
1005 return "https://ipv4.nsupdate.info/nic/update"
1006 elif self
.proto
== "ipv6":
1007 return "https://ipv6.nsupdate.info/nic/update"
1009 raise DDNSUpdateError(_("Invalid protocol has been given"))
1011 def prepare_request_data(self
, proto
):
1013 "myip" : self
.get_address(proto
),
1019 class DDNSProviderOpenDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1020 handle
= "opendns.com"
1022 website
= "http://www.opendns.com"
1024 # Detailed information about the update request and possible
1025 # response codes can be obtained from here:
1026 # https://support.opendns.com/entries/23891440
1028 url
= "https://updates.opendns.com/nic/update"
1030 def prepare_request_data(self
, proto
):
1032 "hostname" : self
.hostname
,
1033 "myip" : self
.get_address(proto
),
1039 class DDNSProviderOVH(DDNSProtocolDynDNS2
, DDNSProvider
):
1042 website
= "http://www.ovh.com/"
1043 protocols
= ("ipv4",)
1045 # OVH only provides very limited information about how to
1046 # update a DynDNS host. They only provide the update url
1047 # on the their german subpage.
1049 # http://hilfe.ovh.de/DomainDynHost
1051 url
= "https://www.ovh.com/nic/update"
1053 def prepare_request_data(self
, proto
):
1054 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1056 "system" : "dyndns",
1062 class DDNSProviderRegfish(DDNSProvider
):
1063 handle
= "regfish.com"
1064 name
= "Regfish GmbH"
1065 website
= "http://www.regfish.com/"
1067 # A full documentation to the providers api can be found here
1068 # but is only available in german.
1069 # https://www.regfish.de/domains/dyndns/dokumentation
1071 url
= "https://dyndns.regfish.de/"
1072 can_remove_records
= False
1076 "fqdn" : self
.hostname
,
1079 # Check if we update an IPv6 address.
1080 address6
= self
.get_address("ipv6")
1082 data
["ipv6"] = address6
1084 # Check if we update an IPv4 address.
1085 address4
= self
.get_address("ipv4")
1087 data
["ipv4"] = address4
1089 # Raise an error if none address is given.
1090 if not data
.has_key("ipv6") and not data
.has_key("ipv4"):
1091 raise DDNSConfigurationError
1093 # Check if a token has been set.
1095 data
["token"] = self
.token
1097 # Raise an error if no token and no useranem and password
1099 elif not self
.username
and not self
.password
:
1100 raise DDNSConfigurationError(_("No Auth details specified."))
1102 # HTTP Basic Auth is only allowed if no token is used.
1104 # Send update to the server.
1105 response
= self
.send_request(self
.url
, data
=data
)
1107 # Send update to the server.
1108 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
1111 # Get the full response message.
1112 output
= response
.read()
1114 # Handle success messages.
1115 if "100" in output
or "101" in output
:
1118 # Handle error codes.
1119 if "401" or "402" in output
:
1120 raise DDNSAuthenticationError
1121 elif "408" in output
:
1122 raise DDNSRequestError(_("Invalid IPv4 address has been sent."))
1123 elif "409" in output
:
1124 raise DDNSRequestError(_("Invalid IPv6 address has been sent."))
1125 elif "412" in output
:
1126 raise DDNSRequestError(_("No valid FQDN was given."))
1127 elif "414" in output
:
1128 raise DDNSInternalServerError
1130 # If we got here, some other update error happened.
1131 raise DDNSUpdateError
1134 class DDNSProviderSelfhost(DDNSProtocolDynDNS2
, DDNSProvider
):
1135 handle
= "selfhost.de"
1136 name
= "Selfhost.de"
1137 website
= "http://www.selfhost.de/"
1138 protocols
= ("ipv4",)
1140 url
= "https://carol.selfhost.de/nic/update"
1142 def prepare_request_data(self
, proto
):
1143 data
= DDNSProtocolDynDNS2
.prepare_request_data(self
, proto
)
1151 class DDNSProviderSPDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1152 handle
= "spdns.org"
1154 website
= "http://spdns.org/"
1156 # Detailed information about request and response codes are provided
1157 # by the vendor. They are using almost the same mechanism and status
1158 # codes as dyndns.org so we can inherit all those stuff.
1160 # http://wiki.securepoint.de/index.php/SPDNS_FAQ
1161 # http://wiki.securepoint.de/index.php/SPDNS_Update-Tokens
1163 url
= "https://update.spdns.de/nic/update"
1167 return self
.get("username") or self
.hostname
1171 return self
.get("username") or self
.token
1174 class DDNSProviderStrato(DDNSProtocolDynDNS2
, DDNSProvider
):
1175 handle
= "strato.com"
1177 website
= "http:/www.strato.com/"
1178 protocols
= ("ipv4",)
1180 # Information about the request and response can be obtained here:
1181 # http://www.strato-faq.de/article/671/So-einfach-richten-Sie-DynDNS-f%C3%BCr-Ihre-Domains-ein.html
1183 url
= "https://dyndns.strato.com/nic/update"
1186 class DDNSProviderTwoDNS(DDNSProtocolDynDNS2
, DDNSProvider
):
1187 handle
= "twodns.de"
1189 website
= "http://www.twodns.de"
1190 protocols
= ("ipv4",)
1192 # Detailed information about the request can be found here
1193 # http://twodns.de/en/faqs
1194 # http://twodns.de/en/api
1196 url
= "https://update.twodns.de/update"
1198 def prepare_request_data(self
, proto
):
1199 assert proto
== "ipv4"
1202 "ip" : self
.get_address(proto
),
1203 "hostname" : self
.hostname
1209 class DDNSProviderUdmedia(DDNSProtocolDynDNS2
, DDNSProvider
):
1210 handle
= "udmedia.de"
1211 name
= "Udmedia GmbH"
1212 website
= "http://www.udmedia.de"
1213 protocols
= ("ipv4",)
1215 # Information about the request can be found here
1216 # http://www.udmedia.de/faq/content/47/288/de/wie-lege-ich-einen-dyndns_eintrag-an.html
1218 url
= "https://www.udmedia.de/nic/update"
1221 class DDNSProviderVariomedia(DDNSProtocolDynDNS2
, DDNSProvider
):
1222 handle
= "variomedia.de"
1224 website
= "http://www.variomedia.de/"
1225 protocols
= ("ipv6", "ipv4",)
1227 # Detailed information about the request can be found here
1228 # https://dyndns.variomedia.de/
1230 url
= "https://dyndns.variomedia.de/nic/update"
1232 def prepare_request_data(self
, proto
):
1234 "hostname" : self
.hostname
,
1235 "myip" : self
.get_address(proto
),
1241 class DDNSProviderZoneedit(DDNSProtocolDynDNS2
, DDNSProvider
):
1242 handle
= "zoneedit.com"
1244 website
= "http://www.zoneedit.com"
1245 protocols
= ("ipv4",)
1247 # Detailed information about the request and the response codes can be
1249 # http://www.zoneedit.com/doc/api/other.html
1250 # http://www.zoneedit.com/faq.html
1252 url
= "https://dynamic.zoneedit.com/auth/dynamic.html"
1254 def update_protocol(self
, proto
):
1256 "dnsto" : self
.get_address(proto
),
1257 "host" : self
.hostname
1260 # Send update to the server.
1261 response
= self
.send_request(self
.url
, username
=self
.username
, password
=self
.password
,
1264 # Get the full response message.
1265 output
= response
.read()
1267 # Handle success messages.
1268 if output
.startswith("<SUCCESS"):
1271 # Handle error codes.
1272 if output
.startswith("invalid login"):
1273 raise DDNSAuthenticationError
1274 elif output
.startswith("<ERROR CODE=\"704\""):
1275 raise DDNSRequestError(_("No valid FQDN was given."))
1276 elif output
.startswith("<ERROR CODE=\"702\""):
1277 raise DDNSInternalServerError
1279 # If we got here, some other update error happened.
1280 raise DDNSUpdateError
1283 class DDNSProviderZZZZ(DDNSProvider
):
1286 website
= "https://zzzz.io"
1287 protocols
= ("ipv6", "ipv4",)
1289 # Detailed information about the update request can be found here:
1290 # https://zzzz.io/faq/
1292 # Details about the possible response codes have been provided in the bugtracker:
1293 # https://bugzilla.ipfire.org/show_bug.cgi?id=10584#c2
1295 url
= "https://zzzz.io/api/v1/update"
1296 can_remove_records
= False
1298 def update_protocol(self
, proto
):
1300 "ip" : self
.get_address(proto
),
1301 "token" : self
.token
,
1305 data
["type"] = "aaaa"
1307 # zzzz uses the host from the full hostname as part
1308 # of the update url.
1309 host
, domain
= self
.hostname
.split(".", 1)
1311 # Add host value to the update url.
1312 url
= "%s/%s" % (self
.url
, host
)
1314 # Send update to the server.
1316 response
= self
.send_request(url
, data
=data
)
1318 # Handle error codes.
1319 except DDNSNotFound
:
1320 raise DDNSRequestError(_("Invalid hostname specified"))
1322 # Handle success messages.
1323 if response
.code
== 200:
1326 # If we got here, some other update error happened.
1327 raise DDNSUpdateError