]> git.ipfire.org Git - oddments/ddns.git/blame - src/ddns/providers.py
Merge remote-tracking branch 'stevee/fixes'
[oddments/ddns.git] / src / ddns / providers.py
CommitLineData
f22ab085 1#!/usr/bin/python
3fdcb9d1
MT
2###############################################################################
3# #
4# ddns - A dynamic DNS client for IPFire #
5# Copyright (C) 2012 IPFire development team #
6# #
7# This program is free software: you can redistribute it and/or modify #
8# it under the terms of the GNU General Public License as published by #
9# the Free Software Foundation, either version 3 of the License, or #
10# (at your option) any later version. #
11# #
12# This program is distributed in the hope that it will be useful, #
13# but WITHOUT ANY WARRANTY; without even the implied warranty of #
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15# GNU General Public License for more details. #
16# #
17# You should have received a copy of the GNU General Public License #
18# along with this program. If not, see <http://www.gnu.org/licenses/>. #
19# #
20###############################################################################
f22ab085 21
7399fc5b
MT
22import logging
23
24from i18n import _
25
f22ab085
MT
26# Import all possible exception types.
27from .errors import *
28
7399fc5b
MT
29logger = logging.getLogger("ddns.providers")
30logger.propagate = 1
31
f22ab085
MT
32class DDNSProvider(object):
33 INFO = {
34 # A short string that uniquely identifies
35 # this provider.
36 "handle" : None,
37
38 # The full name of the provider.
39 "name" : None,
40
41 # A weburl to the homepage of the provider.
42 # (Where to register a new account?)
43 "website" : None,
44
45 # A list of supported protocols.
46 "protocols" : ["ipv6", "ipv4"],
47 }
48
49 DEFAULT_SETTINGS = {}
50
51 def __init__(self, core, **settings):
52 self.core = core
53
54 # Copy a set of default settings and
55 # update them by those from the configuration file.
56 self.settings = self.DEFAULT_SETTINGS.copy()
57 self.settings.update(settings)
58
59 def __repr__(self):
60 return "<DDNS Provider %s (%s)>" % (self.name, self.handle)
61
62 def __cmp__(self, other):
63 return cmp(self.hostname, other.hostname)
64
65 @property
66 def name(self):
67 """
68 Returns the name of the provider.
69 """
70 return self.INFO.get("name")
71
72 @property
73 def website(self):
74 """
75 Returns the website URL of the provider
76 or None if that is not available.
77 """
78 return self.INFO.get("website", None)
79
80 @property
81 def handle(self):
82 """
83 Returns the handle of this provider.
84 """
85 return self.INFO.get("handle")
86
87 def get(self, key, default=None):
88 """
89 Get a setting from the settings dictionary.
90 """
91 return self.settings.get(key, default)
92
93 @property
94 def hostname(self):
95 """
96 Fast access to the hostname.
97 """
98 return self.get("hostname")
99
100 @property
101 def username(self):
102 """
103 Fast access to the username.
104 """
105 return self.get("username")
106
107 @property
108 def password(self):
109 """
110 Fast access to the password.
111 """
112 return self.get("password")
113
7399fc5b
MT
114 @property
115 def protocols(self):
116 return self.INFO.get("protocols")
117
9da3e685
MT
118 def __call__(self, force=False):
119 if force:
120 logger.info(_("Updating %s forced") % self.hostname)
121
7399fc5b 122 # Check if we actually need to update this host.
9da3e685 123 elif self.is_uptodate(self.protocols):
7399fc5b
MT
124 logger.info(_("%s is already up to date") % self.hostname)
125 return
126
127 # Execute the update.
5f402f36
MT
128 self.update()
129
130 def update(self):
f22ab085
MT
131 raise NotImplementedError
132
7399fc5b
MT
133 def is_uptodate(self, protos):
134 """
135 Returns True if this host is already up to date
136 and does not need to change the IP address on the
137 name server.
138 """
139 for proto in protos:
140 addresses = self.core.system.resolve(self.hostname, proto)
141
142 current_address = self.get_address(proto)
143
144 if not current_address in addresses:
145 return False
146
147 return True
148
f22ab085
MT
149 def send_request(self, *args, **kwargs):
150 """
151 Proxy connection to the send request
152 method.
153 """
154 return self.core.system.send_request(*args, **kwargs)
155
156 def get_address(self, proto):
157 """
158 Proxy method to get the current IP address.
159 """
160 return self.core.system.get_address(proto)
161
162
f3cf1f70
SS
163class DDNSProviderDHS(DDNSProvider):
164 INFO = {
165 "handle" : "dhs.org",
166 "name" : "DHS International",
167 "website" : "http://dhs.org/",
168 "protocols" : ["ipv4",]
169 }
170
171 # No information about the used update api provided on webpage,
172 # grabed from source code of ez-ipudate.
173 url = "http://members.dhs.org/nic/hosts"
174
5f402f36 175 def update(self):
f3cf1f70
SS
176 data = {
177 "domain" : self.hostname,
178 "ip" : self.get_address("ipv4"),
179 "hostcmd" : "edit",
180 "hostcmdstage" : "2",
181 "type" : "4",
182 }
183
184 # Send update to the server.
175c9b80 185 response = self.send_request(self.url, username=self.username, password=self.password,
f3cf1f70
SS
186 data=data)
187
188 # Handle success messages.
189 if response.code == 200:
190 return
191
192 # Handle error codes.
4caed6ed 193 elif response.code == 401:
f3cf1f70
SS
194 raise DDNSAuthenticationError
195
196 # If we got here, some other update error happened.
197 raise DDNSUpdateError
198
199
39301272
SS
200class DDNSProviderDNSpark(DDNSProvider):
201 INFO = {
202 "handle" : "dnspark.com",
203 "name" : "DNS Park",
204 "website" : "http://dnspark.com/",
205 "protocols" : ["ipv4",]
206 }
207
208 # Informations to the used api can be found here:
209 # https://dnspark.zendesk.com/entries/31229348-Dynamic-DNS-API-Documentation
210 url = "https://control.dnspark.com/api/dynamic/update.php"
211
5f402f36 212 def update(self):
39301272
SS
213 data = {
214 "domain" : self.hostname,
215 "ip" : self.get_address("ipv4"),
216 }
217
218 # Send update to the server.
175c9b80 219 response = self.send_request(self.url, username=self.username, password=self.password,
39301272
SS
220 data=data)
221
222 # Get the full response message.
223 output = response.read()
224
225 # Handle success messages.
226 if output.startswith("ok") or output.startswith("nochange"):
227 return
228
229 # Handle error codes.
230 if output == "unauth":
231 raise DDNSAuthenticationError
232 elif output == "abuse":
233 raise DDNSAbuseError
234 elif output == "blocked":
235 raise DDNSBlockedError
236 elif output == "nofqdn":
237 raise DDNSRequestError(_("No valid FQDN was given."))
238 elif output == "nohost":
239 raise DDNSRequestError(_("Invalid hostname specified."))
240 elif output == "notdyn":
241 raise DDNSRequestError(_("Hostname not marked as a dynamic host."))
242 elif output == "invalid":
243 raise DDNSRequestError(_("Invalid IP address has been sent."))
244
245 # If we got here, some other update error happened.
246 raise DDNSUpdateError
247
43b2cd59
SS
248
249class DDNSProviderDtDNS(DDNSProvider):
250 INFO = {
251 "handle" : "dtdns.com",
252 "name" : "DtDNS",
253 "website" : "http://dtdns.com/",
254 "protocols" : ["ipv4",]
255 }
256
257 # Information about the format of the HTTPS request is to be found
258 # http://www.dtdns.com/dtsite/updatespec
259 url = "https://www.dtdns.com/api/autodns.cfm"
260
261
262 def update(self):
263 data = {
264 "ip" : self.get_address("ipv4"),
265 "id" : self.hostname,
266 "pw" : self.password
267 }
268
269 # Send update to the server.
270 response = self.send_request(self.url, data=data)
271
272 # Get the full response message.
273 output = response.read()
274
275 # Remove all leading and trailing whitespace.
276 output = output.strip()
277
278 # Handle success messages.
279 if "now points to" in output:
280 return
281
282 # Handle error codes.
283 if output == "No hostname to update was supplied.":
284 raise DDNSRequestError(_("No hostname specified."))
285
286 elif output == "The hostname you supplied is not valid.":
287 raise DDNSRequestError(_("Invalid hostname specified."))
288
289 elif output == "The password you supplied is not valid.":
290 raise DDNSAuthenticationError
291
292 elif output == "Administration has disabled this account.":
293 raise DDNSRequestError(_("Account has been disabled."))
294
295 elif output == "Illegal character in IP.":
296 raise DDNSRequestError(_("Invalid IP address has been sent."))
297
298 elif output == "Too many failed requests.":
299 raise DDNSRequestError(_("Too many failed requests."))
300
301 # If we got here, some other update error happened.
302 raise DDNSUpdateError
303
304
bfed6701
SS
305class DDNSProviderDynDNS(DDNSProvider):
306 INFO = {
307 "handle" : "dyndns.org",
308 "name" : "Dyn",
309 "website" : "http://dyn.com/dns/",
310 "protocols" : ["ipv4",]
311 }
312
313 # Information about the format of the request is to be found
314 # http://http://dyn.com/support/developers/api/perform-update/
315 # http://dyn.com/support/developers/api/return-codes/
316 url = "https://members.dyndns.org/nic/update"
317
318 def update(self):
319 data = {
320 "hostname" : self.hostname,
321 "myip" : self.get_address("ipv4"),
322 }
323
324 # Send update to the server.
325 response = self.send_request(self.url, username=self.username, password=self.password,
326 data=data)
327
328 # Get the full response message.
329 output = response.read()
330
331 # Handle success messages.
332 if output.startswith("good") or output.startswith("nochg"):
333 return
334
335 # Handle error codes.
336 if output == "badauth":
337 raise DDNSAuthenticationError
338 elif output == "aduse":
339 raise DDNSAbuseError
340 elif output == "notfqdn":
341 raise DDNSRequestError(_("No valid FQDN was given."))
342 elif output == "nohost":
343 raise DDNSRequestError(_("Specified host does not exist."))
344 elif output == "911":
345 raise DDNSInternalServerError
346 elif output == "dnserr":
347 raise DDNSInternalServerError(_("DNS error encountered."))
348
349 # If we got here, some other update error happened.
350 raise DDNSUpdateError
351
352
a08c1b72
SS
353class DDNSProviderLightningWireLabs(DDNSProvider):
354 INFO = {
355 "handle" : "dns.lightningwirelabs.com",
356 "name" : "Lightning Wire Labs",
357 "website" : "http://dns.lightningwirelabs.com/",
358 "protocols" : ["ipv6", "ipv4",]
359 }
360
361 # Information about the format of the HTTPS request is to be found
362 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
363 url = "https://dns.lightningwirelabs.com/update"
364
365 @property
366 def token(self):
367 """
368 Fast access to the token.
369 """
370 return self.get("token")
371
5f402f36 372 def update(self):
a08c1b72
SS
373 data = {
374 "hostname" : self.hostname,
375 }
376
377 # Check if we update an IPv6 address.
378 address6 = self.get_address("ipv6")
379 if address6:
380 data["address6"] = address6
381
382 # Check if we update an IPv4 address.
383 address4 = self.get_address("ipv4")
384 if address4:
385 data["address4"] = address4
386
387 # Raise an error if none address is given.
388 if not data.has_key("address6") and not data.has_key("address4"):
389 raise DDNSConfigurationError
390
391 # Check if a token has been set.
392 if self.token:
393 data["token"] = self.token
394
395 # Check for username and password.
396 elif self.username and self.password:
397 data.update({
398 "username" : self.username,
399 "password" : self.password,
400 })
401
402 # Raise an error if no auth details are given.
403 else:
404 raise DDNSConfigurationError
405
406 # Send update to the server.
cb455540 407 response = self.send_request(self.url, data=data)
a08c1b72
SS
408
409 # Handle success messages.
410 if response.code == 200:
411 return
412
413 # Handle error codes.
2e5ad318 414 if response.code == 403:
a08c1b72 415 raise DDNSAuthenticationError
2e5ad318 416 elif response.code == 400:
a08c1b72
SS
417 raise DDNSRequestError
418
419 # If we got here, some other update error happened.
420 raise DDNSUpdateError
421
422
f22ab085
MT
423class DDNSProviderNOIP(DDNSProvider):
424 INFO = {
425 "handle" : "no-ip.com",
426 "name" : "No-IP",
427 "website" : "http://www.no-ip.com/",
428 "protocols" : ["ipv4",]
429 }
430
431 # Information about the format of the HTTP request is to be found
432 # here: http://www.no-ip.com/integrate/request and
433 # here: http://www.no-ip.com/integrate/response
434
2de06f59 435 url = "http://%(username)s:%(password)s@dynupdate.no-ip.com/nic/update"
f22ab085 436
5f402f36 437 def update(self):
f22ab085 438 url = self.url % {
f22ab085
MT
439 "username" : self.username,
440 "password" : self.password,
2de06f59
MT
441 }
442
443 data = {
444 "hostname" : self.hostname,
f22ab085
MT
445 "address" : self.get_address("ipv4"),
446 }
447
448 # Send update to the server.
2de06f59 449 response = self.send_request(url, data=data)
f22ab085
MT
450
451 # Get the full response message.
452 output = response.read()
453
454 # Handle success messages.
455 if output.startswith("good") or output.startswith("nochg"):
456 return
457
458 # Handle error codes.
459 if output == "badauth":
460 raise DDNSAuthenticationError
461 elif output == "aduse":
462 raise DDNSAbuseError
463 elif output == "911":
464 raise DDNSInternalServerError
465
466 # If we got here, some other update error happened.
467 raise DDNSUpdateError
468
469
470class DDNSProviderSelfhost(DDNSProvider):
471 INFO = {
472 "handle" : "selfhost.de",
473 "name" : "Selfhost.de",
474 "website" : "http://www.selfhost.de/",
475 "protocols" : ["ipv4",],
476 }
477
2de06f59 478 url = "https://carol.selfhost.de/update"
f22ab085 479
5f402f36 480 def update(self):
2de06f59
MT
481 data = {
482 "username" : self.username,
483 "password" : self.password,
484 "textmodi" : "1",
485 }
f22ab085 486
2de06f59 487 response = self.send_request(self.url, data=data)
f22ab085
MT
488
489 match = re.search("status=20(0|4)", response.read())
490 if not match:
491 raise DDNSUpdateError