Add GET parameter to allow useres to change expiry TTL
This commit is contained in:
17
README.md
17
README.md
@@ -110,6 +110,9 @@ path = "/var/lib/ddns-service/ddns.db" # required for sqlite
|
|||||||
[defaults]
|
[defaults]
|
||||||
# dns_ttl = 60 # default: 60
|
# dns_ttl = 60 # default: 60
|
||||||
# expiry_ttl = 3600 # default: 3600
|
# expiry_ttl = 3600 # default: 3600
|
||||||
|
# expiry_ttl_min = # optional, min value via HTTP
|
||||||
|
# expiry_ttl_max = # optional, max value via HTTP
|
||||||
|
# expiry_ttl_allow_zero = true # default: true, allow 0 via HTTP
|
||||||
|
|
||||||
[email]
|
[email]
|
||||||
# enabled = false # default: false
|
# enabled = false # default: false
|
||||||
@@ -182,6 +185,7 @@ ipv6 = ["myip6", "ipv6", "ip6"]
|
|||||||
username = ["username", "user"]
|
username = ["username", "user"]
|
||||||
password = ["password", "pass", "token"]
|
password = ["password", "pass", "token"]
|
||||||
notify_change = ["notify_change"]
|
notify_change = ["notify_change"]
|
||||||
|
expiry_ttl = ["expiry_ttl"]
|
||||||
|
|
||||||
[[endpoints]]
|
[[endpoints]]
|
||||||
path = "/nic/update"
|
path = "/nic/update"
|
||||||
@@ -192,6 +196,7 @@ ipv6 = ["myip6"]
|
|||||||
username = ["username"]
|
username = ["username"]
|
||||||
password = ["password"]
|
password = ["password"]
|
||||||
notify_change = []
|
notify_change = []
|
||||||
|
expiry_ttl = []
|
||||||
```
|
```
|
||||||
|
|
||||||
**Default accepted parameter names** (first match wins):
|
**Default accepted parameter names** (first match wins):
|
||||||
@@ -203,6 +208,7 @@ notify_change = []
|
|||||||
| username | username, user |
|
| username | username, user |
|
||||||
| password | password, pass, token |
|
| password | password, pass, token |
|
||||||
| notify_change | notify_change |
|
| notify_change | notify_change |
|
||||||
|
| expiry_ttl | expiry_ttl |
|
||||||
|
|
||||||
## CLI Usage
|
## CLI Usage
|
||||||
|
|
||||||
@@ -295,7 +301,7 @@ kill -HUP $(pidof ddns-service)
|
|||||||
### Request
|
### Request
|
||||||
|
|
||||||
```
|
```
|
||||||
GET /update?hostname=mypc.dyn.example.com[&myip=1.2.3.4][&myip6=2001:db8::1][¬ify_change=1]
|
GET /update?hostname=mypc.dyn.example.com[&myip=1.2.3.4][&myip6=2001:db8::1][¬ify_change=1][&expiry_ttl=7200]
|
||||||
Authorization: Basic base64(username:password)
|
Authorization: Basic base64(username:password)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -306,6 +312,8 @@ GET /update?hostname=mypc.dyn.example.com&username=myuser&password=secret
|
|||||||
|
|
||||||
Set `notify_change=1` to receive an email notification when the IP address changes. Requires email to be enabled and a change notification template configured.
|
Set `notify_change=1` to receive an email notification when the IP address changes. Requires email to be enabled and a change notification template configured.
|
||||||
|
|
||||||
|
Set `expiry_ttl=N` to change the hostname's expiry TTL (in seconds). Can be sent alone without IP parameters.
|
||||||
|
|
||||||
### IP Detection
|
### IP Detection
|
||||||
|
|
||||||
- If `myip` and/or `myip6` provided: use those values
|
- If `myip` and/or `myip6` provided: use those values
|
||||||
@@ -325,7 +333,7 @@ Set `notify_change=1` to receive an email notification when the IP address chang
|
|||||||
|
|
||||||
**JSON (with `Accept: application/json`):**
|
**JSON (with `Accept: application/json`):**
|
||||||
```json
|
```json
|
||||||
{"status": "good", "ipv4": "1.2.3.4", "ipv6": "2001:db8::1"}
|
{"status": "good", "ipv4": "1.2.3.4", "ipv6": "2001:db8::1", "expiry_ttl": 3600}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Client Examples
|
## Client Examples
|
||||||
@@ -346,6 +354,11 @@ With change notification:
|
|||||||
curl -u "username:password" "https://ddns.example.com/update?hostname=mypc.dyn.example.com¬ify_change=1"
|
curl -u "username:password" "https://ddns.example.com/update?hostname=mypc.dyn.example.com¬ify_change=1"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Change expiry TTL:
|
||||||
|
```bash
|
||||||
|
curl -u "username:password" "https://ddns.example.com/update?hostname=mypc.dyn.example.com&expiry_ttl=7200"
|
||||||
|
```
|
||||||
|
|
||||||
### wget
|
### wget
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ path = "/var/lib/ddns-service/ddns.db" # required for sqlite
|
|||||||
[defaults]
|
[defaults]
|
||||||
# dns_ttl = 60 # default, DNS record TTL in seconds
|
# dns_ttl = 60 # default, DNS record TTL in seconds
|
||||||
# expiry_ttl = 3600 # default, 0 to disable expiration
|
# expiry_ttl = 3600 # default, 0 to disable expiration
|
||||||
|
# expiry_ttl_min = # optional, min value allowed via HTTP
|
||||||
|
# expiry_ttl_max = # optional, max value allowed via HTTP
|
||||||
|
# expiry_ttl_allow_zero = true # default, allow 0 (never expire) via HTTP
|
||||||
|
|
||||||
[email]
|
[email]
|
||||||
# enabled = false # default
|
# enabled = false # default
|
||||||
@@ -70,6 +73,7 @@ from_address = "ddns@example.com" # required if email.enabled
|
|||||||
# username: username, user
|
# username: username, user
|
||||||
# password: password, pass, token
|
# password: password, pass, token
|
||||||
# notify_change: notify_change
|
# notify_change: notify_change
|
||||||
|
# expiry_ttl: expiry_ttl
|
||||||
#
|
#
|
||||||
# Multiple endpoints can be defined with custom parameter names
|
# Multiple endpoints can be defined with custom parameter names
|
||||||
|
|
||||||
@@ -82,6 +86,7 @@ from_address = "ddns@example.com" # required if email.enabled
|
|||||||
# username = ["username", "user"]
|
# username = ["username", "user"]
|
||||||
# password = ["password", "pass", "token"]
|
# password = ["password", "pass", "token"]
|
||||||
# notify_change = ["notify_change"]
|
# notify_change = ["notify_change"]
|
||||||
|
# expiry_ttl = ["expiry_ttl"]
|
||||||
|
|
||||||
# [[endpoints]]
|
# [[endpoints]]
|
||||||
# path = "/nic/update"
|
# path = "/nic/update"
|
||||||
@@ -92,3 +97,4 @@ from_address = "ddns@example.com" # required if email.enabled
|
|||||||
# username = ["username"]
|
# username = ["username"]
|
||||||
# password = ["password"]
|
# password = ["password"]
|
||||||
# notify_change = []
|
# notify_change = []
|
||||||
|
# expiry_ttl = []
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ DEFAULT_ENDPOINT_PARAMS = {
|
|||||||
"username": ["username", "user"],
|
"username": ["username", "user"],
|
||||||
"password": ["password", "pass", "token"],
|
"password": ["password", "pass", "token"],
|
||||||
"notify_change": ["notify_change"],
|
"notify_change": ["notify_change"],
|
||||||
|
"expiry_ttl": ["expiry_ttl"],
|
||||||
}
|
}
|
||||||
|
|
||||||
VALID_PARAM_KEYS = frozenset(DEFAULT_ENDPOINT_PARAMS.keys())
|
VALID_PARAM_KEYS = frozenset(DEFAULT_ENDPOINT_PARAMS.keys())
|
||||||
@@ -178,6 +179,9 @@ def load_config(config_path):
|
|||||||
cfg.setdefault("defaults", {})
|
cfg.setdefault("defaults", {})
|
||||||
cfg["defaults"].setdefault("dns_ttl", 60)
|
cfg["defaults"].setdefault("dns_ttl", 60)
|
||||||
cfg["defaults"].setdefault("expiry_ttl", 3600)
|
cfg["defaults"].setdefault("expiry_ttl", 3600)
|
||||||
|
cfg["defaults"].setdefault("expiry_ttl_min", None)
|
||||||
|
cfg["defaults"].setdefault("expiry_ttl_max", None)
|
||||||
|
cfg["defaults"].setdefault("expiry_ttl_allow_zero", True)
|
||||||
|
|
||||||
cfg.setdefault("email", {})
|
cfg.setdefault("email", {})
|
||||||
cfg["email"].setdefault("enabled", False)
|
cfg["email"].setdefault("enabled", False)
|
||||||
|
|||||||
@@ -377,10 +377,77 @@ class DDNSRequestHandler(BaseHTTPRequestHandler):
|
|||||||
else:
|
else:
|
||||||
ipv6 = ip
|
ipv6 = ip
|
||||||
|
|
||||||
|
# Process expiry_ttl parameter
|
||||||
|
expiry_ttl_param = extract_param(
|
||||||
|
params, endpoint["params"]["expiry_ttl"])
|
||||||
|
expiry_ttl = None
|
||||||
|
if expiry_ttl_param:
|
||||||
|
try:
|
||||||
|
expiry_ttl = int(expiry_ttl_param)
|
||||||
|
if expiry_ttl < 0:
|
||||||
|
raise ValueError
|
||||||
|
except ValueError:
|
||||||
|
raise DDNSClientError(
|
||||||
|
"Invalid expiry_ttl",
|
||||||
|
400,
|
||||||
|
STATUS_NOHOST,
|
||||||
|
client=client_ip,
|
||||||
|
username=username,
|
||||||
|
hostname=hostname.hostname,
|
||||||
|
zone=hostname.zone,
|
||||||
|
expiry_ttl=expiry_ttl_param
|
||||||
|
)
|
||||||
|
|
||||||
|
# Validate bounds
|
||||||
|
defaults = self.app.config["defaults"]
|
||||||
|
allow_zero = defaults.get("expiry_ttl_allow_zero", True)
|
||||||
|
ttl_min = defaults.get("expiry_ttl_min")
|
||||||
|
ttl_max = defaults.get("expiry_ttl_max")
|
||||||
|
|
||||||
|
if expiry_ttl == 0:
|
||||||
|
if not allow_zero:
|
||||||
|
raise DDNSClientError(
|
||||||
|
"Zero expiry_ttl not allowed",
|
||||||
|
400,
|
||||||
|
STATUS_NOHOST,
|
||||||
|
client=client_ip,
|
||||||
|
username=username,
|
||||||
|
hostname=hostname.hostname,
|
||||||
|
zone=hostname.zone,
|
||||||
|
expiry_ttl=expiry_ttl
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if ttl_min is not None and expiry_ttl < ttl_min:
|
||||||
|
raise DDNSClientError(
|
||||||
|
"expiry_ttl below minimum",
|
||||||
|
400,
|
||||||
|
STATUS_NOHOST,
|
||||||
|
client=client_ip,
|
||||||
|
username=username,
|
||||||
|
hostname=hostname.hostname,
|
||||||
|
zone=hostname.zone,
|
||||||
|
expiry_ttl=expiry_ttl,
|
||||||
|
min=ttl_min
|
||||||
|
)
|
||||||
|
if ttl_max is not None and expiry_ttl > ttl_max:
|
||||||
|
raise DDNSClientError(
|
||||||
|
"expiry_ttl above maximum",
|
||||||
|
400,
|
||||||
|
STATUS_NOHOST,
|
||||||
|
client=client_ip,
|
||||||
|
username=username,
|
||||||
|
hostname=hostname.hostname,
|
||||||
|
zone=hostname.zone,
|
||||||
|
expiry_ttl=expiry_ttl,
|
||||||
|
max=ttl_max
|
||||||
|
)
|
||||||
|
|
||||||
# Process notify_change parameter
|
# Process notify_change parameter
|
||||||
notify_change = extract_param(params, endpoint["params"]["notify_change"])
|
notify_change = extract_param(
|
||||||
notify_change = notify_change.lower() in ["1", "y", "yes", "on", "true"] \
|
params, endpoint["params"]["notify_change"])
|
||||||
if notify_change else False
|
notify_change = (notify_change.lower() in
|
||||||
|
["1", "y", "yes", "on", "true"]
|
||||||
|
if notify_change else False)
|
||||||
|
|
||||||
# Good rate limit check
|
# Good rate limit check
|
||||||
if self.app.good_limiter:
|
if self.app.good_limiter:
|
||||||
@@ -406,7 +473,8 @@ class DDNSRequestHandler(BaseHTTPRequestHandler):
|
|||||||
hostname,
|
hostname,
|
||||||
ipv4,
|
ipv4,
|
||||||
ipv6,
|
ipv6,
|
||||||
notify_change
|
notify_change,
|
||||||
|
expiry_ttl
|
||||||
)
|
)
|
||||||
|
|
||||||
def _authenticate(self, client_ip, username, password):
|
def _authenticate(self, client_ip, username, password):
|
||||||
@@ -466,7 +534,8 @@ class DDNSRequestHandler(BaseHTTPRequestHandler):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"DNS rollback failed ({record_type}): {e}")
|
logging.error(f"DNS rollback failed ({record_type}): {e}")
|
||||||
|
|
||||||
def _process_ip_update(self, client_ip, user, hostname, ipv4, ipv6, notify_change):
|
def _process_ip_update(self, client_ip, user, hostname, ipv4, ipv6,
|
||||||
|
notify_change, expiry_ttl):
|
||||||
"""Process IP update for hostname."""
|
"""Process IP update for hostname."""
|
||||||
now = now_utc()
|
now = now_utc()
|
||||||
|
|
||||||
@@ -475,6 +544,12 @@ class DDNSRequestHandler(BaseHTTPRequestHandler):
|
|||||||
ipv4_changed = False
|
ipv4_changed = False
|
||||||
ipv6_changed = False
|
ipv6_changed = False
|
||||||
|
|
||||||
|
# Apply expiry_ttl if provided
|
||||||
|
expiry_ttl_changed = False
|
||||||
|
if expiry_ttl is not None and expiry_ttl != hostname.expiry_ttl:
|
||||||
|
hostname.expiry_ttl = expiry_ttl
|
||||||
|
expiry_ttl_changed = True
|
||||||
|
|
||||||
if ipv4:
|
if ipv4:
|
||||||
hostname.last_ipv4_update = now
|
hostname.last_ipv4_update = now
|
||||||
if ipv4 != hostname.last_ipv4:
|
if ipv4 != hostname.last_ipv4:
|
||||||
@@ -546,28 +621,30 @@ class DDNSRequestHandler(BaseHTTPRequestHandler):
|
|||||||
zone=hostname.zone
|
zone=hostname.zone
|
||||||
)
|
)
|
||||||
|
|
||||||
changed_addrs = ""
|
if not ipv4_changed and not ipv6_changed and not expiry_ttl_changed:
|
||||||
if ipv4_changed:
|
|
||||||
changed_addrs += f" ipv4={ipv4}"
|
|
||||||
if ipv6_changed:
|
|
||||||
changed_addrs += f" ipv6={ipv6}"
|
|
||||||
|
|
||||||
if not ipv4_changed and not ipv6_changed:
|
|
||||||
logging.info(
|
logging.info(
|
||||||
f"No change: client={client_ip} hostname={hostname.hostname} "
|
f"No change: client={client_ip} hostname={hostname.hostname} "
|
||||||
f"zone={hostname.zone}{changed_addrs} notify_change={str(notify_change).lower()}"
|
f"zone={hostname.zone} notify_change={str(notify_change).lower()}"
|
||||||
)
|
)
|
||||||
self.respond(
|
self.respond(
|
||||||
200,
|
200,
|
||||||
STATUS_NOCHG,
|
STATUS_NOCHG,
|
||||||
ipv4=hostname.last_ipv4,
|
ipv4=hostname.last_ipv4,
|
||||||
ipv6=hostname.last_ipv6
|
ipv6=hostname.last_ipv6,
|
||||||
|
expiry_ttl=hostname.expiry_ttl
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
changed_info = ""
|
||||||
|
if ipv4_changed:
|
||||||
|
changed_info += f" ipv4={ipv4}"
|
||||||
|
if ipv6_changed:
|
||||||
|
changed_info += f" ipv6={ipv6}"
|
||||||
|
if expiry_ttl_changed:
|
||||||
|
changed_info += f" expiry_ttl={hostname.expiry_ttl}"
|
||||||
logging.info(
|
logging.info(
|
||||||
f"Updated: client={client_ip} hostname={hostname.hostname} "
|
f"Updated: client={client_ip} hostname={hostname.hostname} "
|
||||||
f"zone={hostname.zone}{changed_addrs} notify_change={str(notify_change).lower()}"
|
f"zone={hostname.zone}{changed_info} notify_change={str(notify_change).lower()}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if notify_change:
|
if notify_change:
|
||||||
@@ -585,7 +662,8 @@ class DDNSRequestHandler(BaseHTTPRequestHandler):
|
|||||||
200,
|
200,
|
||||||
STATUS_GOOD,
|
STATUS_GOOD,
|
||||||
ipv4=hostname.last_ipv4,
|
ipv4=hostname.last_ipv4,
|
||||||
ipv6=hostname.last_ipv6
|
ipv6=hostname.last_ipv6,
|
||||||
|
expiry_ttl=hostname.expiry_ttl
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user