diff --git a/.travis.yml b/.travis.yml index 1d54faed..7f7e120c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,9 +26,9 @@ install: _old_path="$PATH"; echo "PATH=$PATH"; export PATH=""; - export OPENSSL_BIN="/usr/local/openssl"; + export ACME_OPENSSL_BIN="/usr/local/openssl"; openssl version 2>&1 || true; - $OPENSSL_BIN version 2>&1 || true; + $ACME_OPENSSL_BIN version 2>&1 || true; export PATH="$_old_path"; fi @@ -44,7 +44,7 @@ script: - cd .. - git clone https://github.com/Neilpang/acmetest.git && cp -r acme.sh acmetest/ && cd acmetest - if [ "$TRAVIS_OS_NAME" = "linux" -a "$NGROK_TOKEN" ]; then sudo TEST_LOCAL="$TEST_LOCAL" NGROK_TOKEN="$NGROK_TOKEN" ./letest.sh ; fi - - if [ "$TRAVIS_OS_NAME" = "osx" -a "$NGROK_TOKEN" ]; then sudo TEST_LOCAL="$TEST_LOCAL" NGROK_TOKEN="$NGROK_TOKEN" OPENSSL_BIN="$OPENSSL_BIN" ./letest.sh ; fi + - if [ "$TRAVIS_OS_NAME" = "osx" -a "$NGROK_TOKEN" ]; then sudo TEST_LOCAL="$TEST_LOCAL" NGROK_TOKEN="$NGROK_TOKEN" ACME_OPENSSL_BIN="$ACME_OPENSSL_BIN" ./letest.sh ; fi matrix: diff --git a/README.md b/README.md index 228c93c4..fa698685 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,18 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa) # [中文说明](https://github.com/Neilpang/acme.sh/wiki/%E8%AF%B4%E6%98%8E) +# Who are using **acme.sh** +- [FreeBSD.org](https://blog.crashed.org/letsencrypt-in-freebsd-org/) +- [ruby-china.org](https://ruby-china.org/topics/31983) +- [Proxmox](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x_and_newer)) +- [pfsense](https://github.com/pfsense/FreeBSD-ports/pull/89) +- [webfaction](https://community.webfaction.com/questions/19988/using-letsencrypt) +- [Loadbalancer.org](https://www.loadbalancer.org/blog/loadbalancer-org-with-lets-encrypt-quick-and-dirty) +- [discourse.org](https://meta.discourse.org/t/setting-up-lets-encrypt/40709) +- [Centminmod](http://centminmod.com/letsencrypt-acmetool-https.html) +- [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297) +- [archlinux](https://aur.archlinux.org/packages/acme.sh-git/) +- [more...](https://github.com/Neilpang/acme.sh/wiki/Blogs-and-tutorials) # Tested OS @@ -149,17 +161,17 @@ You **MUST** use this command to copy the certs to the target files, **DO NOT** **Apache** example: ```bash acme.sh --install-cert -d example.com \ ---certpath /path/to/certfile/in/apache/cert.pem \ ---keypath /path/to/keyfile/in/apache/key.pem \ ---fullchainpath /path/to/fullchain/certfile/apache/fullchain.pem \ +--cert-file /path/to/certfile/in/apache/cert.pem \ +--key-file /path/to/keyfile/in/apache/key.pem \ +--fullchain-file /path/to/fullchain/certfile/apache/fullchain.pem \ --reloadcmd "service apache2 force-reload" ``` **Nginx** example: ```bash acme.sh --install-cert -d example.com \ ---keypath /path/to/keyfile/in/nginx/key.pem \ ---fullchainpath /path/to/fullchain/nginx/cert.pem \ +--key-file /path/to/keyfile/in/nginx/key.pem \ +--fullchain-file /path/to/fullchain/nginx/cert.pem \ --reloadcmd "service nginx force-reload" ``` @@ -293,8 +305,14 @@ You don't have to do anything manually! 1. Linode.com API 1. FreeDNS (https://freedns.afraid.org/) 1. cyon.ch +1. Domain-Offensive/Resellerinterface/Domainrobot API +1. Gandi LiveDNS API +1. Knot DNS API +1. DigitalOcean API (native) +1. ClouDNS.net API 1. Infoblox NIOS API (https://www.infoblox.com/) + **More APIs coming soon...** If your DNS provider is not on the supported list above, you can write your own DNS API script easily. If you do, please consider submitting a [Pull Request](https://github.com/Neilpang/acme.sh/pulls) and contribute it to the project. diff --git a/acme.sh b/acme.sh index e24d4d9a..244f72e3 100755 --- a/acme.sh +++ b/acme.sh @@ -137,16 +137,16 @@ _printargs() { _dlg_versions() { echo "Diagnosis versions: " - echo "openssl:$OPENSSL_BIN" - if _exists "$OPENSSL_BIN"; then - $OPENSSL_BIN version 2>&1 + echo "openssl:$ACME_OPENSSL_BIN" + if _exists "$ACME_OPENSSL_BIN"; then + $ACME_OPENSSL_BIN version 2>&1 else - echo "$OPENSSL_BIN doesn't exists." + echo "$ACME_OPENSSL_BIN doesn't exists." fi echo "apache:" if [ "$_APACHECTL" ] && _exists "$_APACHECTL"; then - _APACHECTL -V 2>&1 + $_APACHECTL -V 2>&1 else echo "apache doesn't exists." fi @@ -299,6 +299,16 @@ _secure_debug3() { fi } +_upper_case() { + # shellcheck disable=SC2018,SC2019 + tr 'a-z' 'A-Z' +} + +_lower_case() { + # shellcheck disable=SC2018,SC2019 + tr 'A-Z' 'a-z' +} + _startswith() { _str="$1" _sub="$2" @@ -780,19 +790,19 @@ _base64() { [ "" ] #urgly if [ "$1" ]; then _debug3 "base64 multiline:'$1'" - $OPENSSL_BIN base64 -e + $ACME_OPENSSL_BIN base64 -e else _debug3 "base64 single line." - $OPENSSL_BIN base64 -e | tr -d '\r\n' + $ACME_OPENSSL_BIN base64 -e | tr -d '\r\n' fi } #Usage: multiline _dbase64() { if [ "$1" ]; then - $OPENSSL_BIN base64 -d -A + $ACME_OPENSSL_BIN base64 -d -A else - $OPENSSL_BIN base64 -d + $ACME_OPENSSL_BIN base64 -d fi } @@ -809,9 +819,9 @@ _digest() { if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ] || [ "$alg" = "md5" ]; then if [ "$outputhex" ]; then - $OPENSSL_BIN dgst -"$alg" -hex | cut -d = -f 2 | tr -d ' ' + $ACME_OPENSSL_BIN dgst -"$alg" -hex | cut -d = -f 2 | tr -d ' ' else - $OPENSSL_BIN dgst -"$alg" -binary | _base64 + $ACME_OPENSSL_BIN dgst -"$alg" -binary | _base64 fi else _err "$alg is not supported yet" @@ -834,9 +844,9 @@ _hmac() { if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ]; then if [ "$outputhex" ]; then - ($OPENSSL_BIN dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" 2>/dev/null || $OPENSSL_BIN dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)") | cut -d = -f 2 | tr -d ' ' + ($ACME_OPENSSL_BIN dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" 2>/dev/null || $ACME_OPENSSL_BIN dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)") | cut -d = -f 2 | tr -d ' ' else - $OPENSSL_BIN dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" -binary 2>/dev/null || $OPENSSL_BIN dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)" -binary + $ACME_OPENSSL_BIN dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" -binary 2>/dev/null || $ACME_OPENSSL_BIN dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)" -binary fi else _err "$alg is not supported yet" @@ -855,7 +865,7 @@ _sign() { return 1 fi - _sign_openssl="$OPENSSL_BIN dgst -sign $keyfile " + _sign_openssl="$ACME_OPENSSL_BIN dgst -sign $keyfile " if [ "$alg" = "sha256" ]; then _sign_openssl="$_sign_openssl -$alg" else @@ -866,7 +876,7 @@ _sign() { if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then $_sign_openssl | _base64 elif grep "BEGIN EC PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then - if ! _signedECText="$($_sign_openssl | $OPENSSL_BIN asn1parse -inform DER)"; then + if ! _signedECText="$($_sign_openssl | $ACME_OPENSSL_BIN asn1parse -inform DER)"; then _err "Sign failed: $_sign_openssl" _err "Key file: $keyfile" _err "Key content:$(wc -l <"$keyfile") lises" @@ -927,12 +937,21 @@ _createkey() { _debug "Use length $length" + if ! touch "$f" >/dev/null 2>&1; then + _f_path="$(dirname "$f")" + _debug _f_path "$_f_path" + if ! mkdir -p "$_f_path"; then + _err "Can not create path: $_f_path" + return 1 + fi + fi + if _isEccKey "$length"; then _debug "Using ec name: $eccname" - $OPENSSL_BIN ecparam -name "$eccname" -genkey 2>/dev/null >"$f" + $ACME_OPENSSL_BIN ecparam -name "$eccname" -genkey 2>/dev/null >"$f" else _debug "Using RSA: $length" - $OPENSSL_BIN genrsa "$length" 2>/dev/null >"$f" + $ACME_OPENSSL_BIN genrsa "$length" 2>/dev/null >"$f" fi if [ "$?" != "0" ]; then @@ -1019,9 +1038,9 @@ _createcsr() { _csr_cn="$(_idn "$domain")" _debug2 _csr_cn "$_csr_cn" if _contains "$(uname -a)" "MINGW"; then - $OPENSSL_BIN req -new -sha256 -key "$csrkey" -subj "//CN=$_csr_cn" -config "$csrconf" -out "$csr" + $ACME_OPENSSL_BIN req -new -sha256 -key "$csrkey" -subj "//CN=$_csr_cn" -config "$csrconf" -out "$csr" else - $OPENSSL_BIN req -new -sha256 -key "$csrkey" -subj "/CN=$_csr_cn" -config "$csrconf" -out "$csr" + $ACME_OPENSSL_BIN req -new -sha256 -key "$csrkey" -subj "/CN=$_csr_cn" -config "$csrconf" -out "$csr" fi } @@ -1033,7 +1052,7 @@ _signcsr() { cert="$4" _debug "_signcsr" - _msg="$($OPENSSL_BIN x509 -req -days 365 -in "$csr" -signkey "$key" -extensions v3_req -extfile "$conf" -out "$cert" 2>&1)" + _msg="$($ACME_OPENSSL_BIN x509 -req -days 365 -in "$csr" -signkey "$key" -extensions v3_req -extfile "$conf" -out "$cert" 2>&1)" _ret="$?" _debug "$_msg" return $_ret @@ -1046,7 +1065,7 @@ _readSubjectFromCSR() { _usage "_readSubjectFromCSR mycsr.csr" return 1 fi - $OPENSSL_BIN req -noout -in "$_csrfile" -subject | _egrep_o "CN *=.*" | cut -d = -f 2 | cut -d / -f 1 | tr -d '\n' + $ACME_OPENSSL_BIN req -noout -in "$_csrfile" -subject | _egrep_o "CN *=.*" | cut -d = -f 2 | cut -d / -f 1 | tr -d '\n' } #_csrfile @@ -1061,7 +1080,7 @@ _readSubjectAltNamesFromCSR() { _csrsubj="$(_readSubjectFromCSR "$_csrfile")" _debug _csrsubj "$_csrsubj" - _dnsAltnames="$($OPENSSL_BIN req -noout -text -in "$_csrfile" | grep "^ *DNS:.*" | tr -d ' \n')" + _dnsAltnames="$($ACME_OPENSSL_BIN req -noout -text -in "$_csrfile" | grep "^ *DNS:.*" | tr -d ' \n')" _debug _dnsAltnames "$_dnsAltnames" if _contains "$_dnsAltnames," "DNS:$_csrsubj,"; then @@ -1082,7 +1101,7 @@ _readKeyLengthFromCSR() { return 1 fi - _outcsr="$($OPENSSL_BIN req -noout -text -in "$_csrfile")" + _outcsr="$($ACME_OPENSSL_BIN req -noout -text -in "$_csrfile")" if _contains "$_outcsr" "Public Key Algorithm: id-ecPublicKey"; then _debug "ECC CSR" echo "$_outcsr" | _egrep_o "^ *ASN1 OID:.*" | cut -d ':' -f 2 | tr -d ' ' @@ -1112,8 +1131,12 @@ _ss() { elif netstat -help 2>&1 | grep -- '-P protocol' >/dev/null; then #for solaris netstat -an -P tcp | grep "\.$_port " | grep "LISTEN" - else + elif netstat -help 2>&1 | grep "\-p" >/dev/null; then + #for full linux netstat -ntpl | grep ":$_port " + else + #for busybox (embedded linux; no pid support) + netstat -ntl 2>/dev/null | grep ":$_port " fi fi return 0 @@ -1136,9 +1159,9 @@ toPkcs() { _initpath "$domain" "$_isEcc" if [ "$pfxPassword" ]; then - $OPENSSL_BIN pkcs12 -export -out "$CERT_PFX_PATH" -inkey "$CERT_KEY_PATH" -in "$CERT_PATH" -certfile "$CA_CERT_PATH" -password "pass:$pfxPassword" + $ACME_OPENSSL_BIN pkcs12 -export -out "$CERT_PFX_PATH" -inkey "$CERT_KEY_PATH" -in "$CERT_PATH" -certfile "$CA_CERT_PATH" -password "pass:$pfxPassword" else - $OPENSSL_BIN pkcs12 -export -out "$CERT_PFX_PATH" -inkey "$CERT_KEY_PATH" -in "$CERT_PATH" -certfile "$CA_CERT_PATH" + $ACME_OPENSSL_BIN pkcs12 -export -out "$CERT_PFX_PATH" -inkey "$CERT_KEY_PATH" -in "$CERT_PATH" -certfile "$CA_CERT_PATH" fi if [ "$?" = "0" ]; then @@ -1147,6 +1170,27 @@ toPkcs() { } +#domain [isEcc] +toPkcs8() { + domain="$1" + + if [ -z "$domain" ]; then + _usage "Usage: $PROJECT_ENTRY --toPkcs8 -d domain [--ecc]" + return 1 + fi + + _isEcc="$2" + + _initpath "$domain" "$_isEcc" + + $ACME_OPENSSL_BIN pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in "$CERT_KEY_PATH" -out "$CERT_PKCS8_PATH" + + if [ "$?" = "0" ]; then + _info "Success, $CERT_PKCS8_PATH" + fi + +} + #[2048] createAccountKey() { _info "Creating account key" @@ -1249,12 +1293,12 @@ _url_replace() { } _time2str() { - #BSD + #Linux if date -u -d@"$1" 2>/dev/null; then return fi - #Linux + #BSD if date -u -r "$1" 2>/dev/null; then return fi @@ -1300,7 +1344,7 @@ _calcjwk() { if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then _debug "RSA key" - pub_exp=$($OPENSSL_BIN rsa -in "$keyfile" -noout -text | grep "^publicExponent:" | cut -d '(' -f 2 | cut -d 'x' -f 2 | cut -d ')' -f 1) + pub_exp=$($ACME_OPENSSL_BIN rsa -in "$keyfile" -noout -text | grep "^publicExponent:" | cut -d '(' -f 2 | cut -d 'x' -f 2 | cut -d ')' -f 1) if [ "${#pub_exp}" = "5" ]; then pub_exp=0$pub_exp fi @@ -1309,7 +1353,7 @@ _calcjwk() { e=$(echo "$pub_exp" | _h2b | _base64) _debug3 e "$e" - modulus=$($OPENSSL_BIN rsa -in "$keyfile" -modulus -noout | cut -d '=' -f 2) + modulus=$($ACME_OPENSSL_BIN rsa -in "$keyfile" -modulus -noout | cut -d '=' -f 2) _debug3 modulus "$modulus" n="$(printf "%s" "$modulus" | _h2b | _base64 | _url_replace)" _debug3 n "$n" @@ -1322,12 +1366,12 @@ _calcjwk() { JWK_HEADERPLACE_PART2='", "alg": "RS256", "jwk": '$jwk'}' elif grep "BEGIN EC PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then _debug "EC key" - crv="$($OPENSSL_BIN ec -in "$keyfile" -noout -text 2>/dev/null | grep "^NIST CURVE:" | cut -d ":" -f 2 | tr -d " \r\n")" + crv="$($ACME_OPENSSL_BIN ec -in "$keyfile" -noout -text 2>/dev/null | grep "^NIST CURVE:" | cut -d ":" -f 2 | tr -d " \r\n")" _debug3 crv "$crv" if [ -z "$crv" ]; then _debug "Let's try ASN1 OID" - crv_oid="$($OPENSSL_BIN ec -in "$keyfile" -noout -text 2>/dev/null | grep "^ASN1 OID:" | cut -d ":" -f 2 | tr -d " \r\n")" + crv_oid="$($ACME_OPENSSL_BIN ec -in "$keyfile" -noout -text 2>/dev/null | grep "^ASN1 OID:" | cut -d ":" -f 2 | tr -d " \r\n")" _debug3 crv_oid "$crv_oid" case "${crv_oid}" in "prime256v1") @@ -1347,15 +1391,15 @@ _calcjwk() { _debug3 crv "$crv" fi - pubi="$($OPENSSL_BIN ec -in "$keyfile" -noout -text 2>/dev/null | grep -n pub: | cut -d : -f 1)" + pubi="$($ACME_OPENSSL_BIN ec -in "$keyfile" -noout -text 2>/dev/null | grep -n pub: | cut -d : -f 1)" pubi=$(_math "$pubi" + 1) _debug3 pubi "$pubi" - pubj="$($OPENSSL_BIN ec -in "$keyfile" -noout -text 2>/dev/null | grep -n "ASN1 OID:" | cut -d : -f 1)" + pubj="$($ACME_OPENSSL_BIN ec -in "$keyfile" -noout -text 2>/dev/null | grep -n "ASN1 OID:" | cut -d : -f 1)" pubj=$(_math "$pubj" - 1) _debug3 pubj "$pubj" - pubtext="$($OPENSSL_BIN ec -in "$keyfile" -noout -text 2>/dev/null | sed -n "$pubi,${pubj}p" | tr -d " \n\r")" + pubtext="$($ACME_OPENSSL_BIN ec -in "$keyfile" -noout -text 2>/dev/null | sed -n "$pubi,${pubj}p" | tr -d " \n\r")" _debug3 pubtext "$pubtext" xlen="$(printf "%s" "$pubtext" | tr -d ':' | wc -c)" @@ -1439,7 +1483,9 @@ _inithttp() { _ACME_CURL="$_ACME_CURL --trace-ascii $_CURL_DUMP " fi - if [ "$CA_BUNDLE" ]; then + if [ "$CA_PATH" ]; then + _ACME_CURL="$_ACME_CURL --capath $CA_PATH " + elif [ "$CA_BUNDLE" ]; then _ACME_CURL="$_ACME_CURL --cacert $CA_BUNDLE " fi @@ -1450,11 +1496,18 @@ _inithttp() { if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then _ACME_WGET="$_ACME_WGET -d " fi - if [ "$CA_BUNDLE" ]; then - _ACME_WGET="$_ACME_WGET --ca-certificate $CA_BUNDLE " + if [ "$CA_PATH" ]; then + _ACME_WGET="$_ACME_WGET --ca-directory=$CA_PATH " + elif [ "$CA_BUNDLE" ]; then + _ACME_WGET="$_ACME_WGET --ca-certificate=$CA_BUNDLE " fi fi + #from wget 1.14: do not skip body on 404 error + if [ "$_ACME_WGET" ] && _contains "$($_ACME_WGET --help 2>&1)" "--content-on-error"; then + _ACME_WGET="$_ACME_WGET --content-on-error " + fi + __HTTP_INITIALIZED=1 } @@ -1475,7 +1528,7 @@ _post() { _inithttp - if [ "$_ACME_CURL" ]; then + if [ "$_ACME_CURL" ] && [ "${ACME_USE_WGET:-0}" = "0" ]; then _CURL="$_ACME_CURL" if [ "$HTTPS_INSECURE" ]; then _CURL="$_CURL --insecure " @@ -1516,7 +1569,7 @@ _post() { _ret="$?" if [ "$_ret" = "8" ]; then _ret=0 - _debug "wget returns 8, the server returns a 'Bad request' respons, lets process the response later." + _debug "wget returns 8, the server returns a 'Bad request' response, lets process the response later." fi if [ "$_ret" != "0" ]; then _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $_ret" @@ -1542,7 +1595,7 @@ _get() { _inithttp - if [ "$_ACME_CURL" ]; then + if [ "$_ACME_CURL" ] && [ "${ACME_USE_WGET:-0}" = "0" ]; then _CURL="$_ACME_CURL" if [ "$HTTPS_INSECURE" ]; then _CURL="$_CURL --insecure " @@ -1579,9 +1632,9 @@ _get() { $_WGET --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" -O - "$url" fi ret=$? - if [ "$_ret" = "8" ]; then - _ret=0 - _debug "wget returns 8, the server returns a 'Bad request' respons, lets process the response later." + if [ "$ret" = "8" ]; then + ret=0 + _debug "wget returns 8, the server returns a 'Bad request' response, lets process the response later." fi if [ "$ret" != "0" ]; then _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $ret" @@ -1964,7 +2017,7 @@ _starttlsserver() { return 1 fi - __S_OPENSSL="$OPENSSL_BIN s_server -cert $TLS_CERT -key $TLS_KEY " + __S_OPENSSL="$ACME_OPENSSL_BIN s_server -cert $TLS_CERT -key $TLS_KEY " if [ "$opaddr" ]; then __S_OPENSSL="$__S_OPENSSL -accept $opaddr:$port" else @@ -2143,8 +2196,8 @@ _initpath() { CERT_HOME="$_DEFAULT_CERT_HOME" fi - if [ -z "$OPENSSL_BIN" ]; then - OPENSSL_BIN="$DEFAULT_OPENSSL_BIN" + if [ -z "$ACME_OPENSSL_BIN" ] || [ ! -f "$ACME_OPENSSL_BIN" ] || [ ! -x "$ACME_OPENSSL_BIN" ]; then + ACME_OPENSSL_BIN="$DEFAULT_OPENSSL_BIN" fi if [ -z "$1" ]; then @@ -2200,6 +2253,9 @@ _initpath() { if [ -z "$CERT_PFX_PATH" ]; then CERT_PFX_PATH="$DOMAIN_PATH/$domain.pfx" fi + if [ -z "$CERT_PKCS8_PATH" ]; then + CERT_PKCS8_PATH="$DOMAIN_PATH/$domain.pkcs8" + fi if [ -z "$TLS_CONF" ]; then TLS_CONF="$DOMAIN_PATH/tls.valdation.conf" @@ -2424,7 +2480,7 @@ _setNginx() { fi _debug "Start detect nginx conf for $_d from:$_start_f" if ! _checkConf "$_d" "$_start_f"; then - "Can not find conf file for domain $d" + _err "Can not find conf file for domain $d" return 1 fi _info "Found conf file: $FOUND_REAL_NGINX_CONF" @@ -2508,7 +2564,7 @@ _checkConf() { if [ ! -f "$2" ] && ! echo "$2" | grep '*$' >/dev/null && echo "$2" | grep '*' >/dev/null; then _debug "wildcard" for _w_f in $2; do - if _checkConf "$1" "$_w_f"; then + if [ -f "$_w_f"] && _checkConf "$1" "$_w_f"; then return 0 fi done @@ -2521,9 +2577,9 @@ _checkConf() { FOUND_REAL_NGINX_CONF="$2" return 0 fi - if grep "^ *include *.*;" "$2" >/dev/null; then + if cat "$2" | tr "\t" " " | grep "^ *include *.*;" >/dev/null; then _debug "Try include files" - for included in $(grep "^ *include *.*;" "$2" | sed "s/include //" | tr -d " ;"); do + for included in $(cat "$2" | tr "\t" " " | grep "^ *include *.*;" | sed "s/include //" | tr -d " ;"); do _debug "check included $included" if _checkConf "$1" "$included"; then return 0 @@ -2795,6 +2851,7 @@ _on_before_issue() { _on_issue_err() { _chk_post_hook="$1" + _chk_vlist="$2" _debug _on_issue_err if [ "$LOG_FILE" ]; then _err "Please check log file for more details: $LOG_FILE" @@ -2803,10 +2860,6 @@ _on_issue_err() { _err "See: $_DEBUG_WIKI" fi - if [ "$DEBUG" ] && [ "$DEBUG" -gt "0" ]; then - _debug "$(_dlg_versions)" - fi - #run the post hook if [ "$_chk_post_hook" ]; then _info "Run post hook:'$_chk_post_hook'" @@ -2817,6 +2870,28 @@ _on_issue_err() { return 1 fi fi + + #trigger the validation to flush the pending authz + if [ "$_chk_vlist" ]; then + ( + _debug2 "_chk_vlist" "$_chk_vlist" + _debug2 "start to deactivate authz" + ventries=$(echo "$_chk_vlist" | tr "$dvsep" ' ') + for ventry in $ventries; do + d=$(echo "$ventry" | cut -d "$sep" -f 1) + keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2) + uri=$(echo "$ventry" | cut -d "$sep" -f 3) + vtype=$(echo "$ventry" | cut -d "$sep" -f 4) + _currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5) + __trigger_validaton "$uri" "$keyauthorization" + done + ) + fi + + if [ "$DEBUG" ] && [ "$DEBUG" -gt "0" ]; then + _debug "$(_dlg_versions)" + fi + } _on_issue_success() { @@ -3029,6 +3104,16 @@ __get_domain_new_authz() { } +#uri keyAuthorization +__trigger_validaton() { + _debug2 "tigger domain validation." + _t_url="$1" + _debug2 _t_url "$_t_url" + _t_key_authz="$2" + _debug2 _t_key_authz "$_t_key_authz" + _send_signed_request "$_t_url" "{\"resource\": \"challenge\", \"keyAuthorization\": \"$_t_key_authz\"}" +} + #webroot, domain domainlist keylength issue() { if [ -z "$2" ]; then @@ -3342,7 +3427,7 @@ issue() { _startserver "$keyauthorization" "$_ncaddr" & if [ "$?" != "0" ]; then _clearup - _on_issue_err "$_post_hook" + _on_issue_err "$_post_hook" "$vlist" return 1 fi serverproc="$!" @@ -3358,7 +3443,7 @@ issue() { BACKUP_NGINX_CONF="" if ! _setNginx "$d" "$_currentRoot" "$thumbprint"; then _clearup - _on_issue_err "$_post_hook" + _on_issue_err "$_post_hook" "$vlist" return 1 fi @@ -3393,14 +3478,17 @@ issue() { _err "$d:Can not write token to file : $wellknown_path/$token" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup - _on_issue_err "$_post_hook" + _on_issue_err "$_post_hook" "$vlist" return 1 fi if [ ! "$usingApache" ]; then if webroot_owner=$(_stat "$_currentRoot"); then _debug "Changing owner/group of .well-known to $webroot_owner" - chown -R "$webroot_owner" "$_currentRoot/.well-known" + if ! _exec "chown -R \"$webroot_owner\" \"$_currentRoot/.well-known\""; then + _debug "$(cat "$_EXEC_TEMP_ERR")" + _exec_err >/dev/null 2>&1 + fi else _debug "not chaning owner/group of webroot" fi @@ -3438,16 +3526,16 @@ issue() { _err "Start tls server error." _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup - _on_issue_err "$_post_hook" + _on_issue_err "$_post_hook" "$vlist" return 1 fi fi - if ! _send_signed_request "$uri" "{\"resource\": \"challenge\", \"keyAuthorization\": \"$keyauthorization\"}"; then + if ! __trigger_validaton "$uri" "$keyauthorization"; then _err "$d:Can not get challenge: $response" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup - _on_issue_err "$_post_hook" + _on_issue_err "$_post_hook" "$vlist" return 1 fi @@ -3455,7 +3543,7 @@ issue() { _err "$d:Challenge error: $response" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup - _on_issue_err "$_post_hook" + _on_issue_err "$_post_hook" "$vlist" return 1 fi @@ -3470,7 +3558,7 @@ issue() { _err "$d:Timeout" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup - _on_issue_err + _on_issue_err "$_post_hook" "$vlist" return 1 fi @@ -3482,7 +3570,7 @@ issue() { _err "$d:Verify error:$response" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup - _on_issue_err "$_post_hook" + _on_issue_err "$_post_hook" "$vlist" return 1 fi _debug2 original "$response" @@ -3517,7 +3605,7 @@ issue() { fi _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup - _on_issue_err "$_post_hook" + _on_issue_err "$_post_hook" "$vlist" return 1 fi @@ -3527,7 +3615,7 @@ issue() { _err "$d:Verify error:$response" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup - _on_issue_err "$_post_hook" + _on_issue_err "$_post_hook" "$vlist" return 1 fi @@ -3623,6 +3711,12 @@ issue() { _clearaccountconf "CA_BUNDLE" fi + if [ "$CA_PATH" ]; then + _saveaccountconf CA_PATH "$CA_PATH" + else + _clearaccountconf "CA_PATH" + fi + if [ "$HTTPS_INSECURE" ]; then _saveaccountconf HTTPS_INSECURE "$HTTPS_INSECURE" else @@ -3941,7 +4035,7 @@ deploy() { installcert() { _main_domain="$1" if [ -z "$_main_domain" ]; then - _usage "Usage: $PROJECT_ENTRY --installcert -d domain.com [--ecc] [--certpath cert-file-path] [--keypath key-file-path] [--capath ca-cert-file-path] [ --reloadCmd reloadCmd] [--fullchainpath fullchain-path]" + _usage "Usage: $PROJECT_ENTRY --installcert -d domain.com [--ecc] [--cert-file cert-file-path] [--key-file key-file-path] [--ca-file ca-cert-file-path] [ --reloadCmd reloadCmd] [--fullchain-file fullchain-path]" return 1 fi @@ -4368,8 +4462,8 @@ _precheck() { fi fi - if ! _exists "$OPENSSL_BIN"; then - _err "Please install openssl first." + if ! _exists "$ACME_OPENSSL_BIN"; then + _err "Please install openssl first. ACME_OPENSSL_BIN=$ACME_OPENSSL_BIN" _err "We need openssl to generate keys." return 1 fi @@ -4551,7 +4645,7 @@ install() { #Modify shebang if _exists bash; then _info "Good, bash is found, so change the shebang to use bash as preferred." - _shebang='#!/usr/bin/env bash' + _shebang='#!'"$(env bash -c "command -v bash")" _setShebang "$LE_WORKING_DIR/$PROJECT_ENTRY" "$_shebang" for subf in $_SUB_FOLDERS; do if [ -d "$LE_WORKING_DIR/$subf" ]; then @@ -4661,6 +4755,7 @@ Commands: --uninstall-cronjob Uninstall the cron job. The 'uninstall' command can do this automatically. --cron Run cron job to renew all the certs. --toPkcs Export the certificate and key to a pfx file. + --toPkcs8 Convert to pkcs8 format. --update-account Update account info. --register-account Register account key. --create-account-key Create an account private key, professional use. @@ -4690,10 +4785,10 @@ Parameters: These parameters are to install the cert to nginx/apache or anyother server after issue/renew a cert: - --certpath /path/to/real/cert/file After issue/renew, the cert will be copied to this path. - --keypath /path/to/real/key/file After issue/renew, the key will be copied to this path. - --capath /path/to/real/ca/file After issue/renew, the intermediate cert will be copied to this path. - --fullchainpath /path/to/fullchain/file After issue/renew, the fullchain cert will be copied to this path. + --cert-file After issue/renew, the cert will be copied to this path. + --key-file After issue/renew, the key will be copied to this path. + --ca-file After issue/renew, the intermediate cert will be copied to this path. + --fullchain-file After issue/renew, the fullchain cert will be copied to this path. --reloadcmd \"service nginx reload\" After issue/renew, it's used to reload the server. @@ -4712,6 +4807,7 @@ Parameters: --stopRenewOnError, -se Only valid for '--renew-all' command. Stop if one cert has error in renewal. --insecure Do not check the server certificate, in some devices, the api server's certificate may not be trusted. --ca-bundle Specifices the path to the CA certificate bundle to verify api server's certificate. + --ca-path Specifies directory containing CA certificates in PEM format, used by wget or curl. --nocron Only valid for '--install' command, which means: do not install the default cron job. In this case, the certs will not be renewed automatically. --ecc Specifies to use the ECC cert. Valid for '--install-cert', '--renew', '--revoke', '--toPkcs' and '--createCSR' --csr Specifies the input csr. @@ -4724,6 +4820,7 @@ Parameters: --listen-v4 Force standalone/tls server to listen at ipv4. --listen-v6 Force standalone/tls server to listen at ipv6. --openssl-bin Specifies a custom openssl bin location. + --use-wget Force to use wget, if you have both curl and wget installed. " } @@ -4791,9 +4888,9 @@ _processAccountConf() { fi if [ "$_openssl_bin" ]; then - _saveaccountconf "OPENSSL_BIN" "$_openssl_bin" - elif [ "$OPENSSL_BIN" ] && [ "$OPENSSL_BIN" != "$DEFAULT_OPENSSL_BIN" ]; then - _saveaccountconf "OPENSSL_BIN" "$OPENSSL_BIN" + _saveaccountconf "ACME_OPENSSL_BIN" "$_openssl_bin" + elif [ "$ACME_OPENSSL_BIN" ] && [ "$ACME_OPENSSL_BIN" != "$DEFAULT_OPENSSL_BIN" ]; then + _saveaccountconf "ACME_OPENSSL_BIN" "$ACME_OPENSSL_BIN" fi if [ "$_auto_upgrade" ]; then @@ -4802,6 +4899,12 @@ _processAccountConf() { _saveaccountconf "AUTO_UPGRADE" "$AUTO_UPGRADE" fi + if [ "$_use_wget" ]; then + _saveaccountconf "ACME_USE_WGET" "$_use_wget" + elif [ "$ACME_USE_WGET" ]; then + _saveaccountconf "ACME_USE_WGET" "$ACME_USE_WGET" + fi + } _process() { @@ -4811,10 +4914,10 @@ _process() { _webroot="" _keylength="" _accountkeylength="" - _certpath="" - _keypath="" - _capath="" - _fullchainpath="" + _cert_file="" + _key_file="" + _ca_file="" + _fullchain_file="" _reloadcmd="" _password="" _accountconf="" @@ -4830,6 +4933,7 @@ _process() { _stopRenewOnError="" #_insecure="" _ca_bundle="" + _ca_path="" _nocron="" _ecc="" _csr="" @@ -4846,6 +4950,7 @@ _process() { _listen_v6="" _openssl_bin="" _syslog="" + _use_wget="" while [ ${#} -gt 0 ]; do case "${1}" in @@ -4908,6 +5013,9 @@ _process() { --toPkcs) _CMD="toPkcs" ;; + --toPkcs8) + _CMD="toPkcs8" + ;; --createAccountKey | --createaccountkey | -cak | --create-account-key) _CMD="createAccountKey" ;; @@ -5051,20 +5159,20 @@ _process() { shift ;; - --certpath) - _certpath="$2" + --cert-file | --certpath) + _cert_file="$2" shift ;; - --keypath) - _keypath="$2" + --key-file | --keypath) + _key_file="$2" shift ;; - --capath) - _capath="$2" + --ca-file | --capath) + _ca_file="$2" shift ;; - --fullchainpath) - _fullchainpath="$2" + --fullchain-file | --fullchainpath) + _fullchain_file="$2" shift ;; --reloadcmd | --reloadCmd) @@ -5140,6 +5248,11 @@ _process() { CA_BUNDLE="$_ca_bundle" shift ;; + --ca-path) + _ca_path="$2" + CA_PATH="$_ca_path" + shift + ;; --nocron) _nocron="1" ;; @@ -5219,7 +5332,12 @@ _process() { ;; --openssl-bin) _openssl_bin="$2" - OPENSSL_BIN="$_openssl_bin" + ACME_OPENSSL_BIN="$_openssl_bin" + shift + ;; + --use-wget) + _use_wget="1" + ACME_USE_WGET="1" ;; *) _err "Unknown parameter : $1" @@ -5276,7 +5394,7 @@ _process() { uninstall) uninstall "$_nocron" ;; upgrade) upgrade ;; issue) - issue "$_webroot" "$_domain" "$_altdomains" "$_keylength" "$_certpath" "$_keypath" "$_capath" "$_reloadcmd" "$_fullchainpath" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" + issue "$_webroot" "$_domain" "$_altdomains" "$_keylength" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" ;; deploy) deploy "$_domain" "$_deploy_hook" "$_ecc" @@ -5288,7 +5406,7 @@ _process() { showcsr "$_csr" "$_domain" ;; installcert) - installcert "$_domain" "$_certpath" "$_keypath" "$_capath" "$_reloadcmd" "$_fullchainpath" "$_ecc" + installcert "$_domain" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_ecc" ;; renew) renew "$_domain" "$_ecc" @@ -5320,6 +5438,9 @@ _process() { toPkcs) toPkcs "$_domain" "$_password" "$_ecc" ;; + toPkcs8) + toPkcs8 "$_domain" "$_ecc" + ;; createAccountKey) createAccountKey "$_accountkeylength" ;; diff --git a/deploy/README.md b/deploy/README.md index fcdf8019..d8c2f57c 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -1,19 +1,21 @@ # Using deploy api +Before you can deploy your cert, you must [issue the cert first](https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert). + Here are the scripts to deploy the certs/key to the server/services. ## 1. Deploy the certs to your cpanel host. (cpanel deploy hook is not finished yet, this is just an example.) -Before you can deploy your cert, you must [issue the cert first](https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert). + Then you can deploy now: ```sh export DEPLOY_CPANEL_USER=myusername export DEPLOY_CPANEL_PASSWORD=PASSWORD -acme.sh --deploy -d example.com --deploy --deploy-hook cpanel +acme.sh --deploy -d example.com --deploy-hook cpanel ``` ## 2. Deploy ssl cert on kong proxy engine based on api. @@ -26,5 +28,52 @@ Before you can deploy your cert, you must [issue the cert first](https://github. (TODO) +## 4. Deploy the cert to local vsftpd server. +```sh +acme.sh --deploy -d ftp.example.com --deploy-hook vsftpd +``` +The default vsftpd conf file is `/etc/vsftpd.conf`, if your vsftpd conf is not in the default location, you can specify one: + +```sh +export DEPLOY_VSFTPD_CONF="/etc/vsftpd.conf" + +acme.sh --deploy -d ftp.example.com --deploy-hook vsftpd +``` + +The default command to restart vsftpd server is `service vsftpd restart`, if it doesn't work, you can specify one: + +```sh +export DEPLOY_VSFTPD_RELOAD="/etc/init.d/vsftpd restart" + +acme.sh --deploy -d ftp.example.com --deploy-hook vsftpd +``` + +## 5. Deploy the cert to local exim4 server. + +```sh +acme.sh --deploy -d ftp.example.com --deploy-hook exim4 +``` + +The default exim4 conf file is `/etc/exim/exim.conf`, if your exim4 conf is not in the default location, you can specify one: + +```sh +export DEPLOY_EXIM4_CONF="/etc/exim4/exim4.conf.template" + +acme.sh --deploy -d ftp.example.com --deploy-hook exim4 +``` + +The default command to restart exim4 server is `service exim4 restart`, if it doesn't work, you can specify one: + +```sh +export DEPLOY_EXIM4_RELOAD="/etc/init.d/exim4 restart" + +acme.sh --deploy -d ftp.example.com --deploy-hook exim4 +``` + +## 6. Deploy the cert to OSX Keychain + +```sh +acme.sh --deploy -d ftp.example.com --deploy-hook keychain +``` diff --git a/deploy/apache.sh b/deploy/apache.sh index b6c1fbc2..7b34bd5f 100644 --- a/deploy/apache.sh +++ b/deploy/apache.sh @@ -1,6 +1,6 @@ #!/usr/bin/env sh -#Here is a script to deploy cert to dovecot server. +#Here is a script to deploy cert to apache server. #returns 0 means success, otherwise error. diff --git a/deploy/exim4.sh b/deploy/exim4.sh index b53f58ec..bf92b438 100644 --- a/deploy/exim4.sh +++ b/deploy/exim4.sh @@ -4,6 +4,9 @@ #returns 0 means success, otherwise error. +#DEPLOY_EXIM4_CONF="/etc/exim/exim.conf" +#DEPLOY_EXIM4_RELOAD="service exim4 restart" + ######## Public functions ##################### #domain keyfile certfile cafile fullchain @@ -20,7 +23,92 @@ exim4_deploy() { _debug _cca "$_cca" _debug _cfullchain "$_cfullchain" - _err "deploy cert to exim4 server, Not implemented yet" - return 1 + _ssl_path="/etc/acme.sh/exim4" + if ! mkdir -p "$_ssl_path"; then + _err "Can not create folder:$_ssl_path" + return 1 + fi + + _info "Copying key and cert" + _real_key="$_ssl_path/exim4.key" + if ! cat "$_ckey" >"$_real_key"; then + _err "Error: write key file to: $_real_key" + return 1 + fi + _real_fullchain="$_ssl_path/exim4.pem" + if ! cat "$_cfullchain" >"$_real_fullchain"; then + _err "Error: write key file to: $_real_fullchain" + return 1 + fi + + DEFAULT_EXIM4_RELOAD="service exim4 restart" + _reload="${DEPLOY_EXIM4_RELOAD:-$DEFAULT_EXIM4_RELOAD}" + + if [ -z "$IS_RENEW" ]; then + DEFAULT_EXIM4_CONF="/etc/exim/exim.conf" + if [ ! -f "$DEFAULT_EXIM4_CONF" ]; then + DEFAULT_EXIM4_CONF="/etc/exim4/exim4.conf.template" + fi + _exim4_conf="${DEPLOY_EXIM4_CONF:-$DEFAULT_EXIM4_CONF}" + _debug _exim4_conf "$_exim4_conf" + if [ ! -f "$_exim4_conf" ]; then + if [ -z "$DEPLOY_EXIM4_CONF" ]; then + _err "exim4 conf is not found, please define DEPLOY_EXIM4_CONF" + return 1 + else + _err "It seems that the specified exim4 conf is not valid, please check." + return 1 + fi + fi + if [ ! -w "$_exim4_conf" ]; then + _err "The file $_exim4_conf is not writable, please change the permission." + return 1 + fi + _backup_conf="$DOMAIN_BACKUP_PATH/exim4.conf.bak" + _info "Backup $_exim4_conf to $_backup_conf" + cp "$_exim4_conf" "$_backup_conf" + + _info "Modify exim4 conf: $_exim4_conf" + if _setopt "$_exim4_conf" "tls_certificate" "=" "$_real_fullchain" \ + && _setopt "$_exim4_conf" "tls_privatekey" "=" "$_real_key"; then + _info "Set config success!" + else + _err "Config exim4 server error, please report bug to us." + _info "Restoring exim4 conf" + if cat "$_backup_conf" >"$_exim4_conf"; then + _info "Restore conf success" + eval "$_reload" + else + _err "Opps, error restore exim4 conf, please report bug to us." + fi + return 1 + fi + fi + + _info "Run reload: $_reload" + if eval "$_reload"; then + _info "Reload success!" + if [ "$DEPLOY_EXIM4_CONF" ]; then + _savedomainconf DEPLOY_EXIM4_CONF "$DEPLOY_EXIM4_CONF" + else + _cleardomainconf DEPLOY_EXIM4_CONF + fi + if [ "$DEPLOY_EXIM4_RELOAD" ]; then + _savedomainconf DEPLOY_EXIM4_RELOAD "$DEPLOY_EXIM4_RELOAD" + else + _cleardomainconf DEPLOY_EXIM4_RELOAD + fi + return 0 + else + _err "Reload error, restoring" + if cat "$_backup_conf" >"$_exim4_conf"; then + _info "Restore conf success" + eval "$_reload" + else + _err "Opps, error restore exim4 conf, please report bug to us." + fi + return 1 + fi + return 0 } diff --git a/deploy/keychain.sh b/deploy/keychain.sh new file mode 100644 index 00000000..a99ed465 --- /dev/null +++ b/deploy/keychain.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env sh + +#Here is a sample custom api script. +#This file name is "myapi.sh" +#So, here must be a method myapi_deploy() +#Which will be called by acme.sh to deploy the cert +#returns 0 means success, otherwise error. + +######## Public functions ##################### + +#domain keyfile certfile cafile fullchain +keychain_deploy() { + _cdomain="$1" + _ckey="$2" + _ccert="$3" + _cca="$4" + _cfullchain="$5" + + _debug _cdomain "$_cdomain" + _debug _ckey "$_ckey" + _debug _ccert "$_ccert" + _debug _cca "$_cca" + _debug _cfullchain "$_cfullchain" + + /usr/bin/security import "$_ckey" -k "/Library/Keychains/System.keychain" + /usr/bin/security import "$_ccert" -k "/Library/Keychains/System.keychain" + /usr/bin/security import "$_cca" -k "/Library/Keychains/System.keychain" + /usr/bin/security import "$_cfullchain" -k "/Library/Keychains/System.keychain" + + return 0 +} diff --git a/deploy/vsftpd.sh b/deploy/vsftpd.sh index 5e89ea95..1c6410a6 100644 --- a/deploy/vsftpd.sh +++ b/deploy/vsftpd.sh @@ -4,6 +4,9 @@ #returns 0 means success, otherwise error. +#DEPLOY_VSFTPD_CONF="/etc/vsftpd.conf" +#DEPLOY_VSFTPD_RELOAD="service vsftpd restart" + ######## Public functions ##################### #domain keyfile certfile cafile fullchain @@ -20,7 +23,88 @@ vsftpd_deploy() { _debug _cca "$_cca" _debug _cfullchain "$_cfullchain" - _err "deploy cert to vsftpd server, Not implemented yet" - return 1 + _ssl_path="/etc/acme.sh/vsftpd" + if ! mkdir -p "$_ssl_path"; then + _err "Can not create folder:$_ssl_path" + return 1 + fi + _info "Copying key and cert" + _real_key="$_ssl_path/vsftpd.key" + if ! cat "$_ckey" >"$_real_key"; then + _err "Error: write key file to: $_real_key" + return 1 + fi + _real_fullchain="$_ssl_path/vsftpd.chain.pem" + if ! cat "$_cfullchain" >"$_real_fullchain"; then + _err "Error: write key file to: $_real_fullchain" + return 1 + fi + + DEFAULT_VSFTPD_RELOAD="service vsftpd restart" + _reload="${DEPLOY_VSFTPD_RELOAD:-$DEFAULT_VSFTPD_RELOAD}" + + if [ -z "$IS_RENEW" ]; then + DEFAULT_VSFTPD_CONF="/etc/vsftpd.conf" + _vsftpd_conf="${DEPLOY_VSFTPD_CONF:-$DEFAULT_VSFTPD_CONF}" + if [ ! -f "$_vsftpd_conf" ]; then + if [ -z "$DEPLOY_VSFTPD_CONF" ]; then + _err "vsftpd conf is not found, please define DEPLOY_VSFTPD_CONF" + return 1 + else + _err "It seems that the specified vsftpd conf is not valid, please check." + return 1 + fi + fi + if [ ! -w "$_vsftpd_conf" ]; then + _err "The file $_vsftpd_conf is not writable, please change the permission." + return 1 + fi + _backup_conf="$DOMAIN_BACKUP_PATH/vsftpd.conf.bak" + _info "Backup $_vsftpd_conf to $_backup_conf" + cp "$_vsftpd_conf" "$_backup_conf" + + _info "Modify vsftpd conf: $_vsftpd_conf" + if _setopt "$_vsftpd_conf" "rsa_cert_file" "=" "$_real_fullchain" \ + && _setopt "$_vsftpd_conf" "rsa_private_key_file" "=" "$_real_key" \ + && _setopt "$_vsftpd_conf" "ssl_enable" "=" "YES"; then + _info "Set config success!" + else + _err "Config vsftpd server error, please report bug to us." + _info "Restoring vsftpd conf" + if cat "$_backup_conf" >"$_vsftpd_conf"; then + _info "Restore conf success" + eval "$_reload" + else + _err "Opps, error restore vsftpd conf, please report bug to us." + fi + return 1 + fi + fi + + _info "Run reload: $_reload" + if eval "$_reload"; then + _info "Reload success!" + if [ "$DEPLOY_VSFTPD_CONF" ]; then + _savedomainconf DEPLOY_VSFTPD_CONF "$DEPLOY_VSFTPD_CONF" + else + _cleardomainconf DEPLOY_VSFTPD_CONF + fi + if [ "$DEPLOY_VSFTPD_RELOAD" ]; then + _savedomainconf DEPLOY_VSFTPD_RELOAD "$DEPLOY_VSFTPD_RELOAD" + else + _cleardomainconf DEPLOY_VSFTPD_RELOAD + fi + return 0 + else + _err "Reload error, restoring" + if cat "$_backup_conf" >"$_vsftpd_conf"; then + _info "Restore conf success" + eval "$_reload" + else + _err "Opps, error restore vsftpd conf, please report bug to us." + fi + return 1 + fi + return 0 } diff --git a/dnsapi/README.md b/dnsapi/README.md index f326efa8..7404827e 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -343,6 +343,104 @@ acme.sh --issue --dns dns_cyon -d example.com -d www.example.com The `CY_Username`, `CY_Password` and `CY_OTP_Secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. +## 17. Use Domain-Offensive/Resellerinterface/Domainrobot API + +You will need your login credentials (Partner ID+Password) to the Resellerinterface, and export them before you run `acme.sh`: +``` +export DO_PID="KD-1234567" +export DO_PW="cdfkjl3n2" +``` + +Ok, let's issue a cert now: +``` +acme.sh --issue --dns dns_do -d example.com -d www.example.com +``` + +## 18. Use Gandi LiveDNS API + +You must enable the new Gandi LiveDNS API first and the create your api key, See: http://doc.livedns.gandi.net/ + +``` +export GANDI_LIVEDNS_KEY="fdmlfsdklmfdkmqsdfk" +``` + +Ok, let's issue a cert now: +``` +acme.sh --issue --dns dns_gandi_livedns -d example.com -d www.example.com +``` + +## 19. Use Knot (knsupdate) DNS API to automatically issue cert + +First, generate a TSIG key for updating the zone. + +``` +keymgr tsig generate acme_key algorithm hmac-sha512 > /etc/knot/acme.key +``` + +Include this key in your knot configuration file. + +``` +include: /etc/knot/acme.key +``` + +Next, configure your zone to allow dynamic updates. + +Dynamic updates for the zone are allowed via proper ACL rule with the `update` action. For in-depth instructions, please see [Knot DNS's documentation](https://www.knot-dns.cz/documentation/). + +``` +acl: + - id: acme_acl + address: 192.168.1.0/24 + key: acme_key + action: update + +zone: + - domain: example.com + file: example.com.zone + acl: acme_acl +``` + +Finally, make the DNS server and TSIG Key available to `acme.sh` + +``` +export KNOT_SERVER="dns.example.com" +export KNOT_KEY=`grep \# /etc/knot/acme.key | cut -d' ' -f2` +``` + +Ok, let's issue a cert now: +``` +acme.sh --issue --dns dns_knot -d example.com -d www.example.com +``` + +The `KNOT_SERVER` and `KNOT_KEY` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed. + +## 20. Use DigitalOcean API (native) + +You need to obtain a read and write capable API key from your DigitalOcean account. See: https://www.digitalocean.com/help/api/ + +``` +export DO_API_KEY="75310dc4ca779ac39a19f6355db573b49ce92ae126553ebd61ac3a3ae34834cc" +``` + +Ok, let's issue a cert now: +``` +acme.sh --issue --dns dns_dgon -d example.com -d www.example.com +``` + +## 21. Use ClouDNS.net API + +You need to set the HTTP API user ID and password credentials. See: https://www.cloudns.net/wiki/article/42/ + +``` +export CLOUDNS_AUTH_ID=XXXXX +export CLOUDNS_AUTH_PASSWORD="YYYYYYYYY" +``` + +Ok, let's issue a cert now: +``` +acme.sh --issue --dns dns_cloudns -d example.com -d www.example.com +``` + # Use custom API If your API is not supported yet, you can write your own DNS API. diff --git a/dnsapi/dns_aws.sh b/dnsapi/dns_aws.sh index 84aa28d3..d5d52de0 100755 --- a/dnsapi/dns_aws.sh +++ b/dnsapi/dns_aws.sh @@ -143,7 +143,7 @@ aws_rest() { CanonicalHeaders="host:$aws_host\nx-amz-date:$RequestDate\n" SignedHeaders="host;x-amz-date" if [ -n "$AWS_SESSION_TOKEN" ]; then - export _H2="x-amz-security-token: $AWS_SESSION_TOKEN" + export _H3="x-amz-security-token: $AWS_SESSION_TOKEN" CanonicalHeaders="${CanonicalHeaders}x-amz-security-token:$AWS_SESSION_TOKEN\n" SignedHeaders="${SignedHeaders};x-amz-security-token" fi @@ -204,8 +204,8 @@ aws_rest() { Authorization="$Algorithm Credential=$AWS_ACCESS_KEY_ID/$CredentialScope, SignedHeaders=$SignedHeaders, Signature=$signature" _debug2 Authorization "$Authorization" - _H3="Authorization: $Authorization" - _debug _H3 "$_H3" + _H2="Authorization: $Authorization" + _debug _H2 "$_H2" url="$AWS_URL/$ep" diff --git a/dnsapi/dns_cloudns.sh b/dnsapi/dns_cloudns.sh new file mode 100755 index 00000000..f48a8052 --- /dev/null +++ b/dnsapi/dns_cloudns.sh @@ -0,0 +1,170 @@ +#!/usr/bin/env sh + +# Author: Boyan Peychev +# Repository: https://github.com/ClouDNS/acme.sh/ + +#CLOUDNS_AUTH_ID=XXXXX +#CLOUDNS_AUTH_PASSWORD="YYYYYYYYY" +CLOUDNS_API="https://api.cloudns.net" + +######## Public functions ##################### + +#Usage: dns_cloudns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_cloudns_add() { + _info "Using cloudns" + + if ! _dns_cloudns_init_check; then + return 1 + fi + + zone="$(_dns_cloudns_get_zone_name "$1")" + if [ -z "$zone" ]; then + _err "Missing DNS zone at ClouDNS. Please log into your control panel and create the required DNS zone for the initial setup." + return 1 + fi + + host="$(echo "$1" | sed "s/\.$zone\$//")" + record=$2 + record_id=$(_dns_cloudns_get_record_id "$zone" "$host") + + _debug zone "$zone" + _debug host "$host" + _debug record "$record" + _debug record_id "$record_id" + + if [ -z "$record_id" ]; then + _info "Adding the TXT record for $1" + _dns_cloudns_http_api_call "dns/add-record.json" "domain-name=$zone&record-type=TXT&host=$host&record=$record&ttl=60" + if ! _contains "$response" "\"status\":\"Success\""; then + _err "Record cannot be added." + return 1 + fi + _info "Added." + else + _info "Updating the TXT record for $1" + _dns_cloudns_http_api_call "dns/mod-record.json" "domain-name=$zone&record-id=$record_id&record-type=TXT&host=$host&record=$record&ttl=60" + if ! _contains "$response" "\"status\":\"Success\""; then + _err "The TXT record for $1 cannot be updated." + return 1 + fi + _info "Updated." + fi + + return 0 +} + +#Usage: dns_cloudns_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_cloudns_rm() { + _info "Using cloudns" + + if ! _dns_cloudns_init_check; then + return 1 + fi + + if [ -z "$zone" ]; then + zone="$(_dns_cloudns_get_zone_name "$1")" + if [ -z "$zone" ]; then + _err "Missing DNS zone at ClouDNS. Please log into your control panel and create the required DNS zone for the initial setup." + return 1 + fi + fi + + host="$(echo "$1" | sed "s/\.$zone\$//")" + record=$2 + record_id=$(_dns_cloudns_get_record_id "$zone" "$host") + + _debug zone "$zone" + _debug host "$host" + _debug record "$record" + _debug record_id "$record_id" + + if [ ! -z "$record_id" ]; then + _info "Deleting the TXT record for $1" + _dns_cloudns_http_api_call "dns/delete-record.json" "domain-name=$zone&record-id=$record_id" + if ! _contains "$response" "\"status\":\"Success\""; then + _err "The TXT record for $1 cannot be deleted." + return 1 + fi + _info "Deleted." + fi + return 0 +} + +#################### Private functions below ################################## +_dns_cloudns_init_check() { + if [ ! -z "$CLOUDNS_INIT_CHECK_COMPLETED" ]; then + return 0 + fi + + if [ -z "$CLOUDNS_AUTH_ID" ]; then + _err "CLOUDNS_AUTH_ID is not configured" + return 1 + fi + + if [ -z "$CLOUDNS_AUTH_PASSWORD" ]; then + _err "CLOUDNS_AUTH_PASSWORD is not configured" + return 1 + fi + + _dns_cloudns_http_api_call "dns/login.json" "" + + if ! _contains "$response" "\"status\":\"Success\""; then + _err "Invalid CLOUDNS_AUTH_ID or CLOUDNS_AUTH_PASSWORD. Please check your login credentials." + return 1 + fi + + CLOUDNS_INIT_CHECK_COMPLETED=1 + + return 0 +} + +_dns_cloudns_get_zone_name() { + i=2 + while true; do + zoneForCheck=$(printf "%s" "$1" | cut -d . -f $i-100) + + if [ -z "$zoneForCheck" ]; then + return 1 + fi + + _debug zoneForCheck "$zoneForCheck" + + _dns_cloudns_http_api_call "dns/get-zone-info.json" "domain-name=$zoneForCheck" + + if ! _contains "$response" "\"status\":\"Failed\""; then + echo "$zoneForCheck" + return 0 + fi + + i=$(_math "$i" + 1) + done + return 1 +} + +_dns_cloudns_get_record_id() { + _dns_cloudns_http_api_call "dns/records.json" "domain-name=$1&host=$2&type=TXT" + if _contains "$response" "\"id\":"; then + echo "$response" | cut -d '"' -f 2 + return 0 + fi + return 1 +} + +_dns_cloudns_http_api_call() { + method=$1 + + _debug CLOUDNS_AUTH_ID "$CLOUDNS_AUTH_ID" + _debug CLOUDNS_AUTH_PASSWORD "$CLOUDNS_AUTH_PASSWORD" + + if [ -z "$2" ]; then + data="auth-id=$CLOUDNS_AUTH_ID&auth-password=$CLOUDNS_AUTH_PASSWORD" + else + data="auth-id=$CLOUDNS_AUTH_ID&auth-password=$CLOUDNS_AUTH_PASSWORD&$2" + fi + + response="$(_get "$CLOUDNS_API/$method?$data")" + + _debug2 response "$response" + + return 0 +} diff --git a/dnsapi/dns_cx.sh b/dnsapi/dns_cx.sh index 2b6d5691..e2f0f099 100755 --- a/dnsapi/dns_cx.sh +++ b/dnsapi/dns_cx.sh @@ -209,8 +209,7 @@ _rest() { return 1 fi _debug2 response "$response" - if ! _contains "$response" '"message":"success"'; then - return 1 - fi - return 0 + + _contains "$response" '"code":1' + } diff --git a/dnsapi/dns_dgon.sh b/dnsapi/dns_dgon.sh new file mode 100755 index 00000000..9ceede44 --- /dev/null +++ b/dnsapi/dns_dgon.sh @@ -0,0 +1,205 @@ +#!/usr/bin/env sh + +## Will be called by acme.sh to add the txt record to your api system. +## returns 0 means success, otherwise error. + +## Author: thewer +## GitHub: https://github.com/gitwer/acme.sh + +## +## Environment Variables Required: +## +## DO_API_KEY="75310dc4ca779ac39a19f6355db573b49ce92ae126553ebd61ac3a3ae34834cc" +## + +##################### Public functions ##################### + +## Create the text record for validation. +## Usage: fulldomain txtvalue +## EG: "_acme-challenge.www.other.domain.com" "XKrxpRBosdq0HG9i01zxXp5CPBs" +dns_dgon_add() { + fulldomain="$(echo "$1" | _lower_case)" + txtvalue=$2 + _info "Using digitalocean dns validation - add record" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + ## save the env vars (key and domain split location) for later automated use + _saveaccountconf DO_API_KEY "$DO_API_KEY" + + ## split the domain for DO API + if ! _get_base_domain "$fulldomain"; then + _err "domain not found in your account for addition" + return 1 + fi + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + ## Set the header with our post type and key auth key + export _H1="Content-Type: application/json" + export _H2="Authorization: Bearer $DO_API_KEY" + PURL='https://api.digitalocean.com/v2/domains/'$_domain'/records' + PBODY='{"type":"TXT","name":"'$_sub_domain'","data":"'$txtvalue'"}' + + _debug PURL "$PURL" + _debug PBODY "$PBODY" + + ## the create request - post + ## args: BODY, URL, [need64, httpmethod] + response="$(_post "$PBODY" "$PURL")" + + ## check response + if [ "$?" != "0" ]; then + _err "error in response: $response" + return 1 + fi + _debug2 response "$response" + + ## finished correctly + return 0 +} + +## Remove the txt record after validation. +## Usage: fulldomain txtvalue +## EG: "_acme-challenge.www.other.domain.com" "XKrxpRBosdq0HG9i01zxXp5CPBs" +dns_dgon_rm() { + fulldomain="$(echo "$1" | _lower_case)" + txtvalue=$2 + _info "Using digitalocean dns validation - remove record" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + ## split the domain for DO API + if ! _get_base_domain "$fulldomain"; then + _err "domain not found in your account for removal" + return 1 + fi + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + ## Set the header with our post type and key auth key + export _H1="Content-Type: application/json" + export _H2="Authorization: Bearer $DO_API_KEY" + ## get URL for the list of domains + ## may get: "links":{"pages":{"last":".../v2/domains/DOM/records?page=2","next":".../v2/domains/DOM/records?page=2"}} + GURL="https://api.digitalocean.com/v2/domains/$_domain/records" + + ## while we dont have a record ID we keep going + while [ -z "$record" ]; do + ## 1) get the URL + ## the create request - get + ## args: URL, [onlyheader, timeout] + domain_list="$(_get "$GURL")" + ## 2) find record + ## check for what we are looing for: "type":"A","name":"$_sub_domain" + record="$(echo "$domain_list" | _egrep_o "\"id\"\s*\:\s*\"*\d+\"*[^}]*\"name\"\s*\:\s*\"$_sub_domain\"[^}]*\"data\"\s*\:\s*\"$txtvalue\"")" + ## 3) check record and get next page + if [ -z "$record" ]; then + ## find the next page if we dont have a match + nextpage="$(echo "$domain_list" | _egrep_o "\"links\".*" | _egrep_o "\"next\".*" | _egrep_o "http.*page\=\d+")" + if [ -z "$nextpage" ]; then + _err "no record and no nextpage in digital ocean DNS removal" + return 1 + fi + _debug2 nextpage "$nextpage" + GURL="$nextpage" + fi + ## we break out of the loop when we have a record + done + + ## we found the record + rec_id="$(echo "$record" | _egrep_o "id\"\s*\:\s*\"*\d+" | _egrep_o "\d+")" + _debug rec_id "$rec_id" + + ## delete the record + ## delete URL for removing the one we dont want + DURL="https://api.digitalocean.com/v2/domains/$_domain/records/$rec_id" + + ## the create request - delete + ## args: BODY, URL, [need64, httpmethod] + response="$(_post "" "$DURL" "" "DELETE")" + + ## check response (sort of) + if [ "$?" != "0" ]; then + _err "error in remove response: $response" + return 1 + fi + _debug2 response "$response" + + ## finished correctly + return 0 +} + +##################### Private functions below ##################### + +## Split the domain provided into the "bade domain" and the "start prefix". +## This function searches for the longest subdomain in your account +## for the full domain given and splits it into the base domain (zone) +## and the prefix/record to be added/removed +## USAGE: fulldomain +## EG: "_acme-challenge.two.three.four.domain.com" +## returns +## _sub_domain="_acme-challenge.two" +## _domain="three.four.domain.com" *IF* zone "three.four.domain.com" exists +## if only "domain.com" exists it will return +## _sub_domain="_acme-challenge.two.three.four" +## _domain="domain.com" +_get_base_domain() { + # args + fulldomain="$(echo "$1" | tr '[:upper:]' '[:lower:]')" + _debug fulldomain "$fulldomain" + + # domain max legal length = 253 + MAX_DOM=255 + + ## get a list of domains for the account to check thru + ## Set the headers + export _H1="Content-Type: application/json" + export _H2="Authorization: Bearer $DO_API_KEY" + _debug DO_API_KEY "$DO_API_KEY" + ## get URL for the list of domains + ## havent seen this request paginated, tested with 18 domains (more requres manual requests with DO) + DOMURL="https://api.digitalocean.com/v2/domains" + + ## get the domain list (DO gives basically a full XFER!) + domain_list="$(_get "$DOMURL")" + + ## check response + if [ "$?" != "0" ]; then + _err "error in domain_list response: $domain_list" + return 1 + fi + _debug2 domain_list "$domain_list" + + ## for each shortening of our $fulldomain, check if it exists in the $domain_list + ## can never start on 1 (aka whole $fulldomain) as $fulldomain starts with "_acme-challenge" + i=2 + while [ $i -gt 0 ]; do + ## get next longest domain + _domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-"$MAX_DOM") + ## check we got something back from our cut (or are we at the end) + if [ -z "$_domain" ]; then + ## we got to the end of the domain - invalid domain + _err "domain not found in DigitalOcean account" + return 1 + fi + ## we got part of a domain back - grep it out + found="$(echo "$domain_list" | _egrep_o "\"name\"\s*\:\s*\"$_domain\"")" + ## check if it exists + if [ ! -z "$found" ]; then + ## exists - exit loop returning the parts + sub_point=$(_math $i - 1) + _sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$sub_point") + _debug _domain "$_domain" + _debug _sub_domain "$_sub_domain" + return 0 + fi + ## increment cut point $i + i=$(_math $i + 1) + done + + ## we went through the entire domain zone list and dint find one that matched + ## doesnt look like we can add in the record + _err "domain not found in DigitalOcean account, but we should never get here" + return 1 +} diff --git a/dnsapi/dns_do.sh b/dnsapi/dns_do.sh new file mode 100755 index 00000000..3a2f8f49 --- /dev/null +++ b/dnsapi/dns_do.sh @@ -0,0 +1,148 @@ +#!/usr/bin/env sh + +# DNS API for Domain-Offensive / Resellerinterface / Domainrobot + +# Report bugs at https://github.com/seidler2547/acme.sh/issues + +# set these environment variables to match your customer ID and password: +# DO_PID="KD-1234567" +# DO_PW="cdfkjl3n2" + +DO_URL="https://soap.resellerinterface.de/" + +######## Public functions ##################### + +#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_do_add() { + fulldomain=$1 + txtvalue=$2 + if _dns_do_authenticate; then + _info "Adding TXT record to ${_domain} as ${fulldomain}" + _dns_do_soap createRR origin "${_domain}" name "${fulldomain}" type TXT data "${txtvalue}" ttl 300 + if _contains "${response}" '>success<'; then + return 0 + fi + _err "Could not create resource record, check logs" + fi + return 1 +} + +#fulldomain +dns_do_rm() { + fulldomain=$1 + if _dns_do_authenticate; then + if _dns_do_list_rrs; then + _dns_do_had_error=0 + for _rrid in ${_rr_list}; do + _info "Deleting resource record $_rrid for $_domain" + _dns_do_soap deleteRR origin "${_domain}" rrid "${_rrid}" + if ! _contains "${response}" '>success<'; then + _dns_do_had_error=1 + _err "Could not delete resource record for ${_domain}, id ${_rrid}" + fi + done + return $_dns_do_had_error + fi + fi + return 1 +} + +#################### Private functions below ################################## +_dns_do_authenticate() { + _info "Authenticating as ${DO_PID}" + _dns_do_soap authPartner partner "${DO_PID}" password "${DO_PW}" + if _contains "${response}" '>success<'; then + _get_root "$fulldomain" + _debug "_domain $_domain" + return 0 + else + _err "Authentication failed, are DO_PID and DO_PW set correctly?" + fi + return 1 +} + +_dns_do_list_rrs() { + _dns_do_soap getRRList origin "${_domain}" + if ! _contains "${response}" 'SOAP-ENC:Array'; then + _err "getRRList origin ${_domain} failed" + return 1 + fi + _rr_list="$(echo "${response}" \ + | tr -d "\n\r\t" \ + | sed -e 's//\n/g' \ + | grep ">$(_regexcape "$fulldomain")" \ + | sed -e 's/<\/item>/\n/g' \ + | grep '>id[0-9]{1,16}<' \ + | tr -d '><')" + [ "${_rr_list}" ] +} + +_dns_do_soap() { + func="$1" + shift + # put the parameters to xml + body="" + while [ "$1" ]; do + _k="$1" + shift + _v="$1" + shift + body="$body<$_k>$_v" + done + body="$body" + _debug2 "SOAP request ${body}" + + # build SOAP XML + _xml=' + + '"$body"' +' + + # set SOAP headers + export _H1="SOAPAction: ${DO_URL}#${func}" + + if ! response="$(_post "${_xml}" "${DO_URL}")"; then + _err "Error <$1>" + return 1 + fi + _debug2 "SOAP response $response" + + # retrieve cookie header + _H2="$(_egrep_o 'Cookie: [^;]+' <"$HTTP_HEADER" | _head_n 1)" + export _H2 + + return 0 +} + +_get_root() { + domain=$1 + i=1 + + _dns_do_soap getDomainList + _all_domains="$(echo "${response}" \ + | tr -d "\n\r\t " \ + | _egrep_o 'domain]+>[^<]+' \ + | sed -e 's/^domain<\/key>]*>//g')" + + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + if [ -z "$h" ]; then + return 1 + fi + + if _contains "${_all_domains}" "^$(_regexcape "$h")\$"; then + _domain="$h" + return 0 + fi + + i=$(_math $i + 1) + done + _debug "$domain not found" + + return 1 +} + +_regexcape() { + echo "$1" | sed -e 's/\([]\.$*^[]\)/\\\1/g' +} diff --git a/dnsapi/dns_gandi_livedns.sh b/dnsapi/dns_gandi_livedns.sh new file mode 100755 index 00000000..41f42980 --- /dev/null +++ b/dnsapi/dns_gandi_livedns.sh @@ -0,0 +1,123 @@ +#!/usr/bin/env sh + +# Gandi LiveDNS v5 API +# http://doc.livedns.gandi.net/ +# currently under beta +# +# Requires GANDI API KEY set in GANDI_LIVEDNS_KEY set as environment variable +# +#Author: Frédéric Crozat +#Report Bugs here: https://github.com/fcrozat/acme.sh +# +######## Public functions ##################### + +GANDI_LIVEDNS_API="https://dns.beta.gandi.net/api/v5" + +#Usage: dns_gandi_livedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_gandi_livedns_add() { + fulldomain=$1 + txtvalue=$2 + + if [ -z "$GANDI_LIVEDNS_KEY" ]; then + _err "No API key specifed for Gandi LiveDNS." + _err "Create your key and export it as GANDI_LIVEDNS_KEY" + return 1 + fi + + _saveaccountconf GANDI_LIVEDNS_KEY "$GANDI_LIVEDNS_KEY" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + _debug domain "$_domain" + _debug sub_domain "$_sub_domain" + + _gandi_livedns_rest PUT "domains/$_domain/records/$_sub_domain/TXT" "{\"rrset_ttl\": 300, \"rrset_values\":[\"$txtvalue\"]}" \ + && _contains "$response" '{"message": "Zone Record Created"}' \ + && _info "Add $(__green "success")" +} + +#Usage: fulldomain txtvalue +#Remove the txt record after validation. +dns_gandi_livedns_rm() { + fulldomain=$1 + txtvalue=$2 + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + _debug fulldomain "$fulldomain" + _debug domain "$_domain" + _debug sub_domain "$_sub_domain" + + _gandi_livedns_rest DELETE "domains/$_domain/records/$_sub_domain/TXT" "" + +} + +#################### Private functions below ################################## +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +_get_root() { + domain=$1 + i=2 + p=1 + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + _debug h "$h" + if [ -z "$h" ]; then + #not valid + return 1 + fi + + if ! _gandi_livedns_rest GET "domains/$h"; then + return 1 + fi + + if _contains "$response" '"code": 401'; then + _err "$response" + return 1 + elif _contains "$response" '"code": 404'; then + _debug "$h not found" + else + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) + _domain="$h" + return 0 + fi + p="$i" + i=$(_math "$i" + 1) + done + return 1 +} + +_gandi_livedns_rest() { + m=$1 + ep="$2" + data="$3" + _debug "$ep" + + export _H1="Content-Type: application/json" + export _H2="X-Api-Key: $GANDI_LIVEDNS_KEY" + + if [ "$m" = "GET" ]; then + response="$(_get "$GANDI_LIVEDNS_API/$ep")" + else + _debug data "$data" + response="$(_post "$data" "$GANDI_LIVEDNS_API/$ep" "" "$m")" + fi + + if [ "$?" != "0" ]; then + _err "error $ep" + return 1 + fi + _debug2 response "$response" + return 0 +} diff --git a/dnsapi/dns_knot.sh b/dnsapi/dns_knot.sh new file mode 100644 index 00000000..094a6981 --- /dev/null +++ b/dnsapi/dns_knot.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env sh + +######## Public functions ##################### + +#Usage: dns_knot_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_knot_add() { + fulldomain=$1 + txtvalue=$2 + _checkKey || return 1 + [ -n "${KNOT_SERVER}" ] || KNOT_SERVER="localhost" + # save the dns server and key to the account.conf file. + _saveaccountconf KNOT_SERVER "${KNOT_SERVER}" + _saveaccountconf KNOT_KEY "${KNOT_KEY}" + + if ! _get_root "$fulldomain"; then + _err "Domain does not exist." + return 1 + fi + + _info "Adding ${fulldomain}. 60 TXT \"${txtvalue}\"" + + knsupdate -y "${KNOT_KEY}" <