From fe467364cb881bc18478442f0e8976ab6cf4d74d Mon Sep 17 00:00:00 2001 From: Franco Fichtner Date: Mon, 19 Jan 2026 04:17:06 +0100 Subject: [PATCH] dns/ddclient: wrap up new version --- dns/ddclient/Makefile | 2 +- dns/ddclient/pkg-descr | 10 +++- .../scripts/ddclient/lib/account/dnspod_cn.py | 52 +++++++++---------- .../scripts/ddclient/lib/account/hetzner.py | 0 4 files changed, 35 insertions(+), 29 deletions(-) mode change 100644 => 100755 dns/ddclient/src/opnsense/scripts/ddclient/lib/account/dnspod_cn.py mode change 100644 => 100755 dns/ddclient/src/opnsense/scripts/ddclient/lib/account/hetzner.py diff --git a/dns/ddclient/Makefile b/dns/ddclient/Makefile index cd453e19c..f947225d0 100644 --- a/dns/ddclient/Makefile +++ b/dns/ddclient/Makefile @@ -1,5 +1,5 @@ PLUGIN_NAME= ddclient -PLUGIN_VERSION= 1.28 +PLUGIN_VERSION= 1.29 PLUGIN_DEPENDS= ddclient py${PLUGIN_PYTHON}-boto3 PLUGIN_COMMENT= Dynamic DNS client PLUGIN_MAINTAINER= ad@opnsense.org diff --git a/dns/ddclient/pkg-descr b/dns/ddclient/pkg-descr index f5f2d1ace..7ead5a14f 100644 --- a/dns/ddclient/pkg-descr +++ b/dns/ddclient/pkg-descr @@ -1,11 +1,17 @@ +This plugin offers dynamic DNS capabilities using a native backend +or ddclient. The native backend is the default implementation. ddclient is a Perl client used to update dynamic DNS entries for accounts on many dynamic DNS services. -WWW: https://github.com/ddclient/ddclient - Plugin Changelog ================ +1.29 + +* Add native backend support for Hetzner DNS (contributed by Michael J. Arcan) +* Add native backend support for dnspod.cn (contributed by Ansen) +* Add Cloudflare DNS IP check option (contributed by GTechAlpha) + 1.28 * Add native backend support for PowerDNS API (contributed by Oliver Traber) diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/dnspod_cn.py b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/dnspod_cn.py old mode 100644 new mode 100755 index e95ffbe5d..cb4631fcc --- a/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/dnspod_cn.py +++ b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/dnspod_cn.py @@ -61,11 +61,11 @@ class DNSPod_CN(BaseAccount): def _sign(key, msg): """ Generate HMAC-SHA256 signature. - + Args: key (bytes): Signing key msg (str): Message to sign - + Returns: bytes: Signature digest """ @@ -74,21 +74,21 @@ class DNSPod_CN(BaseAccount): def generate_signature(self, action, payload="{}"): """ Generate signature and headers for a Tencent Cloud API request. - + Args: action (str): API action name payload (str or dict, optional): Request payload. Defaults to "{}". - + Returns: tuple: Request headers and canonical request """ # Ensure payload is a string payload = json.dumps(payload) if isinstance(payload, dict) else payload - + # Get current timestamp timestamp = int(time.time()) date = datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%d") - + # Step 1: Create Canonical Request http_request_method = "POST" canonical_uri = "/" @@ -97,7 +97,7 @@ class DNSPod_CN(BaseAccount): canonical_headers = f"content-type:{ct}\nhost:{self._services[self.settings.get('service')]}\nx-tc-action:{action.lower()}\n" signed_headers = "content-type;host;x-tc-action" hashed_request_payload = hashlib.sha256(payload.encode("utf-8")).hexdigest() - + canonical_request = ( f"{http_request_method}\n" f"{canonical_uri}\n" @@ -106,25 +106,25 @@ class DNSPod_CN(BaseAccount): f"{signed_headers}\n" f"{hashed_request_payload}" ) - + # Step 2: Create String to Sign algorithm = "TC3-HMAC-SHA256" credential_scope = f"{date}/{self.service}/tc3_request" hashed_canonical_request = hashlib.sha256(canonical_request.encode("utf-8")).hexdigest() - + string_to_sign = ( f"{algorithm}\n" f"{timestamp}\n" f"{credential_scope}\n" f"{hashed_canonical_request}" ) - + # Step 3: Calculate Signature secret_date = self._sign(("TC3" + self.settings.get('password')).encode("utf-8"), date) secret_service = self._sign(secret_date, self.service) secret_signing = self._sign(secret_service, "tc3_request") signature = hmac.new(secret_signing, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest() - + # Step 4: Create Authorization Header authorization = ( f"{algorithm} " @@ -132,7 +132,7 @@ class DNSPod_CN(BaseAccount): f"SignedHeaders={signed_headers}, " f"Signature={signature}" ) - + # Prepare headers headers = { "Authorization": authorization, @@ -143,53 +143,53 @@ class DNSPod_CN(BaseAccount): "X-TC-Version": "2021-03-23", 'User-Agent': 'OPNsense-dyndns', } - + return headers, payload def send_request(self, action, payload="{}", region="", token=""): """ Send a request to the Tencent Cloud API. - + Args: action (str): API action name payload (str or dict, optional): Request payload. Defaults to "{}". region (str, optional): Optional region parameter token (str, optional): Optional token parameter - + Returns: dict: API response JSON """ # Get headers and prepared payload headers, payload = self.generate_signature(action, payload) - + # Add optional headers if region: headers["X-TC-Region"] = region if token: headers["X-TC-Token"] = token - + try: # Send request using requests library response = requests.post( - url=f"https://{self._services[self.settings.get('service')]}", - headers=headers, + url=f"https://{self._services[self.settings.get('service')]}", + headers=headers, data=payload, timeout=10 ) - + # Raise an exception for bad responses response.raise_for_status() - + # Return JSON response return response - + except requests.RequestException as err: print(f"Request error: {err}") - + # If there's a response, print its content for debugging if hasattr(err, 'response') and err.response is not None: print(f"Response content: {err.response.text}") - + return None @@ -205,7 +205,7 @@ class DNSPod_CN(BaseAccount): subdomains.append('@') else: subdomains.append(_subdomain.replace(f".{self.settings.get('zone')}", '')) - + if len(subdomains) < 1: syslog.syslog( syslog.LOG_ERR, @@ -277,7 +277,7 @@ class DNSPod_CN(BaseAccount): "Account %s failed to set new ip %s [%s]" % (self.description, self.current_address, response.text) ) return False - + record_list = payload['Response']['DetailList'][0].get('RecordList', False) if record_list and len(record_list) == len(subdomains): syslog.syslog( diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/hetzner.py b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/hetzner.py old mode 100644 new mode 100755