Use db model hostname validation in cli and improve exception handling

This commit is contained in:
2026-01-24 00:46:48 +01:00
parent cde4b879c1
commit 07e37e525c
3 changed files with 175 additions and 132 deletions

View File

@@ -2,6 +2,7 @@
from __future__ import annotations
import argon2
import base64
import ipaddress
import json
@@ -9,11 +10,6 @@ import logging
import signal
import ssl
import threading
from concurrent.futures import ThreadPoolExecutor
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import parse_qs, urlparse
import argon2
from . import (
datetime_str,
@@ -27,9 +23,18 @@ from . import (
STATUS_BADIP,
)
from .cleanup import ExpiredRecordsCleanupThread, RateLimitCleanupThread
from .dns import detect_ip_type
from .logging import clear_txn_id, set_txn_id
from .models import DoesNotExist, get_hostname_for_user, get_user
from .dns import detect_ip_type, encode_dnsname, EncodingError
from .models import (
DatabaseError,
DoesNotExist,
EncodingError,
get_hostname_for_user,
get_user
)
from concurrent.futures import ThreadPoolExecutor
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import parse_qs, urlparse
def extract_param(params, aliases):
@@ -123,7 +128,9 @@ class DDNSServer(ThreadingHTTPServer):
logging.info(f"Waiting for {self.active_requests} active request(s)")
self.requests_done.wait(timeout=timeout)
if self.active_requests > 0:
logging.warning(f"Shutdown timeout, {self.active_requests} request(s) still active")
logging.warning(
f"Shutdown timeout, {self.active_requests} request(s) still active"
)
def server_close(self):
"""Shutdown thread pool and close server."""
@@ -212,6 +219,9 @@ class DDNSRequestHandler(BaseHTTPRequestHandler):
set_txn_id()
try:
self._handle_get_request()
except Exception as e:
logging.exception(f"Uncaught exception: {e}")
self.respond(500, "Internal Server Error")
finally:
clear_txn_id()
@@ -259,39 +269,41 @@ class DDNSRequestHandler(BaseHTTPRequestHandler):
# Validate credentials
try:
user = get_user(username)
self.app.password_hasher.verify(user.password_hash, password)
except (DoesNotExist, argon2.exceptions.VerifyMismatchError):
logging.warning(f"Auth failed: client={client_ip} user={username}")
self._handle_bad_request(client_ip, 401, STATUS_BADAUTH)
return
try:
user = get_user(username)
self.app.password_hasher.verify(user.password_hash, password)
except (DoesNotExist, argon2.exceptions.VerifyMismatchError):
logging.warning(f"Auth failed: client={client_ip} user={username}")
self._handle_bad_request(client_ip, 401, STATUS_BADAUTH)
return
# Get hostname parameter
hostname_param = extract_param(params, endpoint["params"]["hostname"])
if not hostname_param:
logging.warning(f"Missing hostname: client={client_ip} user={username}")
self._handle_bad_request(client_ip, 400, STATUS_NOHOST)
return
# Get hostname parameter
hostname_param = extract_param(params, endpoint["params"]["hostname"])
if not hostname_param:
logging.warning(f"Missing hostname: client={client_ip} user={username}")
self._handle_bad_request(client_ip, 400, STATUS_NOHOST)
return
# Validate and encode hostname
try:
hostname_param = encode_dnsname(hostname_param)
except EncodingError:
logging.warning(
f"Invalid hostname: client={client_ip}, "
f"hostname={hostname_param}")
self._handle_bad_request(client_ip, 400, STATUS_NOHOST)
return
# Check hostname ownership
try:
hostname = get_hostname_for_user(hostname_param, user)
except DoesNotExist:
logging.warning(
f"Access denied: client={client_ip} user={username} "
f"hostname={hostname_param}"
)
self._handle_bad_request(client_ip, 403, STATUS_NOHOST)
return
except EncodingError:
logging.warning(
f"Invalid hostname: client={client_ip}, "
f"hostname={hostname_param}")
self._handle_bad_request(client_ip, 400, STATUS_NOHOST)
return
# Check hostname ownership
try:
hostname = get_hostname_for_user(hostname_param, user)
except DoesNotExist:
logging.warning(
f"Access denied: client={client_ip} user={username} "
f"hostname={hostname_param}"
)
self._handle_bad_request(client_ip, 403, STATUS_NOHOST)
except DatabaseError as e:
logging.error(f"Database error: {e}")
self.respond(500, "Internal Server Error")
return
# Good rate limit check