Compare commits

...
6 Commits
Author SHA1 Message Date
neil c9e9e24e24 _json_encode: fix empty output on Solaris, newline loss in _hex_dump fallback
_hex_dump strips the trailing newline, and Oracle Solaris sed drops an
unterminated last line, so the sed step in _json_encode produced nothing
and every caller got an empty string on Solaris. Capture the hex first and
re-emit it with printf "%s\n".

The _ascii_hex fallback of _hex_dump (used when od/hexdump/xxd are not in
PATH, e.g. Tribblix) captured stdin with $(cat), losing trailing newlines,
and ran cut -c per line, so multi-line input was mis-encoded. Process the
input line by line instead.

Both surfaced by le_test_json_encode (#7212).
2026-10-21 20:22:33 +08:00
neil aef81df5eb dns_gandi_livedns: delete the TXT record when rm removes its last value
rm PUT the remaining values back even when none were left, leaving an
empty TXT rrset behind. Use DELETE (204 on success) in that case; keep
PUT when other values remain, since the rrset can hold values from
other pending validations.

Refs #7347
2026-10-21 18:49:59 +08:00
neil e3004b84a1 dns_gandi_livedns: load saved credentials in rm
Since 329dab9a (3.1.3) the token/key is saved with
_saveaccountconf_mutable, but only _add read it back. rm runs in its
own subshell, so on cron renewal it called the API unauthenticated;
the 401 body has no "code": 401, so _get_root took the first probe as
the zone and the TXT record was never removed.

Fixes #7347
2026-10-21 17:52:45 +08:00
neil 3288c35c2a _json_encode/_json_decode: use printf instead of echo
dash's echo interprets backslash escapes, so the \. MarkdownV2 escapes
built by notify/telegram.sh collapsed to \. in the JSON body and
Telegram rejected the message with "Character '.' is reserved".
bash's echo does not, which is why only dash-based systems were hit.

Fixes #7212
2026-10-21 17:52:08 +08:00
5d07634e0f dns_namecheap: let NAMECHEAP_* env vars override saved credentials (#7344)
Co-authored-by: zorba <zorba-claude@pavlovian.net>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 14:09:31 +08:00
a57c8a45fa Add hp_ews deploy hook for HP printers (#7198)
* Add hp_ews deploy hook for HP printers

Deploys the certificate to an HP printer's Embedded Web Server by building
a PKCS#12 bundle from the key, certificate and CA chain and POSTing it to
/Security/DeviceCertificates/NewCertWithPassword/Upload.

Tested on an HP LaserJet Pro MFP M479fdw.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* hp_ews: upload with _post_file and Basic auth

Replace the hand-rolled curl/wget helper with _post_file, as requested
in review. Authenticate with a Basic Authorization header like the other
deploy hooks, and map HP_EWS_INSECURE onto HTTPS_INSECURE, which stays
inside the hook's subshell. Printers that only offer Digest auth are no
longer supported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* hp_ews: declare and export _H1 separately (SC2155)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Sean Graham <sean.graham@vizit.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-19 15:23:35 +08:00
4 changed files with 250 additions and 7 deletions

No files matched your search

+16 -4
View File
@@ -666,8 +666,15 @@ _hex_dump() {
xxd -ps -c 20 -i | sed "s/ 0x/ /g" | tr -d ",\n" | tr -s " "
else
_debug3 "using _ascii_hex"
str=$(cat)
_ascii_hex "$str"
#line by line: the cut -c in _ascii_hex works per line, and a $(cat)
#capture would drop the trailing newlines (illumos without od in PATH)
while IFS= read -r _hd_line; do
_ascii_hex "$_hd_line"
printf " 0a"
done
if [ -n "$_hd_line" ]; then
_ascii_hex "$_hd_line"
fi
fi
}
@@ -907,7 +914,12 @@ _json_encode() {
_j_str="$(sed 's/"/\\"/g' | sed "s/\r/\\r/g")"
_debug3 "_json_encode"
_debug3 "_j_str" "$_j_str"
echo "$_j_str" | _hex_dump | _lower_case | sed 's/0a/5c 6e/g' | tr -d ' ' | _h2b | tr -d "\r\n"
#printf, not echo: dash's echo interprets backslash escapes and would
#collapse the \\ sequences (e.g. Telegram MarkdownV2 escapes, issue 7212)
_j_hex="$(printf "%s\n" "$_j_str" | _hex_dump | _lower_case)"
#the hex line must end with a newline: Solaris sed drops an unterminated
#last line, which emptied the whole output
printf "%s\n" "$_j_hex" | sed 's/0a/5c 6e/g' | tr -d ' ' | _h2b | tr -d "\r\n"
}
#from: http:\/\/ to http://
@@ -915,7 +927,7 @@ _json_decode() {
_j_str="$(sed 's#\\/#/#g')"
_debug3 "_json_decode"
_debug3 "_j_str" "$_j_str"
echo "$_j_str"
printf "%s\n" "$_j_str"
}
#extract the authorization URLs from an order response on stdin, as a
+207
View File
@@ -0,0 +1,207 @@
#!/usr/bin/env sh
# Here is a script to deploy cert to an HP printer's Embedded Web Server (EWS).
#
# Docs: https://github.com/acmesh-official/acme.sh/wiki/deployhooks#47-deploy-the-certificate-to-an-hp-printer-ews
#
# Tested on an HP Color LaserJet Pro MFP M479fdw, firmware
# CLRWTRXXXN002.2539E.00. It should work on any modern HP printer whose EWS
# exposes the "Install Certificate" flow at
# /Security/DeviceCertificates/NewCertWithPassword/Upload -- that endpoint
# takes a password-protected PKCS#12 bundle, which this hook builds from the
# issued certificate, its key and the CA chain.
#
# ```sh
# export HP_EWS_PASSWORD=mysecret
# export HP_EWS_INSECURE=1 # needed until the printer serves a trusted cert
# acme.sh --deploy -d printer.example.com --deploy-hook hp_ews
# ```
#
# Environment variables:
# HP_EWS_PASSWORD (required) EWS administrator password.
# HP_EWS_HOST (optional) IP or hostname of the printer.
# Default: the certificate's domain.
# HP_EWS_USERNAME (optional) EWS administrator user. Default: admin
# HP_EWS_PFX_PASSWORD (optional) Password for the generated PKCS#12
# bundle. A random one is used per run if
# unset; set it only if you want the
# bundle to be reproducible.
# HP_EWS_INSECURE (optional) Set to a non-empty value to skip TLS
# verification when talking to the
# printer. Needed on the first deploy,
# because the printer still serves its
# self-signed certificate at that point.
# acme.sh's global HTTPS_INSECURE is
# honoured as well.
#
# The hook authenticates with HTTP Basic auth. Printers whose EWS only offers
# Digest auth are not supported.
#
# Requires openssl, to build the PKCS#12 bundle.
#
# returns 0 means success, otherwise error.
######## Public functions #####################
#domain keyfile certfile cafile fullchain pfx
hp_ews_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_cpfx="$6"
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
_debug _cpfx "$_cpfx"
_getdeployconf HP_EWS_HOST
_getdeployconf HP_EWS_USERNAME
_getdeployconf HP_EWS_PASSWORD
_getdeployconf HP_EWS_PFX_PASSWORD
_getdeployconf HP_EWS_INSECURE
HP_EWS_HOST="${HP_EWS_HOST:-$_cdomain}"
HP_EWS_USERNAME="${HP_EWS_USERNAME:-admin}"
_debug HP_EWS_HOST "$HP_EWS_HOST"
_debug HP_EWS_USERNAME "$HP_EWS_USERNAME"
_secure_debug HP_EWS_PASSWORD "$HP_EWS_PASSWORD"
_secure_debug HP_EWS_PFX_PASSWORD "$HP_EWS_PFX_PASSWORD"
_debug HP_EWS_INSECURE "$HP_EWS_INSECURE"
if [ -z "$HP_EWS_PASSWORD" ]; then
_err "HP_EWS_PASSWORD is required. Please set the password of the printer's EWS administrator account."
return 1
fi
_savedeployconf HP_EWS_HOST "$HP_EWS_HOST"
_savedeployconf HP_EWS_USERNAME "$HP_EWS_USERNAME"
_savedeployconf HP_EWS_PASSWORD "$HP_EWS_PASSWORD" "base64"
_savedeployconf HP_EWS_PFX_PASSWORD "$HP_EWS_PFX_PASSWORD" "base64"
_savedeployconf HP_EWS_INSECURE "$HP_EWS_INSECURE"
if ! _exists "${ACME_OPENSSL_BIN:-openssl}"; then
_err "openssl is required by this deploy hook, but was not found."
return 1
fi
# The bundle password only has to survive the single upload below, so a
# fresh random one is used unless the operator asked for a fixed value.
_HP_EWS_PFXPW="$HP_EWS_PFX_PASSWORD"
if [ -z "$_HP_EWS_PFXPW" ]; then
_HP_EWS_PFXPW="$(${ACME_OPENSSL_BIN:-openssl} rand -hex 24)"
if [ -z "$_HP_EWS_PFXPW" ]; then
_err "Could not generate a random password for the PKCS#12 bundle."
return 1
fi
fi
export _HP_EWS_PFXPW
# $6 is only materialised when Le_PFXPassword is set for this domain, and
# it would be built without the CA chain, so build our own bundle instead.
_hp_ews_pfx="$(_mktemp)"
_hp_ews_body="$(_mktemp)"
if [ -z "$_hp_ews_pfx" ] || [ -z "$_hp_ews_body" ]; then
_err "Could not create the temporary files needed for the upload."
_hp_ews_cleanup
return 1
fi
_hp_ews_ret=0
if ! _hp_ews_make_pfx; then
_hp_ews_ret=1
elif ! _hp_ews_upload; then
_hp_ews_ret=1
fi
_hp_ews_cleanup
return "$_hp_ews_ret"
}
#################### Private functions below ##################################
_hp_ews_cleanup() {
[ -n "$_hp_ews_pfx" ] && rm -f "$_hp_ews_pfx"
[ -n "$_hp_ews_body" ] && rm -f "$_hp_ews_body"
unset _HP_EWS_PFXPW
# Do not leak the request header into any hook that runs after this one.
export _H1=""
return 0
}
# Bundle key, certificate and chain into $_hp_ews_pfx, protected by
# $_HP_EWS_PFXPW. The password goes through the environment rather than the
# command line so that it does not show up in ps output.
_hp_ews_make_pfx() {
_info "Building the PKCS#12 bundle"
if ! ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export \
-out "$_hp_ews_pfx" \
-inkey "$_ckey" \
-in "$_ccert" \
-certfile "$_cca" \
-passout env:_HP_EWS_PFXPW; then
_err "Failed to build the PKCS#12 bundle."
return 1
fi
if [ ! -s "$_hp_ews_pfx" ]; then
_err "The PKCS#12 bundle is empty."
return 1
fi
return 0
}
# Build the multipart body in $_hp_ews_body and POST it to the printer.
_hp_ews_upload() {
_hp_ews_url="https://${HP_EWS_HOST}/Security/DeviceCertificates/NewCertWithPassword/Upload"
_hp_ews_boundary="----------------------------acmesh$(${ACME_OPENSSL_BIN:-openssl} rand -hex 16)"
{
printf -- "--%s\r\n" "$_hp_ews_boundary"
printf "Content-Disposition: form-data; name=\"certificate\"; filename=\"%s.pfx\"\r\n" "$_cdomain"
printf "Content-Type: application/x-pkcs12\r\n\r\n"
cat "$_hp_ews_pfx"
printf "\r\n"
printf -- "--%s\r\n" "$_hp_ews_boundary"
printf "Content-Disposition: form-data; name=\"password\"\r\n\r\n%s\r\n" "$_HP_EWS_PFXPW"
printf -- "--%s--\r\n" "$_hp_ews_boundary"
} >"$_hp_ews_body"
_H1="Authorization: Basic $(printf "%s:%s" "$HP_EWS_USERNAME" "$HP_EWS_PASSWORD" | _base64)"
export _H1
if [ "$HP_EWS_INSECURE" ]; then
export HTTPS_INSECURE=1
fi
_info "Uploading the certificate to $_hp_ews_url"
if ! _post_file "$_hp_ews_body" "$_hp_ews_url" "" "POST" "multipart/form-data; boundary=$_hp_ews_boundary" >/dev/null; then
if [ -z "$HTTPS_INSECURE" ]; then
_err "If the printer still serves its self-signed certificate, set HP_EWS_INSECURE=1 and try again."
fi
return 1
fi
# The EWS answers 201 Created on a successful install.
_hp_ews_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
_debug _hp_ews_code "$_hp_ews_code"
if [ "$_hp_ews_code" != "201" ] && [ "$_hp_ews_code" != "200" ]; then
_err "The printer rejected the certificate (HTTP $_hp_ews_code)."
if [ "$_hp_ews_code" = "401" ] || [ "$_hp_ews_code" = "403" ]; then
_err "Check HP_EWS_USERNAME and HP_EWS_PASSWORD. Only HTTP Basic authentication is"
_err "supported; printers that only offer Digest cannot be used with this hook."
fi
return 1
fi
_info "Certificate installed (HTTP $_hp_ews_code). The printer restarts its web server shortly."
return 0
}
+21
View File
@@ -59,6 +59,13 @@ dns_gandi_livedns_rm() {
fulldomain=$1
txtvalue=$2
GANDI_LIVEDNS_KEY="${GANDI_LIVEDNS_KEY:-$(_readaccountconf_mutable GANDI_LIVEDNS_KEY)}"
GANDI_LIVEDNS_TOKEN="${GANDI_LIVEDNS_TOKEN:-$(_readaccountconf_mutable GANDI_LIVEDNS_TOKEN)}"
if [ -z "$GANDI_LIVEDNS_KEY" ] && [ -z "$GANDI_LIVEDNS_TOKEN" ]; then
_err "No Token or API key (deprecated) specified for Gandi LiveDNS."
return 1
fi
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
@@ -80,6 +87,20 @@ dns_gandi_livedns_rm() {
_new_rrset_values=$(echo "$_new_rrset_values" | sed "s/\[ *,/\[/g")
_debug "New rrset_values" "$_new_rrset_values"
# Last value removed: delete the record instead of leaving an empty TXT rrset.
if [ "$(echo "$_new_rrset_values" | tr -d ' ')" = "[]" ]; then
if ! _gandi_livedns_rest DELETE "domains/$_domain/records/$_sub_domain/TXT"; then
return 1
fi
_gandi_livedns_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
if [ "$_gandi_livedns_code" != "204" ]; then
_err "Error deleting record: $response"
return 1
fi
_info "Removing record $(__green "success")"
return 0
fi
_gandi_livedns_rest PUT \
"domains/$_domain/records/$_sub_domain/TXT" \
"{\"rrset_ttl\": 300, \"rrset_values\": $_new_rrset_values}" &&
+6 -3
View File
@@ -185,13 +185,14 @@ _get_root_by_getHosts() {
}
_namecheap_set_publicip() {
NAMECHEAP_SOURCEIP="${NAMECHEAP_SOURCEIP:-$(_readaccountconf_mutable NAMECHEAP_SOURCEIP)}"
if [ -z "$NAMECHEAP_SOURCEIP" ]; then
_err "No Source IP specified for Namecheap API."
_err "Use your public ip address or an url to retrieve it (e.g. https://ifconfig.co/ip) and export it as NAMECHEAP_SOURCEIP"
return 1
else
_saveaccountconf NAMECHEAP_SOURCEIP "$NAMECHEAP_SOURCEIP"
_saveaccountconf_mutable NAMECHEAP_SOURCEIP "$NAMECHEAP_SOURCEIP"
_debug sourceip "$NAMECHEAP_SOURCEIP"
ip=$(echo "$NAMECHEAP_SOURCEIP" | _egrep_o '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')
@@ -252,6 +253,8 @@ _namecheap_parse_host() {
}
_namecheap_check_config() {
NAMECHEAP_API_KEY="${NAMECHEAP_API_KEY:-$(_readaccountconf_mutable NAMECHEAP_API_KEY)}"
NAMECHEAP_USERNAME="${NAMECHEAP_USERNAME:-$(_readaccountconf_mutable NAMECHEAP_USERNAME)}"
if [ -z "$NAMECHEAP_API_KEY" ]; then
_err "No API key specified for Namecheap API."
@@ -265,8 +268,8 @@ _namecheap_check_config() {
return 1
fi
_saveaccountconf NAMECHEAP_API_KEY "$NAMECHEAP_API_KEY"
_saveaccountconf NAMECHEAP_USERNAME "$NAMECHEAP_USERNAME"
_saveaccountconf_mutable NAMECHEAP_API_KEY "$NAMECHEAP_API_KEY"
_saveaccountconf_mutable NAMECHEAP_USERNAME "$NAMECHEAP_USERNAME"
return 0
}
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.