Files
ivatar/ivatar/tools/views.py
2025-10-24 13:51:45 +02:00

330 lines
11 KiB
Python

"""
View classes for ivatar/tools/
"""
from socket import inet_ntop, AF_INET6
import hashlib
import random
from django.views.generic.edit import FormView
from django.urls import reverse_lazy as reverse
from django.shortcuts import render
import DNS
from libravatar import libravatar_url, parse_user_identity
from libravatar import SECURE_BASE_URL as LIBRAVATAR_SECURE_BASE_URL
from libravatar import BASE_URL as LIBRAVATAR_BASE_URL
from ivatar.settings import SECURE_BASE_URL, BASE_URL, SITE_NAME, DEBUG
from .forms import (
CheckDomainForm,
CheckForm,
) # pylint: disable=relative-beyond-top-level
class CheckDomainView(FormView):
"""
View class for checking a domain
"""
template_name = "check_domain.html"
form_class = CheckDomainForm
success_url = reverse("tools_check_domain")
def form_valid(self, form):
super().form_valid(form)
domain = form.cleaned_data["domain"]
result = {"avatar_server_http": lookup_avatar_server(domain, False)}
if result["avatar_server_http"]:
result["avatar_server_http_ipv4"] = lookup_ip_address(
result["avatar_server_http"], False
)
result["avatar_server_http_ipv6"] = lookup_ip_address(
result["avatar_server_http"], True
)
result["avatar_server_https"] = lookup_avatar_server(domain, True)
if result["avatar_server_https"]:
result["avatar_server_https_ipv4"] = lookup_ip_address(
result["avatar_server_https"], False
)
result["avatar_server_https_ipv6"] = lookup_ip_address(
result["avatar_server_https"], True
)
return render(
self.request,
self.template_name,
{
"form": form,
"result": result,
},
)
class CheckView(FormView):
"""
View class for checking an e-mail or openid address
"""
template_name = "check.html"
form_class = CheckForm
success_url = reverse("tools_check")
def form_valid(self, form):
mailurl = None
openidurl = None
mailurl_secure = None
mailurl_secure_256 = None
openidurl_secure = None
mail_hash = None
mail_hash256 = None
openid_hash = None
super().form_valid(form)
if form.cleaned_data["default_url"]:
default_url = form.cleaned_data["default_url"]
elif (
form.cleaned_data["default_opt"]
and form.cleaned_data["default_opt"] != "none"
):
default_url = form.cleaned_data["default_opt"]
else:
default_url = None
size = form.cleaned_data["size"] if "size" in form.cleaned_data else 80
if form.cleaned_data["mail"]:
mailurl = libravatar_url(
email=form.cleaned_data["mail"], size=size, default=default_url
)
mailurl = mailurl.replace(LIBRAVATAR_BASE_URL, BASE_URL)
mailurl_secure = libravatar_url(
email=form.cleaned_data["mail"],
size=size,
https=True,
default=default_url,
)
mailurl_secure = mailurl_secure.replace(
LIBRAVATAR_SECURE_BASE_URL, SECURE_BASE_URL
)
mail_hash = parse_user_identity(
email=form.cleaned_data["mail"], openid=None
)[0]
hash_obj = hashlib.new("sha256")
hash_obj.update(form.cleaned_data["mail"].encode("utf-8"))
mail_hash256 = hash_obj.hexdigest()
mailurl_secure_256 = mailurl_secure.replace(mail_hash, mail_hash256)
if form.cleaned_data["openid"]:
if not form.cleaned_data["openid"].startswith(
"http://"
) and not form.cleaned_data["openid"].startswith("https://"):
form.cleaned_data["openid"] = f'http://{form.cleaned_data["openid"]}'
openidurl = libravatar_url(
openid=form.cleaned_data["openid"], size=size, default=default_url
)
openidurl = openidurl.replace(LIBRAVATAR_BASE_URL, BASE_URL)
openidurl_secure = libravatar_url(
openid=form.cleaned_data["openid"],
size=size,
https=True,
default=default_url,
)
openidurl_secure = openidurl_secure.replace(
LIBRAVATAR_SECURE_BASE_URL, SECURE_BASE_URL
)
openid_hash = parse_user_identity(
openid=form.cleaned_data["openid"], email=None
)[0]
if "DEVELOPMENT" in SITE_NAME and DEBUG:
if mailurl:
mailurl = mailurl.replace(
"https://avatars.linux-kernel.at",
f"http://{self.request.get_host()}",
)
if mailurl_secure:
mailurl_secure = mailurl_secure.replace(
"https://avatars.linux-kernel.at",
f"http://{self.request.get_host()}",
)
if mailurl_secure_256:
mailurl_secure_256 = mailurl_secure_256.replace(
"https://avatars.linux-kernel.at",
f"http://{self.request.get_host()}",
)
if openidurl:
openidurl = openidurl.replace(
"https://avatars.linux-kernel.at",
f"http://{self.request.get_host()}",
)
if openidurl_secure:
openidurl_secure = openidurl_secure.replace(
"https://avatars.linux-kernel.at",
f"http://{self.request.get_host()}",
)
print(mailurl, openidurl, mailurl_secure, mailurl_secure_256, openidurl_secure)
return render(
self.request,
self.template_name,
{
"form": form,
"mailurl": mailurl,
"openidurl": openidurl,
"mailurl_secure": mailurl_secure,
"mailurl_secure_256": mailurl_secure_256,
"openidurl_secure": openidurl_secure,
"mail_hash": mail_hash,
"mail_hash256": mail_hash256,
"openid_hash": openid_hash,
"size": size,
},
)
def lookup_avatar_server(domain, https):
"""
Extract the avatar server from an SRV record in the DNS zone
The SRV records should look like this:
_avatars._tcp.example.com. IN SRV 0 0 80 avatars.example.com
_avatars-sec._tcp.example.com. IN SRV 0 0 443 avatars.example.com
"""
if domain and len(domain) > 60:
domain = domain[:60]
service_name = None
if https:
service_name = f"_avatars-sec._tcp.{domain}"
else:
service_name = f"_avatars._tcp.{domain}"
DNS.DiscoverNameServers()
try:
dns_request = DNS.Request(name=service_name, qtype="SRV").req()
except DNS.DNSError as message:
print(f"DNS Error: {message} ({domain})")
return None
if dns_request.header["status"] == "NXDOMAIN":
# Not an error, but no point in going any further
return None
if dns_request.header["status"] != "NOERROR":
print(f'DNS Error: status={dns_request.header["status"]} ({domain})')
return None
records = []
for answer in dns_request.answers:
if (
("data" not in answer)
or (not answer["data"])
or (not answer["typename"])
or (answer["typename"] != "SRV")
):
continue
record = {
"priority": int(answer["data"][0]),
"weight": int(answer["data"][1]),
"port": int(answer["data"][2]),
"target": answer["data"][3],
}
records.append(record)
target, port = srv_hostname(records)
if target and ((https and port != 443) or (not https and port != 80)):
return f"{target}:{port}"
return target
def srv_hostname(records):
"""
Return the right (target, port) pair from a list of SRV records.
"""
if len(records) < 1:
return (None, None)
if len(records) == 1:
ret = records[0]
return (ret["target"], ret["port"])
# Keep only the servers in the top priority
priority_records = []
total_weight = 0
top_priority = records[0]["priority"] # highest priority = lowest number
for ret in records:
if ret["priority"] > top_priority:
# ignore the record (ret has lower priority)
continue
# Take care - this if is only a if, if the above if
# uses continue at the end. else it should be an elsif
if ret["priority"] < top_priority:
# reset the priority (ret has higher priority)
top_priority = ret["priority"]
total_weight = 0
priority_records = []
total_weight += ret["weight"]
if ret["weight"] > 0:
priority_records.append((total_weight, ret))
else:
# zero-weight elements must come first
priority_records.insert(0, (0, ret))
if len(priority_records) == 1:
unused, ret = priority_records[0] # pylint: disable=unused-variable
return (ret["target"], ret["port"])
# Select first record according to RFC2782 weight ordering algorithm (page 3)
random_number = random.randint(0, total_weight)
for record in priority_records:
weighted_index, ret = record
if weighted_index >= random_number:
return (ret["target"], ret["port"])
print("There is something wrong with our SRV weight ordering algorithm")
return (None, None)
def lookup_ip_address(hostname, ipv6):
"""
Try to get IPv4 or IPv6 addresses for the given hostname
"""
DNS.DiscoverNameServers()
try:
if ipv6:
dns_request = DNS.Request(name=hostname, qtype=DNS.Type.AAAA).req()
else:
dns_request = DNS.Request(name=hostname, qtype=DNS.Type.A).req()
except DNS.DNSError as message:
print(f"DNS Error: {message} ({hostname})")
return None
if dns_request.header["status"] != "NOERROR":
print(f'DNS Error: status={dns_request.header["status"]} ({hostname})')
return None
for answer in dns_request.answers:
if ("data" not in answer) or (not answer["data"]):
continue
if (ipv6 and answer["typename"] != "AAAA") or (
not ipv6 and answer["typename"] != "A"
):
continue # skip CNAME records
return inet_ntop(AF_INET6, answer["data"]) if ipv6 else answer["data"]
return None