diff --git a/README.md b/README.md index b7e43c2..cb566b9 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,7 @@ Without TSIG authentication, the DNS server must allow updates based on IP addre ### Endpoints Configure one or more HTTP endpoints. If no endpoints are defined, a default endpoint at `/update` is created with standard parameter names. +Set an empty list of names to disable a parameter. ```toml [[endpoints]] @@ -176,6 +177,7 @@ ipv4 = ["myip", "ipv4", "ip4"] ipv6 = ["myip6", "ipv6", "ip6"] username = ["username", "user"] password = ["password", "pass", "token"] +notify_change = ["notify_change"] [[endpoints]] path = "/nic/update" @@ -185,6 +187,7 @@ ipv4 = ["myip"] ipv6 = ["myip6"] username = ["username"] password = ["password"] +notify_change = [] ``` **Default accepted parameter names** (first match wins): diff --git a/files/config.example.toml b/files/config.example.toml index 410addf..35d3462 100644 --- a/files/config.example.toml +++ b/files/config.example.toml @@ -65,7 +65,10 @@ from_address = "ddns@example.com" # required if email.enabled # ipv6 (IPv6 address): myip6, ipv6, ip6 # username: username, user # password: password, pass, token +# notify_change: notify_change +# # Multiple endpoints can be defined with custom parameter names + # [[endpoints]] # path = "/update" # [endpoints.params] @@ -74,6 +77,7 @@ from_address = "ddns@example.com" # required if email.enabled # ipv6 = ["myip6", "ipv6", "ip6"] # username = ["username", "user"] # password = ["password", "pass", "token"] +# notify_change = ["notify_change"] # [[endpoints]] # path = "/nic/update" @@ -83,3 +87,4 @@ from_address = "ddns@example.com" # required if email.enabled # ipv6 = ["myip6"] # username = ["username"] # password = ["password"] +# notify_change = [] diff --git a/src/ddns_service/cli.py b/src/ddns_service/cli.py index fd277fc..957353b 100644 --- a/src/ddns_service/cli.py +++ b/src/ddns_service/cli.py @@ -149,7 +149,7 @@ def cmd_hostname_list(args, app): return 0 print( - f"{'Hostname':<35} {'User':<15} {'Zone':<20} " + f"\n{'Hostname':<35} {'User':<15} {'Zone':<20} " f"{'DNS-TTL':<8} {'Exp-TTL':<8} {'Last-Update IPv4':<25} {'Last-Update IPv6'}" ) print("-" * 140) diff --git a/src/ddns_service/config.py b/src/ddns_service/config.py index 2c96207..7f23ed6 100644 --- a/src/ddns_service/config.py +++ b/src/ddns_service/config.py @@ -21,6 +21,7 @@ DEFAULT_ENDPOINT_PARAMS = { "ipv6": ["myip6", "ipv6", "ip6"], "username": ["username", "user"], "password": ["password", "pass", "token"], + "notify_change": ["notify_change"], } VALID_PARAM_KEYS = frozenset(DEFAULT_ENDPOINT_PARAMS.keys()) diff --git a/src/ddns_service/email.py b/src/ddns_service/email.py index 58bbc92..21d62d9 100644 --- a/src/ddns_service/email.py +++ b/src/ddns_service/email.py @@ -94,7 +94,7 @@ class EmailService: logging.error(f"Email send failed: to={to} error={e}") return False - def send_changed_notification( + def send_change_notification( self, email, hostname, @@ -135,8 +135,8 @@ class EmailService: "expiry_ttl": hostname.expiry_ttl, } - subject = f"DDNS hostname expired: {fqdn}" - body = self.expiry_template.render(**template_variables) + subject = f"DDNS hostname changed: {fqdn}" + body = self.change_template.render(**template_variables) return self.send(email, subject, body) diff --git a/src/ddns_service/server.py b/src/ddns_service/server.py index 50e1f51..ecb0966 100644 --- a/src/ddns_service/server.py +++ b/src/ddns_service/server.py @@ -317,7 +317,8 @@ class DDNSRequestHandler(BaseHTTPRequestHandler): now = utc_now() - changed = False + ipv4_changed = False + ipv6_changed = False if ipv4: hostname.last_ipv4_update = now if ipv4 != hostname.last_ipv4: @@ -330,7 +331,7 @@ class DDNSRequestHandler(BaseHTTPRequestHandler): hostname.dns_ttl ) hostname.last_ipv4 = ipv4 - changed = True + ipv4_changed = True except Exception as e: hostname.save() logging.error( @@ -351,7 +352,7 @@ class DDNSRequestHandler(BaseHTTPRequestHandler): hostname.dns_ttl ) hostname.last_ipv6 = ipv6 - changed = True + ipv6_changed = True except Exception as e: hostname.save() logging.error( @@ -364,15 +365,15 @@ class DDNSRequestHandler(BaseHTTPRequestHandler): hostname.save() changed_addrs = "" - if ipv4: - changed_addrs += f"ipv4={ipv4}" - if ipv6: + if ipv4_changed: + changed_addrs += f" ipv4={ipv4}" + if ipv6_changed: changed_addrs += f" ipv6={ipv6}" - if not changed: + if not ipv4_changed and not ipv6_changed: logging.info( f"No change: client={client_ip} hostname={hostname.hostname} " - f"zone={hostname.zone} {changed_addrs}" + f"zone={hostname.zone}{changed_addrs}" ) return ( 200, "nochg", @@ -381,8 +382,21 @@ class DDNSRequestHandler(BaseHTTPRequestHandler): logging.info( f"Updated: client={client_ip} hostname={hostname.hostname} " - f"zone={hostname.zone} {changed_addrs}" + f"zone={hostname.zone}{changed_addrs}" ) + + notify_change = extract_param(params, endpoint["params"]["notify_change"]) + if notify_change and notify_change.lower() not in ["0", "no", "off"]: + try: + self.app.email_service.send_change_notification( + hostname.user.email, + hostname, + ipv4_changed, + ipv6_changed + ) + except Exception as e: + logging.error(f"Sending change notification error: {e}") + return ( 200, "good", {"ipv4": hostname.last_ipv4, "ipv6": hostname.last_ipv6}