]> git.ipfire.org Git - oddments/ddns.git/blob - src/ddns/providers.py
Remove useless auth code from various providers.
[oddments/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
24 from i18n import _
25
26 # Import all possible exception types.
27 from .errors import *
28
29 logger = logging.getLogger("ddns.providers")
30 logger.propagate = 1
31
32 class 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
114 @property
115 def protocols(self):
116 return self.INFO.get("protocols")
117
118 def __call__(self, force=False):
119 if force:
120 logger.info(_("Updating %s forced") % self.hostname)
121
122 # Check if we actually need to update this host.
123 elif self.is_uptodate(self.protocols):
124 logger.info(_("%s is already up to date") % self.hostname)
125 return
126
127 # Execute the update.
128 self.update()
129
130 def update(self):
131 raise NotImplementedError
132
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
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
163 class 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
175 def update(self):
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.
185 response = self.send_request(self.url, username=self.username, password=self.password,
186 data=data)
187
188 # Handle success messages.
189 if response.code == 200:
190 return
191
192 # Handle error codes.
193 elif response.code == 401:
194 raise DDNSAuthenticationError
195
196 # If we got here, some other update error happened.
197 raise DDNSUpdateError
198
199
200 class 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
212 def update(self):
213 data = {
214 "domain" : self.hostname,
215 "ip" : self.get_address("ipv4"),
216 }
217
218 # Send update to the server.
219 response = self.send_request(self.url, username=self.username, password=self.password,
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
248
249 class 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
305 class DDNSProviderLightningWireLabs(DDNSProvider):
306 INFO = {
307 "handle" : "dns.lightningwirelabs.com",
308 "name" : "Lightning Wire Labs",
309 "website" : "http://dns.lightningwirelabs.com/",
310 "protocols" : ["ipv6", "ipv4",]
311 }
312
313 # Information about the format of the HTTPS request is to be found
314 # https://dns.lightningwirelabs.com/knowledge-base/api/ddns
315 url = "https://dns.lightningwirelabs.com/update"
316
317 @property
318 def token(self):
319 """
320 Fast access to the token.
321 """
322 return self.get("token")
323
324 def update(self):
325 data = {
326 "hostname" : self.hostname,
327 }
328
329 # Check if we update an IPv6 address.
330 address6 = self.get_address("ipv6")
331 if address6:
332 data["address6"] = address6
333
334 # Check if we update an IPv4 address.
335 address4 = self.get_address("ipv4")
336 if address4:
337 data["address4"] = address4
338
339 # Raise an error if none address is given.
340 if not data.has_key("address6") and not data.has_key("address4"):
341 raise DDNSConfigurationError
342
343 # Check if a token has been set.
344 if self.token:
345 data["token"] = self.token
346
347 # Check for username and password.
348 elif self.username and self.password:
349 data.update({
350 "username" : self.username,
351 "password" : self.password,
352 })
353
354 # Raise an error if no auth details are given.
355 else:
356 raise DDNSConfigurationError
357
358 # Send update to the server.
359 response = self.send_request(self.url, data=data)
360
361 # Handle success messages.
362 if response.code == 200:
363 return
364
365 # Handle error codes.
366 if response.code == 403:
367 raise DDNSAuthenticationError
368 elif response.code == 400:
369 raise DDNSRequestError
370
371 # If we got here, some other update error happened.
372 raise DDNSUpdateError
373
374
375 class DDNSProviderNOIP(DDNSProvider):
376 INFO = {
377 "handle" : "no-ip.com",
378 "name" : "No-IP",
379 "website" : "http://www.no-ip.com/",
380 "protocols" : ["ipv4",]
381 }
382
383 # Information about the format of the HTTP request is to be found
384 # here: http://www.no-ip.com/integrate/request and
385 # here: http://www.no-ip.com/integrate/response
386
387 url = "http://%(username)s:%(password)s@dynupdate.no-ip.com/nic/update"
388
389 def update(self):
390 url = self.url % {
391 "username" : self.username,
392 "password" : self.password,
393 }
394
395 data = {
396 "hostname" : self.hostname,
397 "address" : self.get_address("ipv4"),
398 }
399
400 # Send update to the server.
401 response = self.send_request(url, data=data)
402
403 # Get the full response message.
404 output = response.read()
405
406 # Handle success messages.
407 if output.startswith("good") or output.startswith("nochg"):
408 return
409
410 # Handle error codes.
411 if output == "badauth":
412 raise DDNSAuthenticationError
413 elif output == "aduse":
414 raise DDNSAbuseError
415 elif output == "911":
416 raise DDNSInternalServerError
417
418 # If we got here, some other update error happened.
419 raise DDNSUpdateError
420
421
422 class DDNSProviderSelfhost(DDNSProvider):
423 INFO = {
424 "handle" : "selfhost.de",
425 "name" : "Selfhost.de",
426 "website" : "http://www.selfhost.de/",
427 "protocols" : ["ipv4",],
428 }
429
430 url = "https://carol.selfhost.de/update"
431
432 def update(self):
433 data = {
434 "username" : self.username,
435 "password" : self.password,
436 "textmodi" : "1",
437 }
438
439 response = self.send_request(self.url, data=data)
440
441 match = re.search("status=20(0|4)", response.read())
442 if not match:
443 raise DDNSUpdateError