#!/bin/sh
#
# cloudflare - nginx-ech-publish provider for the Cloudflare DNS API.
#
# Invoked by nginx-ech-publish with the contract exported in the environment:
#   ECH_B64                  the ECHConfigList to advertise (public part only)
#   ECH_ZONE_ID              Cloudflare zone id
#   ECH_RECORD_NAME          FQDN of the HTTPS record
#   ECH_ALPN                 alpn= SvcParam value, e.g. h3,h2
#   ECH_TTL                  record TTL in seconds
#   ECH_PUBLISH_CREDENTIALS  ini file with dns_cloudflare_email/_api_key
#
# Credentials are shared with certbot's dns-cloudflare plugin
# (/root/.cloudflare.ini by default) rather than copied into a second file.
#
# EL7 ships curl 7.29, which has neither --fail-with-body nor a way to keep
# the body on a 4xx. So curl's exit status is never the verdict: every
# response is checked for the API's own "success" flag. In particular a
# failed record lookup must never be read as "no record exists" - that would
# create a duplicate HTTPS record on every rotation.

set -eu

PROG="nginx-ech-publish"

die() {
    echo "$PROG: $*" >&2
    exit 1
}

[ -n "${ECH_B64:-}" ]         || die "cloudflare: ECH_B64 is not set (run via nginx-ech-publish)"
[ -n "${ECH_ZONE_ID:-}" ]     || die "cloudflare: ECH_ZONE_ID is not set"
[ -n "${ECH_RECORD_NAME:-}" ] || die "cloudflare: ECH_RECORD_NAME is not set"
[ -n "${ECH_ALPN:-}" ]        || die "cloudflare: ECH_ALPN is not set"
[ -n "${ECH_TTL:-}" ]         || die "cloudflare: ECH_TTL is not set"

CF_INI=${ECH_PUBLISH_CREDENTIALS:-/root/.cloudflare.ini}
[ -r "$CF_INI" ] || die "cloudflare: $CF_INI is not readable"

CF_EMAIL=$(sed -n 's/^[[:space:]]*dns_cloudflare_email[[:space:]]*=[[:space:]]*//p' "$CF_INI" | head -n1)
CF_KEY=$(sed -n 's/^[[:space:]]*dns_cloudflare_api_key[[:space:]]*=[[:space:]]*//p' "$CF_INI" | head -n1)
[ -n "$CF_EMAIL" ] && [ -n "$CF_KEY" ] || die "cloudflare: no dns_cloudflare_email/dns_cloudflare_api_key pair in $CF_INI"

api() {
    curl -sS --max-time 30 \
        -H "X-Auth-Email: $CF_EMAIL" -H "X-Auth-Key: $CF_KEY" \
        -H "Content-Type: application/json" "$@"
}

# ok - true when the response on stdin carries Cloudflare's "success" flag.
ok() {
    grep -q '"success"[[:space:]]*:[[:space:]]*true'
}

BASE="https://api.cloudflare.com/client/v4/zones/$ECH_ZONE_ID/dns_records"

existing=$(api "$BASE?type=HTTPS&name=$ECH_RECORD_NAME")
printf '%s\n' "$existing" | ok \
    || die "cloudflare: could not list HTTPS records for $ECH_RECORD_NAME: $existing"

# The record value as it appears escaped inside the JSON response.
json_value="alpn=\\\"$ECH_ALPN\\\" ech=\\\"$ECH_B64\\\""

if printf '%s\n' "$existing" | grep -qF "$json_value"; then
    echo "$PROG: $ECH_RECORD_NAME already advertises the current key; nothing to do"
    exit 0
fi

payload=$(printf '{"type":"HTTPS","name":"%s","ttl":%s,"data":{"priority":1,"target":".","value":"alpn=\\"%s\\" ech=\\"%s\\""},"comment":"Managed by nginx-ech-publish; ech= rotated by nginx-ech-rotate.timer. Keep DNS-only."}' \
    "$ECH_RECORD_NAME" "$ECH_TTL" "$ECH_ALPN" "$ECH_B64")

if printf '%s\n' "$existing" | grep -q '"result"[[:space:]]*:[[:space:]]*\[[[:space:]]*\]'; then
    # The lookup succeeded and genuinely found nothing: first publication.
    out=$(api -X POST --data "$payload" "$BASE")
else
    # A record exists; update it in place. Record ids are 32-hex. The filter
    # (type=HTTPS&name=...) returns at most a handful of records; take the
    # first. If no id can be extracted, fail hard rather than POST - creating
    # a sibling record next to one we failed to parse is the one outcome this
    # provider must never produce.
    record_id=$(printf '%s\n' "$existing" \
        | grep -o '"id"[[:space:]]*:[[:space:]]*"[0-9a-f]\{32\}"' \
        | head -n1 | grep -o '[0-9a-f]\{32\}')
    [ -n "$record_id" ] || die "cloudflare: HTTPS record exists but its id could not be parsed: $existing"
    out=$(api -X PUT --data "$payload" "$BASE/$record_id")
fi

printf '%s\n' "$out" | ok \
    || die "cloudflare: API rejected the update for $ECH_RECORD_NAME: $out"

echo "$PROG: published $(printf '%.24s' "$ECH_B64")... in the HTTPS record for $ECH_RECORD_NAME"
