2013-09-28 18:46:31 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
2013-09-28 21:41:47 +02:00
|
|
|
|
|
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2013-09-28 18:46:31 +02:00
|
|
|
from django.http import HttpResponse
|
|
|
|
from django.conf import settings
|
2013-09-29 00:34:26 +02:00
|
|
|
from django.contrib.auth.hashers import check_password
|
2013-09-29 02:07:58 +02:00
|
|
|
from django.contrib.auth.decorators import login_required
|
2013-09-29 00:34:26 +02:00
|
|
|
|
2013-09-28 23:32:15 +02:00
|
|
|
from main.models import Host
|
2013-09-28 20:18:20 +02:00
|
|
|
import dns.inet
|
2013-09-29 01:12:23 +02:00
|
|
|
import os
|
2013-09-28 18:46:31 +02:00
|
|
|
|
2013-09-28 20:35:30 +02:00
|
|
|
from main.dnstools import update, SameIpError
|
|
|
|
|
|
|
|
|
2013-09-28 18:46:31 +02:00
|
|
|
def MyIpView(request):
|
2013-09-28 19:01:40 +02:00
|
|
|
return HttpResponse(request.META['REMOTE_ADDR'], content_type="text/plain")
|
|
|
|
|
2013-09-28 20:35:30 +02:00
|
|
|
|
2013-09-29 02:07:58 +02:00
|
|
|
def DetectIpView(request):
|
2013-09-28 20:18:20 +02:00
|
|
|
ipaddr = request.META['REMOTE_ADDR']
|
|
|
|
af = dns.inet.af_for_address(ipaddr)
|
2013-09-28 20:42:18 +02:00
|
|
|
key = 'ipv4' if af == dns.inet.AF_INET else 'ipv6'
|
|
|
|
request.session[key] = ipaddr
|
2013-09-29 01:59:47 +02:00
|
|
|
with open(os.path.join(settings.STATIC_ROOT, "1px.gif"), "rb") as f:
|
|
|
|
image_data = f.read()
|
2013-09-29 01:12:23 +02:00
|
|
|
return HttpResponse(image_data, mimetype="image/png")
|
2013-09-28 20:35:30 +02:00
|
|
|
|
|
|
|
|
2013-09-28 23:32:15 +02:00
|
|
|
def basic_challenge(realm, content='Authorization Required'):
|
|
|
|
"""
|
|
|
|
Construct a 401 response requesting http basic auth.
|
|
|
|
|
|
|
|
:param realm: realm string (displayed by the browser)
|
|
|
|
:param content: request body content
|
|
|
|
:return: HttpResponse object
|
|
|
|
"""
|
|
|
|
response = HttpResponse(content, content_type="text/plain")
|
2013-09-28 20:35:30 +02:00
|
|
|
response['WWW-Authenticate'] = 'Basic realm="%s"' % (realm, )
|
|
|
|
response.status_code = 401
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
|
def basic_authenticate(auth):
|
2013-09-28 23:32:15 +02:00
|
|
|
"""
|
|
|
|
Get username and password from http basic auth string.
|
|
|
|
|
|
|
|
:param auth: http basic auth string
|
|
|
|
:return: username, password
|
|
|
|
"""
|
2013-09-28 20:35:30 +02:00
|
|
|
authmeth, auth = auth.split(' ', 1)
|
|
|
|
if authmeth.lower() != 'basic':
|
|
|
|
return
|
|
|
|
auth = auth.strip().decode('base64')
|
|
|
|
username, password = auth.split(':', 1)
|
|
|
|
return username, password
|
|
|
|
|
|
|
|
|
2013-09-29 02:42:20 +02:00
|
|
|
def check_api_auth(username, password):
|
2013-09-28 23:32:15 +02:00
|
|
|
"""
|
|
|
|
Check username and password against our database.
|
|
|
|
|
|
|
|
:param username: http basic auth username (== fqdn)
|
|
|
|
:param password: update password
|
|
|
|
:return: True if authenticated, False otherwise.
|
|
|
|
"""
|
2013-09-29 00:34:26 +02:00
|
|
|
fqdn = username
|
|
|
|
hosts = Host.objects.filter(fqdn=fqdn)
|
|
|
|
num_hosts = len(hosts)
|
|
|
|
if num_hosts == 0:
|
|
|
|
return False
|
|
|
|
if num_hosts > 1:
|
|
|
|
logging.error("fqdn %s has multiple entries" % fqdn)
|
|
|
|
return False
|
|
|
|
password_hash = hosts[0].update_secret
|
|
|
|
return check_password(password, password_hash)
|
2013-09-28 20:35:30 +02:00
|
|
|
|
|
|
|
|
2013-09-29 02:42:20 +02:00
|
|
|
def check_session_auth(user, hostname):
|
|
|
|
"""
|
|
|
|
Check our database whether the hostname is owned by the user.
|
|
|
|
|
|
|
|
:param user: django user object
|
|
|
|
:param hostname: fqdn
|
|
|
|
:return: True if hostname is owned by this user, False otherwise.
|
|
|
|
"""
|
|
|
|
hosts = Host.objects.filter(fqdn=hostname, created_by=user)
|
|
|
|
num_hosts = len(hosts)
|
|
|
|
if num_hosts == 0:
|
|
|
|
return False
|
|
|
|
if num_hosts > 1:
|
|
|
|
logging.error("fqdn %s has multiple entries" % fqdn)
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2013-09-28 21:41:47 +02:00
|
|
|
def Response(content):
|
2013-09-28 20:35:30 +02:00
|
|
|
return HttpResponse(content, content_type='text/plain')
|
|
|
|
|
|
|
|
|
|
|
|
def NicUpdateView(request):
|
2013-09-28 23:32:15 +02:00
|
|
|
"""
|
|
|
|
dyndns2 compatible /nic/update API.
|
|
|
|
|
|
|
|
Example URLs:
|
|
|
|
|
|
|
|
Will request username (fqdn) and password (secret) from user,
|
|
|
|
for interactive testing / updating:
|
|
|
|
https://nsupdate.info/nic/update
|
|
|
|
|
|
|
|
You can put it also into the url, so the browser will automatically
|
|
|
|
send the http basic auth with the request:
|
|
|
|
https://fqdn:secret@nsupdate.info/nic/update
|
|
|
|
|
|
|
|
If the request does not come from the correct IP, you can give it as
|
|
|
|
a query parameter, you can also give the hostname (then it won't use
|
|
|
|
the username from http basic auth as the fqdn:
|
|
|
|
https://fqdn:secret@nsupdate.info/nic/update?hostname=fqdn&myip=1.2.3.4
|
|
|
|
"""
|
2013-09-28 21:41:47 +02:00
|
|
|
hostname = request.GET.get('hostname')
|
2013-09-28 20:35:30 +02:00
|
|
|
auth = request.META.get('HTTP_AUTHORIZATION')
|
|
|
|
if auth is None:
|
2013-09-28 21:41:47 +02:00
|
|
|
logger.warning('%s - received no auth' % (hostname, ))
|
2013-09-28 23:32:15 +02:00
|
|
|
return basic_challenge("authenticate to update DNS", 'noauth')
|
2013-09-28 20:35:30 +02:00
|
|
|
username, password = basic_authenticate(auth)
|
2013-09-29 02:42:20 +02:00
|
|
|
if not check_api_auth(username, password):
|
2013-09-28 21:41:47 +02:00
|
|
|
logger.info('%s - received bad credentials, username: %s' % (hostname, username, ))
|
2013-09-28 23:32:15 +02:00
|
|
|
return basic_challenge("authenticate to update DNS", 'badauth')
|
2013-09-28 21:41:47 +02:00
|
|
|
if hostname is None:
|
|
|
|
# as we use update_username == hostname, we can fall back to that:
|
|
|
|
hostname = username
|
2013-09-28 20:35:30 +02:00
|
|
|
ipaddr = request.GET.get('myip')
|
|
|
|
if ipaddr is None:
|
|
|
|
ipaddr = request.META.get('REMOTE_ADDR')
|
2013-09-28 21:41:47 +02:00
|
|
|
agent = request.META.get('HTTP_USER_AGENT')
|
|
|
|
if agent in settings.BAD_AGENTS:
|
|
|
|
logger.info('%s - received update from bad user agent %s' % (hostname, agent, ))
|
|
|
|
return Response('badagent')
|
2013-09-29 02:42:20 +02:00
|
|
|
return _update(hostname, ipaddr)
|
|
|
|
|
|
|
|
|
|
|
|
@login_required
|
|
|
|
def AuthorizedNicUpdateView(request):
|
|
|
|
"""
|
|
|
|
similar to NicUpdateView, but the client is not a router or other dyndns client,
|
|
|
|
but the admin browser who is currently logged into the nsupdate.info site.
|
|
|
|
|
|
|
|
Example URLs:
|
|
|
|
|
|
|
|
https://supdate.info/nic/update?hostname=fqdn&myip=1.2.3.4
|
|
|
|
"""
|
|
|
|
hostname = request.GET.get('hostname')
|
|
|
|
if hostname is None:
|
|
|
|
return Response('nohost')
|
|
|
|
if not check_session_auth(request.user, hostname):
|
|
|
|
logger.info('%s - is not owned by user: %s' % (hostname, request.user.username, ))
|
|
|
|
return Response('nohost')
|
|
|
|
ipaddr = request.GET.get('myip')
|
|
|
|
if ipaddr is None:
|
|
|
|
ipaddr = request.META.get('REMOTE_ADDR')
|
|
|
|
return _update(hostname, ipaddr)
|
|
|
|
|
|
|
|
|
|
|
|
def _update(hostname, ipaddr):
|
2013-09-28 20:35:30 +02:00
|
|
|
ipaddr = str(ipaddr) # XXX bug in dnspython: crashes if ipaddr is unicode, wants a str!
|
|
|
|
try:
|
|
|
|
update(hostname, ipaddr)
|
2013-09-28 21:41:47 +02:00
|
|
|
logger.info('%s - received good update -> ip: %s' % (hostname, ipaddr, ))
|
2013-09-28 20:35:30 +02:00
|
|
|
return Response('good %s' % ipaddr)
|
|
|
|
except SameIpError:
|
2013-09-28 21:41:47 +02:00
|
|
|
logger.warning('%s - received no-change update, ip: %s' % (hostname, ipaddr, ))
|
2013-09-28 20:35:30 +02:00
|
|
|
return Response('nochg %s' % ipaddr)
|