diff --git a/README.md b/README.md index c8bebc6..b9a5cc5 100644 --- a/README.md +++ b/README.md @@ -321,6 +321,12 @@ You don't have to do anything manually! 1. acme-dns (https://github.com/joohoi/acme-dns) 1. TELE3 (https://www.tele3.cz) 1. EUSERV.EU (https://www.euserv.eu) +1. DNSPod.com API (https://www.dnspod.com) +1. Google Cloud DNS API +1. ConoHa (https://www.conoha.jp) +1. netcup DNS API (https://www.netcup.de) +1. GratisDNS.dk (https://gratisdns.dk) +1. Namecheap API (https://www.namecheap.com/) And: diff --git a/acme.sh b/acme.sh index db2953e..ee23827 100755 --- a/acme.sh +++ b/acme.sh @@ -1327,6 +1327,7 @@ createDomainKey() { if _createkey "$_cdl" "$CERT_KEY_PATH"; then _savedomainconf Le_Keylength "$_cdl" _info "The domain key is here: $(__green $CERT_KEY_PATH)" + return 0 fi else if [ "$IS_RENEW" ]; then diff --git a/deploy/README.md b/deploy/README.md index 181989d..5c03ce6 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -275,3 +275,24 @@ acme.sh --deploy -d haproxy.example.com --deploy-hook haproxy ``` The path for the PEM file will be stored with the domain configuration and will be available when renewing, so that deploy will happen automatically when renewed. + +## 11. Deploy your cert to Gitlab pages + +You must define the API key and the informations for the project and Gitlab page you are updating the certificate for. + +```sh +# The token can be created in your user settings under "Access Tokens" +export GITLAB_TOKEN="xxxxxxxxxxx" + +# The project ID is displayed on the home page of the project +export GITLAB_PROJECT_ID=12345678 + +# The domain must match the one defined for the Gitlab page, without "https://" +export GITLAB_DOMAIN="www.mydomain.com" +``` + +You can then deploy the certificate as follows + +```sh +acme.sh --deploy -d www.mydomain.com --deploy-hook gitlab +``` \ No newline at end of file diff --git a/deploy/fritzbox.sh b/deploy/fritzbox.sh index 943b198..21ea6cf 100644 --- a/deploy/fritzbox.sh +++ b/deploy/fritzbox.sh @@ -28,8 +28,10 @@ fritzbox_deploy() { _debug _cfullchain "$_cfullchain" if ! _exists iconv; then - _err "iconv not found" - return 1 + if ! _exists perl; then + _err "iconv or perl not found" + return 1 + fi fi _fritzbox_username="${DEPLOY_FRITZBOX_USERNAME}" @@ -61,7 +63,11 @@ fritzbox_deploy() { _info "Log in to the FRITZ!Box" _fritzbox_challenge="$(_get "${_fritzbox_url}/login_sid.lua" | sed -e 's/^.*//' -e 's/<\/Challenge>.*$//')" - _fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${_fritzbox_password}" | iconv -f ASCII -t UTF16LE | md5sum | awk '{print $1}')" + if _exists iconv; then + _fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${_fritzbox_password}" | iconv -f ASCII -t UTF16LE | md5sum | awk '{print $1}')" + else + _fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${_fritzbox_password}" | perl -p -e 'use Encode qw/encode/; print encode("UTF-16LE","$_"); $_="";' | md5sum | awk '{print $1}')" + fi _fritzbox_sid="$(_get "${_fritzbox_url}/login_sid.lua?sid=0000000000000000&username=${_fritzbox_username}&response=${_fritzbox_challenge}-${_fritzbox_hash}" | sed -e 's/^.*//' -e 's/<\/SID>.*$//')" if [ -z "${_fritzbox_sid}" ] || [ "${_fritzbox_sid}" = "0000000000000000" ]; then diff --git a/deploy/gitlab.sh b/deploy/gitlab.sh new file mode 100644 index 0000000..ba2d312 --- /dev/null +++ b/deploy/gitlab.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env sh + +# Script to deploy certificate to a Gitlab hosted page + +# The following variables exported from environment will be used. +# If not set then values previously saved in domain.conf file are used. + +# All the variables are required + +# export GITLAB_TOKEN="xxxxxxx" +# export GITLAB_PROJECT_ID=012345 +# export GITLAB_DOMAIN="mydomain.com" + +gitlab_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" + + if [ -z "$GITLAB_TOKEN" ]; then + if [ -z "$Le_Deploy_gitlab_token" ]; then + _err "GITLAB_TOKEN not defined." + return 1 + fi + else + Le_Deploy_gitlab_token="$GITLAB_TOKEN" + _savedomainconf Le_Deploy_gitlab_token "$Le_Deploy_gitlab_token" + fi + + if [ -z "$GITLAB_PROJECT_ID" ]; then + if [ -z "$Le_Deploy_gitlab_project_id" ]; then + _err "GITLAB_PROJECT_ID not defined." + return 1 + fi + else + Le_Deploy_gitlab_project_id="$GITLAB_PROJECT_ID" + _savedomainconf Le_Deploy_gitlab_project_id "$Le_Deploy_gitlab_project_id" + fi + + if [ -z "$GITLAB_DOMAIN" ]; then + if [ -z "$Le_Deploy_gitlab_domain" ]; then + _err "GITLAB_DOMAIN not defined." + return 1 + fi + else + Le_Deploy_gitlab_domain="$GITLAB_DOMAIN" + _savedomainconf Le_Deploy_gitlab_domain "$Le_Deploy_gitlab_domain" + fi + + string_fullchain=$(_url_encode <"$_cfullchain") + string_key=$(_url_encode <"$_ckey") + + body="certificate=$string_fullchain&key=$string_key" + + export _H1="PRIVATE-TOKEN: $Le_Deploy_gitlab_token" + + gitlab_url="https://gitlab.com/api/v4/projects/$Le_Deploy_gitlab_project_id/pages/domains/$Le_Deploy_gitlab_domain" + + _response=$(_post "$body" "$gitlab_url" 0 PUT | _dbase64 "multiline") + + error_response="error" + + if test "${_response#*$error_response}" != "$_response"; then + _err "Error in deploying certificate:" + _err "$_response" + return 1 + fi + + _debug response "$_response" + _info "Certificate successfully deployed" + + return 0 +} diff --git a/deploy/ssh.sh b/deploy/ssh.sh index a68da35..9cb0af9 100644 --- a/deploy/ssh.sh +++ b/deploy/ssh.sh @@ -11,7 +11,7 @@ # # Only a username is required. All others are optional. # -# The following examples are for QNAP NAS running QTS 4.2 +# The following examples are for QNAP NAS running QTS 4.2 # export DEPLOY_SSH_CMD="" # defaults to ssh # export DEPLOY_SSH_USER="admin" # required # export DEPLOY_SSH_SERVER="qnap" # defaults to domain name @@ -101,7 +101,7 @@ ssh_deploy() { fi # CERTFILE is optional. - # If provided then private key will be copied or appended to provided filename. + # If provided then certificate will be copied or appended to provided filename. if [ -n "$DEPLOY_SSH_CERTFILE" ]; then Le_Deploy_ssh_certfile="$DEPLOY_SSH_CERTFILE" _savedomainconf Le_Deploy_ssh_certfile "$Le_Deploy_ssh_certfile" @@ -190,7 +190,7 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d _info "Backup directories erased after 180 days." fi - _debug "Remote commands to execute: $_cmdstr" + _secure_debug "Remote commands to execute: " "$_cmdstr" _info "Submitting sequence of commands to remote server by ssh" # quotations in bash cmd below intended. Squash travis spellcheck error # shellcheck disable=SC2029 diff --git a/dnsapi/README.md b/dnsapi/README.md index 1f394f9..2cecfa5 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -876,6 +876,7 @@ acme.sh --issue --dns dns_tele3 -d example.com -d *.example.com ``` The TELE3_Key and TELE3_Secret will be saved in ~/.acme.sh/account.conf and will be reused when needed. + ## 47. Use Euserv.eu API First you need to login to your euserv.eu account and activate your API Administration (API Verwaltung). @@ -897,6 +898,122 @@ acme.sh --issue --dns dns_euserv -d example.com -d *.example.com --insecure The `EUSERV_Username` and `EUSERV_Password` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. Please report any issues to https://github.com/initit/acme.sh or to + +## 48. Use DNSPod.com domain API to automatically issue cert + +First you need to get your API Key and ID by this [get-the-user-token](https://www.dnspod.com/docs/info.html#get-the-user-token). + +``` +export DPI_Id="1234" +export DPI_Key="sADDsdasdgdsf" +``` + +Ok, let's issue a cert now: +``` +acme.sh --issue --dns dns_dpi -d example.com -d www.example.com +``` + +The `DPI_Id` and `DPI_Key` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. + +## 49. Use Google Cloud DNS API to automatically issue cert + +First you need to authenticate to gcloud. + +``` +gcloud init +``` + +**The `dns_gcloud` script uses the active gcloud configuration and credentials.** +There is no logic inside `dns_gcloud` to override the project and other settings. +If needed, create additional [gcloud configurations](https://cloud.google.com/sdk/gcloud/reference/topic/configurations). +You can change the configuration being used without *activating* it; simply set the `CLOUDSDK_ACTIVE_CONFIG_NAME` environment variable. + +To issue a certificate you can: +``` +export CLOUDSDK_ACTIVE_CONFIG_NAME=default # see the note above +acme.sh --issue --dns dns_gcloud -d example.com -d '*.example.com' +``` + +`dns_gcloud` also supports [DNS alias mode](https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode). + +## 50. Use ConoHa API + +First you need to login to your ConoHa account to get your API credentials. + +``` +export CONOHA_Username="xxxxxx" +export CONOHA_Password="xxxxxx" +export CONOHA_TenantId="xxxxxx" +export CONOHA_IdentityServiceApi="https://identity.xxxx.conoha.io/v2.0" +``` + +To issue a cert: +``` +acme.sh --issue --dns dns_conoha -d example.com -d www.example.com +``` + +The `CONOHA_Username`, `CONOHA_Password`, `CONOHA_TenantId` and `CONOHA_IdentityServiceApi` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. + +## 51. Use netcup DNS API to automatically issue cert + +First you need to login in your CCP account to get your API Key and API Password. +``` +export NC_Apikey="" +export NC_Apipw="" +export NC_CID="" +``` + +Now, let's issue a cert: +``` +acme.sh --issue --dns dns_netcup -d example.com -d www.example.com +``` + +The `NC_Apikey`,`NC_Apipw` and `NC_CID` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. + +## 52. Use GratisDNS.dk + +GratisDNS.dk (https://gratisdns.dk/) does not provide an API to update DNS records (other than IPv4 and IPv6 +dynamic DNS addresses). The acme.sh plugin therefore retrieves and updates domain TXT records by logging +into the GratisDNS website to read the HTML and posting updates as HTTP. The plugin needs to know your +userid and password for the GratisDNS website. + +```sh +export GDNSDK_Username="..." +export GDNSDK_Password="..." +``` +The username and password will be saved in `~/.acme.sh/account.conf` and will be reused when needed. + + +Now you can issue a certificate. + +Note: It usually takes a few minutes (usually 3-4 minutes) before the changes propagates to gratisdns.dk nameservers (ns3.gratisdns.dk often are slow), +and in rare cases I have seen over 5 minutes before google DNS catches it. Therefor a DNS sleep of at least 300 seconds are recommended- + +```sh +acme.sh --issue --dns dns_gdnsdk --dnssleep 300 -d example.com -d *.example.com +``` + +## 53. Use Namecheap + +You will need your namecheap username, API KEY (https://www.namecheap.com/support/api/intro.aspx) and your external IP address (or an URL to get it), this IP will need to be whitelisted at Namecheap. +Due to Namecheap's API limitation all the records of your domain will be read and re applied, make sure to have a backup of your records you could apply if any issue would arise. + +```sh +export NAMECHEAP_USERNAME="..." +export NAMECHEAP_API_KEY="..." +export NAMECHEAP_SOURCEIP="..." +``` + +NAMECHEAP_SOURCEIP can either be an IP address or an URL to provide it (e.g. https://ifconfig.co/ip). + +The username and password will be saved in `~/.acme.sh/account.conf` and will be reused when needed. + +Now you can issue a certificate. + +```sh +acme.sh --issue --dns dns_namecheap -d example.com -d *.example.com +``` + # Use custom API If your API is not supported yet, you can write your own DNS API. @@ -917,4 +1034,4 @@ See: https://github.com/Neilpang/acme.sh/wiki/DNS-API-Dev-Guide # Use lexicon DNS API -https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api \ No newline at end of file +https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api diff --git a/dnsapi/dns_aws.sh b/dnsapi/dns_aws.sh index 8ce7c34..2ad3c81 100755 --- a/dnsapi/dns_aws.sh +++ b/dnsapi/dns_aws.sh @@ -29,7 +29,7 @@ dns_aws_add() { if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then AWS_ACCESS_KEY_ID="" AWS_SECRET_ACCESS_KEY="" - _err "You don't specify aws route53 api key id and and api key secret yet." + _err "You haven't specifed the aws route53 api key id and and api key secret yet." _err "Please create your key and try again. see $(__green $AWS_WIKI)" return 1 fi @@ -62,7 +62,7 @@ dns_aws_add() { fi if [ "$_resource_record" ] && _contains "$response" "$txtvalue"; then - _info "The txt record already exists, skip" + _info "The TXT record already exists. Skipping." return 0 fi @@ -71,7 +71,7 @@ dns_aws_add() { _aws_tmpl_xml="UPSERT$fulldomainTXT300$_resource_record\"$txtvalue\"" if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then - _info "txt record updated success." + _info "TXT record updated successfully." return 0 fi @@ -99,7 +99,7 @@ dns_aws_rm() { _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" - _info "Geting existing records for $fulldomain" + _info "Getting existing records for $fulldomain" if ! aws_rest GET "2013-04-01$_domain_id/rrset" "name=$fulldomain&type=TXT"; then return 1 fi @@ -108,14 +108,14 @@ dns_aws_rm() { _resource_record="$(echo "$response" | sed 's//"/g' | tr '"' "\n" | grep "$fulldomain." | _egrep_o "" | sed "s///" | sed "s###")" _debug "_resource_record" "$_resource_record" else - _debug "no records exists, skip" + _debug "no records exist, skip" return 0 fi _aws_tmpl_xml="DELETE$_resource_record$fulldomain.TXT300" if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then - _info "txt record deleted success." + _info "TXT record deleted successfully." return 0 fi @@ -163,7 +163,7 @@ _get_root() { _domain=$h return 0 fi - _err "Can not find domain id: $h" + _err "Can't find domain with id: $h" return 1 fi fi diff --git a/dnsapi/dns_conoha.sh b/dnsapi/dns_conoha.sh new file mode 100755 index 0000000..d3bee13 --- /dev/null +++ b/dnsapi/dns_conoha.sh @@ -0,0 +1,253 @@ +#!/usr/bin/env sh + +CONOHA_DNS_EP_PREFIX_REGEXP="https://dns-service\." + +######## Public functions ##################### + +#Usage: dns_conoha_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_conoha_add() { + fulldomain=$1 + txtvalue=$2 + _info "Using conoha" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + _debug "Check uesrname and password" + CONOHA_Username="${CONOHA_Username:-$(_readaccountconf_mutable CONOHA_Username)}" + CONOHA_Password="${CONOHA_Password:-$(_readaccountconf_mutable CONOHA_Password)}" + CONOHA_TenantId="${CONOHA_TenantId:-$(_readaccountconf_mutable CONOHA_TenantId)}" + CONOHA_IdentityServiceApi="${CONOHA_IdentityServiceApi:-$(_readaccountconf_mutable CONOHA_IdentityServiceApi)}" + if [ -z "$CONOHA_Username" ] || [ -z "$CONOHA_Password" ] || [ -z "$CONOHA_TenantId" ] || [ -z "$CONOHA_IdentityServiceApi" ]; then + CONOHA_Username="" + CONOHA_Password="" + CONOHA_TenantId="" + CONOHA_IdentityServiceApi="" + _err "You didn't specify a conoha api username and password yet." + _err "Please create the user and try again." + return 1 + fi + + _saveaccountconf_mutable CONOHA_Username "$CONOHA_Username" + _saveaccountconf_mutable CONOHA_Password "$CONOHA_Password" + _saveaccountconf_mutable CONOHA_TenantId "$CONOHA_TenantId" + _saveaccountconf_mutable CONOHA_IdentityServiceApi "$CONOHA_IdentityServiceApi" + + if token="$(_conoha_get_accesstoken "$CONOHA_IdentityServiceApi/tokens" "$CONOHA_Username" "$CONOHA_Password" "$CONOHA_TenantId")"; then + accesstoken="$(printf "%s" "$token" | sed -n 1p)" + CONOHA_Api="$(printf "%s" "$token" | sed -n 2p)" + else + return 1 + fi + + _debug "First detect the root zone" + if ! _get_root "$fulldomain" "$CONOHA_Api" "$accesstoken"; then + _err "invalid domain" + return 1 + fi + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _info "Adding record" + body="{\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"data\":\"$txtvalue\",\"ttl\":60}" + if _conoha_rest POST "$CONOHA_Api/v1/domains/$_domain_id/records" "$body" "$accesstoken"; then + if _contains "$response" '"data":"'"$txtvalue"'"'; then + _info "Added, OK" + return 0 + else + _err "Add txt record error." + return 1 + fi + fi + + _err "Add txt record error." + return 1 +} + +#Usage: fulldomain txtvalue +#Remove the txt record after validation. +dns_conoha_rm() { + fulldomain=$1 + txtvalue=$2 + _info "Using conoha" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + _debug "Check uesrname and password" + CONOHA_Username="${CONOHA_Username:-$(_readaccountconf_mutable CONOHA_Username)}" + CONOHA_Password="${CONOHA_Password:-$(_readaccountconf_mutable CONOHA_Password)}" + CONOHA_TenantId="${CONOHA_TenantId:-$(_readaccountconf_mutable CONOHA_TenantId)}" + CONOHA_IdentityServiceApi="${CONOHA_IdentityServiceApi:-$(_readaccountconf_mutable CONOHA_IdentityServiceApi)}" + if [ -z "$CONOHA_Username" ] || [ -z "$CONOHA_Password" ] || [ -z "$CONOHA_TenantId" ] || [ -z "$CONOHA_IdentityServiceApi" ]; then + CONOHA_Username="" + CONOHA_Password="" + CONOHA_TenantId="" + CONOHA_IdentityServiceApi="" + _err "You didn't specify a conoha api username and password yet." + _err "Please create the user and try again." + return 1 + fi + + _saveaccountconf_mutable CONOHA_Username "$CONOHA_Username" + _saveaccountconf_mutable CONOHA_Password "$CONOHA_Password" + _saveaccountconf_mutable CONOHA_TenantId "$CONOHA_TenantId" + _saveaccountconf_mutable CONOHA_IdentityServiceApi "$CONOHA_IdentityServiceApi" + + if token="$(_conoha_get_accesstoken "$CONOHA_IdentityServiceApi/tokens" "$CONOHA_Username" "$CONOHA_Password" "$CONOHA_TenantId")"; then + accesstoken="$(printf "%s" "$token" | sed -n 1p)" + CONOHA_Api="$(printf "%s" "$token" | sed -n 2p)" + else + return 1 + fi + + _debug "First detect the root zone" + if ! _get_root "$fulldomain" "$CONOHA_Api" "$accesstoken"; then + _err "invalid domain" + return 1 + fi + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _debug "Getting txt records" + if ! _conoha_rest GET "$CONOHA_Api/v1/domains/$_domain_id/records" "" "$accesstoken"; then + _err "Error" + return 1 + fi + + record_id=$(printf "%s" "$response" | _egrep_o '{[^}]*}' \ + | grep '"type":"TXT"' | grep "\"data\":\"$txtvalue\"" | _egrep_o "\"id\":\"[^\"]*\"" \ + | _head_n 1 | cut -d : -f 2 | tr -d \") + if [ -z "$record_id" ]; then + _err "Can not get record id to remove." + return 1 + fi + _debug record_id "$record_id" + + _info "Removing the txt record" + if ! _conoha_rest DELETE "$CONOHA_Api/v1/domains/$_domain_id/records/$record_id" "" "$accesstoken"; then + _err "Delete record error." + return 1 + fi + + return 0 +} + +#################### Private functions below ################################## + +_conoha_rest() { + m="$1" + ep="$2" + data="$3" + accesstoken="$4" + + export _H1="Accept: application/json" + export _H2="Content-Type: application/json" + if [ -n "$accesstoken" ]; then + export _H3="X-Auth-Token: $accesstoken" + fi + + _debug "$ep" + if [ "$m" != "GET" ]; then + _secure_debug2 data "$data" + response="$(_post "$data" "$ep" "" "$m")" + else + response="$(_get "$ep")" + fi + _ret="$?" + _secure_debug2 response "$response" + if [ "$_ret" != "0" ]; then + _err "error $ep" + return 1 + fi + + response="$(printf "%s" "$response" | _normalizeJson)" + return 0 +} + +_conoha_get_accesstoken() { + ep="$1" + username="$2" + password="$3" + tenantId="$4" + + accesstoken="$(_readaccountconf_mutable conoha_accesstoken)" + expires="$(_readaccountconf_mutable conoha_tokenvalidto)" + CONOHA_Api="$(_readaccountconf_mutable conoha_dns_ep)" + + # can we reuse the access token? + if [ -n "$accesstoken" ] && [ -n "$expires" ] && [ -n "$CONOHA_Api" ]; then + utc_date="$(_utc_date | sed "s/ /T/")" + if expr "$utc_date" "<" "$expires" >/dev/null; then + # access token is still valid - reuse it + _debug "reusing access token" + printf "%s\n%s\n" "$accesstoken" "$CONOHA_Api" + return 0 + else + _debug "access token expired" + fi + fi + _debug "getting new access token" + + body="$(printf '{"auth":{"passwordCredentials":{"username":"%s","password":"%s"},"tenantId":"%s"}}' "$username" "$password" "$tenantId")" + if ! _conoha_rest POST "$ep" "$body" ""; then + _err error "$response" + return 1 + fi + accesstoken=$(printf "%s" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") + expires=$(printf "%s" "$response" | _egrep_o "\"expires\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2-4 | tr -d \" | tr -d Z) #expect UTC + if [ -z "$accesstoken" ] || [ -z "$expires" ]; then + _err "no acccess token received. Check your Conoha settings see $WIKI" + return 1 + fi + _saveaccountconf_mutable conoha_accesstoken "$accesstoken" + _saveaccountconf_mutable conoha_tokenvalidto "$expires" + + CONOHA_Api=$(printf "%s" "$response" | _egrep_o 'publicURL":"'"$CONOHA_DNS_EP_PREFIX_REGEXP"'[^"]*"' | _head_n 1 | cut -d : -f 2-3 | tr -d \") + if [ -z "$CONOHA_Api" ]; then + _err "failed to get conoha dns endpoint url" + return 1 + fi + _saveaccountconf_mutable conoha_dns_ep "$CONOHA_Api" + + printf "%s\n%s\n" "$accesstoken" "$CONOHA_Api" + return 0 +} + +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +# _domain_id=sdjkglgdfewsdfg +_get_root() { + domain="$1" + ep="$2" + accesstoken="$3" + 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 ! _conoha_rest GET "$ep/v1/domains?name=$h" "" "$accesstoken"; then + return 1 + fi + + if _contains "$response" "\"name\":\"$h\"" >/dev/null; then + _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \") + if [ "$_domain_id" ]; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) + _domain=$h + return 0 + fi + return 1 + fi + p=$i + i=$(_math "$i" + 1) + done + return 1 +} diff --git a/dnsapi/dns_dpi.sh b/dnsapi/dns_dpi.sh new file mode 100755 index 0000000..831150a --- /dev/null +++ b/dnsapi/dns_dpi.sh @@ -0,0 +1,161 @@ +#!/usr/bin/env sh + +# Dnspod.com Domain api +# +#DPI_Id="1234" +# +#DPI_Key="sADDsdasdgdsf" + +REST_API="https://api.dnspod.com" + +######## Public functions ##################### + +#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_dpi_add() { + fulldomain=$1 + txtvalue=$2 + + DPI_Id="${DPI_Id:-$(_readaccountconf_mutable DPI_Id)}" + DPI_Key="${DPI_Key:-$(_readaccountconf_mutable DPI_Key)}" + if [ -z "$DPI_Id" ] || [ -z "$DPI_Key" ]; then + DPI_Id="" + DPI_Key="" + _err "You don't specify dnspod api key and key id yet." + _err "Please create you key and try again." + return 1 + fi + + #save the api key and email to the account conf file. + _saveaccountconf_mutable DPI_Id "$DPI_Id" + _saveaccountconf_mutable DPI_Key "$DPI_Key" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + add_record "$_domain" "$_sub_domain" "$txtvalue" + +} + +#fulldomain txtvalue +dns_dpi_rm() { + fulldomain=$1 + txtvalue=$2 + + DPI_Id="${DPI_Id:-$(_readaccountconf_mutable DPI_Id)}" + DPI_Key="${DPI_Key:-$(_readaccountconf_mutable DPI_Key)}" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + if ! _rest POST "Record.List" "user_token=$DPI_Id,$DPI_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain"; then + _err "Record.Lis error." + return 1 + fi + + if _contains "$response" 'No records'; then + _info "Don't need to remove." + return 0 + fi + + record_id=$(echo "$response" | _egrep_o '{[^{]*"value":"'"$txtvalue"'"' | cut -d , -f 1 | cut -d : -f 2 | tr -d \") + _debug record_id "$record_id" + if [ -z "$record_id" ]; then + _err "Can not get record id." + return 1 + fi + + if ! _rest POST "Record.Remove" "user_token=$DPI_Id,$DPI_Key&format=json&domain_id=$_domain_id&record_id=$record_id"; then + _err "Record.Remove error." + return 1 + fi + + _contains "$response" "Action completed successful" + +} + +#add the txt record. +#usage: root sub txtvalue +add_record() { + root=$1 + sub=$2 + txtvalue=$3 + fulldomain="$sub.$root" + + _info "Adding record" + + if ! _rest POST "Record.Create" "user_token=$DPI_Id,$DPI_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain&record_type=TXT&value=$txtvalue&record_line=default"; then + return 1 + fi + + _contains "$response" "Action completed successful" || _contains "$response" "Domain record already exists" +} + +#################### Private functions below ################################## +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +# _domain_id=sdjkglgdfewsdfg +_get_root() { + domain=$1 + i=2 + p=1 + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + if [ -z "$h" ]; then + #not valid + return 1 + fi + + if ! _rest POST "Domain.Info" "user_token=$DPI_Id,$DPI_Key&format=json&domain=$h"; then + return 1 + fi + + if _contains "$response" "Action completed successful"; then + _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \") + _debug _domain_id "$_domain_id" + if [ "$_domain_id" ]; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) + _debug _sub_domain "$_sub_domain" + _domain="$h" + _debug _domain "$_domain" + return 0 + fi + return 1 + fi + p="$i" + i=$(_math "$i" + 1) + done + return 1 +} + +#Usage: method URI data +_rest() { + m="$1" + ep="$2" + data="$3" + _debug "$ep" + url="$REST_API/$ep" + + _debug url "$url" + + if [ "$m" = "GET" ]; then + response="$(_get "$url" | tr -d '\r')" + else + _debug2 data "$data" + response="$(_post "$data" "$url" | tr -d '\r')" + fi + + if [ "$?" != "0" ]; then + _err "error $ep" + return 1 + fi + _debug2 response "$response" + return 0 +} diff --git a/dnsapi/dns_gcloud.sh b/dnsapi/dns_gcloud.sh new file mode 100755 index 0000000..99fbf41 --- /dev/null +++ b/dnsapi/dns_gcloud.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env sh + +# Author: Janos Lenart + +######## Public functions ##################### + +# Usage: dns_gcloud_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_gcloud_add() { + fulldomain=$1 + txtvalue=$2 + _info "Using gcloud" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + _dns_gcloud_find_zone || return $? + + # Add an extra RR + _dns_gcloud_start_tr || return $? + _dns_gcloud_get_rrdatas || return $? + echo "$rrdatas" | _dns_gcloud_remove_rrs || return $? + printf "%s\n%s\n" "$rrdatas" "\"$txtvalue\"" | grep -v '^$' | _dns_gcloud_add_rrs || return $? + _dns_gcloud_execute_tr || return $? + + _info "$fulldomain record added" +} + +# Usage: dns_gcloud_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +# Remove the txt record after validation. +dns_gcloud_rm() { + fulldomain=$1 + txtvalue=$2 + _info "Using gcloud" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + _dns_gcloud_find_zone || return $? + + # Remove one RR + _dns_gcloud_start_tr || return $? + _dns_gcloud_get_rrdatas || return $? + echo "$rrdatas" | _dns_gcloud_remove_rrs || return $? + echo "$rrdatas" | grep -F -v "\"$txtvalue\"" | _dns_gcloud_add_rrs || return $? + _dns_gcloud_execute_tr || return $? + + _info "$fulldomain record added" +} + +#################### Private functions below ################################## + +_dns_gcloud_start_tr() { + if ! trd=$(mktemp -d); then + _err "_dns_gcloud_start_tr: failed to create temporary directory" + return 1 + fi + tr="$trd/tr.yaml" + _debug tr "$tr" + + if ! gcloud dns record-sets transaction start \ + --transaction-file="$tr" \ + --zone="$managedZone"; then + rm -r "$trd" + _err "_dns_gcloud_start_tr: failed to execute transaction" + return 1 + fi +} + +_dns_gcloud_execute_tr() { + if ! gcloud dns record-sets transaction execute \ + --transaction-file="$tr" \ + --zone="$managedZone"; then + _debug tr "$(cat "$tr")" + rm -r "$trd" + _err "_dns_gcloud_execute_tr: failed to execute transaction" + return 1 + fi + rm -r "$trd" + + for i in $(seq 1 120); do + if gcloud dns record-sets changes list \ + --zone="$managedZone" \ + --filter='status != done' \ + | grep -q '^.*'; then + _info "_dns_gcloud_execute_tr: waiting for transaction to be comitted ($i/120)..." + sleep 5 + else + return 0 + fi + done + + _err "_dns_gcloud_execute_tr: transaction is still pending after 10 minutes" + rm -r "$trd" + return 1 +} + +_dns_gcloud_remove_rrs() { + if ! xargs --no-run-if-empty gcloud dns record-sets transaction remove \ + --name="$fulldomain." \ + --ttl="$ttl" \ + --type=TXT \ + --zone="$managedZone" \ + --transaction-file="$tr"; then + _debug tr "$(cat "$tr")" + rm -r "$trd" + _err "_dns_gcloud_remove_rrs: failed to remove RRs" + return 1 + fi +} + +_dns_gcloud_add_rrs() { + ttl=60 + if ! xargs --no-run-if-empty gcloud dns record-sets transaction add \ + --name="$fulldomain." \ + --ttl="$ttl" \ + --type=TXT \ + --zone="$managedZone" \ + --transaction-file="$tr"; then + _debug tr "$(cat "$tr")" + rm -r "$trd" + _err "_dns_gcloud_add_rrs: failed to add RRs" + return 1 + fi +} + +_dns_gcloud_find_zone() { + # Prepare a filter that matches zones that are suiteable for this entry. + # For example, _acme-challenge.something.domain.com might need to go into something.domain.com or domain.com; + # this function finds the longest postfix that has a managed zone. + part="$fulldomain" + filter="dnsName=( " + while [ "$part" != "" ]; do + filter="$filter$part. " + part="$(echo "$part" | sed 's/[^.]*\.*//')" + done + filter="$filter)" + _debug filter "$filter" + + # List domains and find the longest match (in case of some levels of delegation) + if ! match=$(gcloud dns managed-zones list \ + --format="value(name, dnsName)" \ + --filter="$filter" \ + | while read -r dnsName name; do + printf "%s\t%s\t%s\n" "${#dnsName}" "$dnsName" "$name" + done \ + | sort -n -r | _head_n 1 | cut -f2,3 | grep '^.*'); then + _err "_dns_gcloud_find_zone: Can't find a matching managed zone! Perhaps wrong project or gcloud credentials?" + return 1 + fi + + dnsName=$(echo "$match" | cut -f2) + _debug dnsName "$dnsName" + managedZone=$(echo "$match" | cut -f1) + _debug managedZone "$managedZone" +} + +_dns_gcloud_get_rrdatas() { + if ! rrdatas=$(gcloud dns record-sets list \ + --zone="$managedZone" \ + --name="$fulldomain." \ + --type=TXT \ + --format="value(ttl,rrdatas)"); then + _err "_dns_gcloud_get_rrdatas: Failed to list record-sets" + rm -r "$trd" + return 1 + fi + ttl=$(echo "$rrdatas" | cut -f1) + rrdatas=$(echo "$rrdatas" | cut -f2 | sed 's/","/"\n"/g') +} diff --git a/dnsapi/dns_gdnsdk.sh b/dnsapi/dns_gdnsdk.sh new file mode 100755 index 0000000..7dc7894 --- /dev/null +++ b/dnsapi/dns_gdnsdk.sh @@ -0,0 +1,168 @@ +#!/usr/bin/env sh +#Author: Herman Sletteng +#Report Bugs here: https://github.com/loial/acme.sh +# +# +# Note, gratisdns requires a login first, so the script needs to handle +# temporary cookies. Since acme.sh _get/_post currently don't directly support +# cookies, I've defined wrapper functions _myget/_mypost to set the headers + +GDNSDK_API="https://admin.gratisdns.com" +######## Public functions ##################### +#Usage: dns_gdnsdk_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_gdnsdk_add() { + fulldomain=$1 + txtvalue=$2 + _info "Using gratisdns.dk" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + if ! _gratisdns_login; then + _err "Login failed!" + return 1 + fi + #finding domain zone + if ! _get_domain; then + _err "No matching root domain for $fulldomain found" + return 1 + fi + # adding entry + _info "Adding the entry" + _mypost "action=dns_primary_record_added_txt&user_domain=$_domain&name=$fulldomain&txtdata=$txtvalue&ttl=1" + if _successful_update; then return 0; fi + _err "Couldn't create entry!" + return 1 +} + +#Usage: fulldomain txtvalue +#Remove the txt record after validation. +dns_gdnsdk_rm() { + fulldomain=$1 + txtvalue=$2 + _info "Using gratisdns.dk" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + if ! _gratisdns_login; then + _err "Login failed!" + return 1 + fi + if ! _get_domain; then + _err "No matching root domain for $fulldomain found" + return 1 + fi + _findentry "$fulldomain" "$txtvalue" + if [ -z "$_id" ]; then + _info "Entry doesn't exist, nothing to delete" + return 0 + fi + _debug "Deleting record..." + _mypost "action=dns_primary_delete_txt&user_domain=$_domain&id=$_id" + # removing entry + + if _successful_update; then return 0; fi + _err "Couldn't delete entry!" + return 1 +} + +#################### Private functions below ################################## + +_checkcredentials() { + GDNSDK_Username="${GDNSDK_Username:-$(_readaccountconf_mutable GDNSDK_Username)}" + GDNSDK_Password="${GDNSDK_Password:-$(_readaccountconf_mutable GDNSDK_Password)}" + + if [ -z "$GDNSDK_Username" ] || [ -z "$GDNSDK_Password" ]; then + GDNSDK_Username="" + GDNSDK_Password="" + _err "You haven't specified gratisdns.dk username and password yet." + _err "Please add credentials and try again." + return 1 + fi + #save the credentials to the account conf file. + _saveaccountconf_mutable GDNSDK_Username "$GDNSDK_Username" + _saveaccountconf_mutable GDNSDK_Password "$GDNSDK_Password" + return 0 +} + +_checkcookie() { + GDNSDK_Cookie="${GDNSDK_Cookie:-$(_readaccountconf_mutable GDNSDK_Cookie)}" + if [ -z "$GDNSDK_Cookie" ]; then + _debug "No cached cookie found" + return 1 + fi + _myget "action=" + if (echo "$_result" | grep -q "logmeout"); then + _debug "Cached cookie still valid" + return 0 + fi + _debug "Cached cookie no longer valid" + GDNSDK_Cookie="" + _saveaccountconf_mutable GDNSDK_Cookie "$GDNSDK_Cookie" + return 1 +} + +_gratisdns_login() { + if ! _checkcredentials; then return 1; fi + + if _checkcookie; then + _debug "Already logged in" + return 0 + fi + _debug "Logging into GratisDNS with user $GDNSDK_Username" + + if ! _mypost "login=$GDNSDK_Username&password=$GDNSDK_Password&action=logmein"; then + _err "GratisDNS login failed for user $GDNSDK_Username bad RC from _post" + return 1 + fi + + GDNSDK_Cookie="$(grep -A 15 '302 Found' "$HTTP_HEADER" | _egrep_o 'Cookie: [^;]*' | _head_n 1 | cut -d ' ' -f2)" + + if [ -z "$GDNSDK_Cookie" ]; then + _err "GratisDNS login failed for user $GDNSDK_Username. Check $HTTP_HEADER file" + return 1 + fi + export GDNSDK_Cookie + _saveaccountconf_mutable GDNSDK_Cookie "$GDNSDK_Cookie" + return 0 +} + +_myget() { + #Adds cookie to request + export _H1="Cookie: $GDNSDK_Cookie" + _result=$(_get "$GDNSDK_API?$1") +} +_mypost() { + #Adds cookie to request + export _H1="Cookie: $GDNSDK_Cookie" + _result=$(_post "$1" "$GDNSDK_API") +} + +_get_domain() { + _myget 'action=dns_primarydns' + _domains=$(echo "$_result" | _egrep_o ' domain="[[:alnum:].-_]+' | sed 's/^.*"//') + if [ -z "$_domains" ]; then + _err "Primary domain list not found!" + return 1 + fi + for _domain in $_domains; do + if (_endswith "$fulldomain" "$_domain"); then + _debug "Root domain: $_domain" + return 0 + fi + done + return 1 +} + +_successful_update() { + if (echo "$_result" | grep -q 'table-success'); then return 0; fi + return 1 +} + +_findentry() { + #returns id of dns entry, if it exists + _myget "action=dns_primary_changeDNSsetup&user_domain=$_domain" + _id=$(echo "$_result" | _egrep_o "$1\s*$2[^?]*[^&]*&id=[^&]*" | sed 's/^.*=//') + if [ -n "$_id" ]; then + _debug "Entry found with _id=$_id" + return 0 + fi + return 1 +} diff --git a/dnsapi/dns_lexicon.sh b/dnsapi/dns_lexicon.sh index c09f16f..f6f5446 100755 --- a/dnsapi/dns_lexicon.sh +++ b/dnsapi/dns_lexicon.sh @@ -7,20 +7,13 @@ lexicon_cmd="lexicon" wiki="https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api" -######## Public functions ##################### - -#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_lexicon_add() { - fulldomain=$1 - txtvalue=$2 - - domain=$(printf "%s" "$fulldomain" | cut -d . -f 2-999) - +_lexicon_init() { if ! _exists "$lexicon_cmd"; then _err "Please install $lexicon_cmd first: $wiki" return 1 fi + PROVIDER="${PROVIDER:-$(_readdomainconf PROVIDER)}" if [ -z "$PROVIDER" ]; then PROVIDER="" _err "Please define env PROVIDER first: $wiki" @@ -33,46 +26,78 @@ dns_lexicon_add() { # e.g. busybox-ash does not know [:upper:] # shellcheck disable=SC2018,SC2019 Lx_name=$(echo LEXICON_"${PROVIDER}"_USERNAME | tr 'a-z' 'A-Z') + eval "$Lx_name=\${$Lx_name:-$(_readaccountconf_mutable "$Lx_name")}" Lx_name_v=$(eval echo \$"$Lx_name") _secure_debug "$Lx_name" "$Lx_name_v" if [ "$Lx_name_v" ]; then - _saveaccountconf "$Lx_name" "$Lx_name_v" + _saveaccountconf_mutable "$Lx_name" "$Lx_name_v" eval export "$Lx_name" fi # shellcheck disable=SC2018,SC2019 Lx_token=$(echo LEXICON_"${PROVIDER}"_TOKEN | tr 'a-z' 'A-Z') + eval "$Lx_token=\${$Lx_token:-$(_readaccountconf_mutable "$Lx_token")}" Lx_token_v=$(eval echo \$"$Lx_token") _secure_debug "$Lx_token" "$Lx_token_v" if [ "$Lx_token_v" ]; then - _saveaccountconf "$Lx_token" "$Lx_token_v" + _saveaccountconf_mutable "$Lx_token" "$Lx_token_v" eval export "$Lx_token" fi # shellcheck disable=SC2018,SC2019 Lx_password=$(echo LEXICON_"${PROVIDER}"_PASSWORD | tr 'a-z' 'A-Z') + eval "$Lx_password=\${$Lx_password:-$(_readaccountconf_mutable "$Lx_password")}" Lx_password_v=$(eval echo \$"$Lx_password") _secure_debug "$Lx_password" "$Lx_password_v" if [ "$Lx_password_v" ]; then - _saveaccountconf "$Lx_password" "$Lx_password_v" + _saveaccountconf_mutable "$Lx_password" "$Lx_password_v" eval export "$Lx_password" fi # shellcheck disable=SC2018,SC2019 Lx_domaintoken=$(echo LEXICON_"${PROVIDER}"_DOMAINTOKEN | tr 'a-z' 'A-Z') + eval "$Lx_domaintoken=\${$Lx_domaintoken:-$(_readaccountconf_mutable "$Lx_domaintoken")}" Lx_domaintoken_v=$(eval echo \$"$Lx_domaintoken") _secure_debug "$Lx_domaintoken" "$Lx_domaintoken_v" if [ "$Lx_domaintoken_v" ]; then + _saveaccountconf_mutable "$Lx_domaintoken" "$Lx_domaintoken_v" eval export "$Lx_domaintoken" - _saveaccountconf "$Lx_domaintoken" "$Lx_domaintoken_v" fi +} + +######## Public functions ##################### + +#Usage: dns_lexicon_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_lexicon_add() { + fulldomain=$1 + txtvalue=$2 + + if ! _lexicon_init; then + return 1 + fi + + domain=$(printf "%s" "$fulldomain" | cut -d . -f 2-999) + + _secure_debug LEXICON_OPTS "$LEXICON_OPTS" + _savedomainconf LEXICON_OPTS "$LEXICON_OPTS" - $lexicon_cmd "$PROVIDER" create "${domain}" TXT --name="_acme-challenge.${domain}." --content="${txtvalue}" + # shellcheck disable=SC2086 + $lexicon_cmd "$PROVIDER" $LEXICON_OPTS create "${domain}" TXT --name="_acme-challenge.${domain}." --content="${txtvalue}" } -#fulldomain +#Usage: dns_lexicon_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_lexicon_rm() { fulldomain=$1 + txtvalue=$2 + + if ! _lexicon_init; then + return 1 + fi + + domain=$(printf "%s" "$fulldomain" | cut -d . -f 2-999) + + # shellcheck disable=SC2086 + $lexicon_cmd "$PROVIDER" $LEXICON_OPTS delete "${domain}" TXT --name="_acme-challenge.${domain}." --content="${txtvalue}" } diff --git a/dnsapi/dns_namecheap.sh b/dnsapi/dns_namecheap.sh new file mode 100755 index 0000000..7089c2d --- /dev/null +++ b/dnsapi/dns_namecheap.sh @@ -0,0 +1,308 @@ +#!/usr/bin/env sh + +# Namecheap API +# https://www.namecheap.com/support/api/intro.aspx +# +# Requires Namecheap API key set in NAMECHEAP_API_KEY, NAMECHEAP_SOURCEIP and NAMECHEAP_USERNAME set as environment variable +# Due to Namecheap's API limitation all the records of your domain will be read and re applied, make sure to have a backup of your records you could apply if any issue would arise. + +######## Public functions ##################### + +if [ "$STAGE" -eq 1 ]; then + NAMECHEAP_API="https://api.sandbox.namecheap.com/xml.response" +else + NAMECHEAP_API="https://api.namecheap.com/xml.response" +fi + +#Usage: dns_namecheap_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_namecheap_add() { + fulldomain=$1 + txtvalue=$2 + + if ! _namecheap_check_config; then + _err "$error" + return 1 + fi + + if ! _namecheap_set_publicip; then + return 1 + fi + + _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" + + _set_namecheap_TXT "$_domain" "$_sub_domain" "$txtvalue" +} + +#Usage: fulldomain txtvalue +#Remove the txt record after validation. +dns_namecheap_rm() { + fulldomain=$1 + txtvalue=$2 + + if ! _namecheap_set_publicip; then + return 1 + fi + + if ! _namecheap_check_config; then + _err "$error" + return 1 + fi + + _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" + + _del_namecheap_TXT "$_domain" "$_sub_domain" "$txtvalue" +} + +#################### Private functions below ################################## +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +_get_root() { + domain=$1 + + if ! _namecheap_post "namecheap.domains.getList"; then + _err "$error" + return 1 + fi + + 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 ! _contains "$response" "$h"; 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 +} + +_namecheap_set_publicip() { + + 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://ipconfig.co/ip) and export it as NAMECHEAP_SOURCEIP" + return 1 + else + _saveaccountconf 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}') + addr=$(echo "$NAMECHEAP_SOURCEIP" | _egrep_o '(http|https)://.*') + + _debug2 ip "$ip" + _debug2 addr "$addr" + + if [ -n "$ip" ]; then + _publicip="$ip" + elif [ -n "$addr" ]; then + _publicip=$(_get "$addr") + else + _err "No Source IP specified for Namecheap API." + _err "Use your public ip address or an url to retrieve it (e.g. https://ipconfig.co/ip) and export it as NAMECHEAP_SOURCEIP" + return 1 + fi + fi + + _debug publicip "$_publicip" + + return 0 +} + +_namecheap_post() { + command=$1 + data="ApiUser=${NAMECHEAP_USERNAME}&ApiKey=${NAMECHEAP_API_KEY}&ClientIp=${_publicip}&UserName=${NAMECHEAP_USERNAME}&Command=${command}" + + response="$(_post "$data" "$NAMECHEAP_API" "" "POST")" + _debug2 response "$response" + + if _contains "$response" "Status=\"ERROR\"" >/dev/null; then + error=$(echo "$response" | _egrep_o ">.*<\\/Error>" | cut -d '<' -f 1 | tr -d '>') + _err "error $error" + return 1 + fi + + return 0 +} + +_namecheap_parse_host() { + _host=$1 + _debug _host "$_host" + + _hostid=$(echo "$_host" | _egrep_o '\sHostId="[^"]*' | cut -d '"' -f 2) + _hostname=$(echo "$_host" | _egrep_o '\sName="[^"]*' | cut -d '"' -f 2) + _hosttype=$(echo "$_host" | _egrep_o '\sType="[^"]*' | cut -d '"' -f 2) + _hostaddress=$(echo "$_host" | _egrep_o '\sAddress="[^"]*' | cut -d '"' -f 2) + _hostmxpref=$(echo "$_host" | _egrep_o '\sMXPref="[^"]*' | cut -d '"' -f 2) + _hostttl=$(echo "$_host" | _egrep_o '\sTTL="[^"]*' | cut -d '"' -f 2) + + _debug hostid "$_hostid" + _debug hostname "$_hostname" + _debug hosttype "$_hosttype" + _debug hostaddress "$_hostaddress" + _debug hostmxpref "$_hostmxpref" + _debug hostttl "$_hostttl" +} + +_namecheap_check_config() { + + if [ -z "$NAMECHEAP_API_KEY" ]; then + _err "No API key specified for Namecheap API." + _err "Create your key and export it as NAMECHEAP_API_KEY" + return 1 + fi + + if [ -z "$NAMECHEAP_USERNAME" ]; then + _err "No username key specified for Namecheap API." + _err "Create your key and export it as NAMECHEAP_USERNAME" + return 1 + fi + + _saveaccountconf NAMECHEAP_API_KEY "$NAMECHEAP_API_KEY" + _saveaccountconf NAMECHEAP_USERNAME "$NAMECHEAP_USERNAME" + + return 0 +} + +_set_namecheap_TXT() { + subdomain=$2 + txt=$3 + tld=$(echo "$1" | cut -d '.' -f 2) + sld=$(echo "$1" | cut -d '.' -f 1) + request="namecheap.domains.dns.getHosts&SLD=$sld&TLD=$tld" + + if ! _namecheap_post "$request"; then + _err "$error" + return 1 + fi + + hosts=$(echo "$response" | _egrep_o ']*') + _debug hosts "$hosts" + + if [ -z "$hosts" ]; then + _error "Hosts not found" + return 1 + fi + + _namecheap_reset_hostList + + while read -r host; do + if _contains "$host" "]*') + _debug hosts "$hosts" + + if [ -z "$hosts" ]; then + _error "Hosts not found" + return 1 + fi + + _namecheap_reset_hostList + + found=0 + + while read -r host; do + if _contains "$host" "/dev/null; then - _info "Adding record" - - if _uno_rest POST "my/products/$h/dns/records" "{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":120}"; then - if _contains "$response" "\"status\": 200" >/dev/null; then - _info "Added, OK" - return 0 - else - _err "Add txt record error." - return 1 - fi - fi - _err "Add txt record error." - else - _info "Updating record" - record_line_number=$(echo "$response" | grep -n "$_sub_domain" | cut -d : -f 1) - record_line_number=$(_math "$record_line_number" - 1) - record_id=$(echo "$response" | _head_n "$record_line_number" | _tail_n 1 1 | _egrep_o "[0-9]{1,}") - _debug "record_id" "$record_id" - - _uno_rest PUT "my/products/$h/dns/records/$record_id" "{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":120}" + if _uno_rest POST "my/products/$h/dns/records" "{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":120}"; then if _contains "$response" "\"status\": 200" >/dev/null; then - _info "Updated, OK" + _info "Added, OK" return 0 + else + _err "Add txt record error." + return 1 fi - _err "Update error" - return 1 fi } @@ -122,23 +104,24 @@ dns_unoeuro_rm() { if ! _contains "$response" "$_sub_domain"; then _info "Don't need to remove." else - record_line_number=$(echo "$response" | grep -n "$_sub_domain" | cut -d : -f 1) - record_line_number=$(_math "$record_line_number" - 1) - record_id=$(echo "$response" | _head_n "$record_line_number" | _tail_n 1 1 | _egrep_o "[0-9]{1,}") - _debug "record_id" "$record_id" - - if [ -z "$record_id" ]; then - _err "Can not get record id to remove." - return 1 - fi + for record_line_number in $(echo "$response" | grep -n "$_sub_domain" | cut -d : -f 1); do + record_line_number=$(_math "$record_line_number" - 1) + _debug "record_line_number" "$record_line_number" + record_id=$(echo "$response" | _head_n "$record_line_number" | _tail_n 1 1 | _egrep_o "[0-9]{1,}") + _debug "record_id" "$record_id" + + if [ -z "$record_id" ]; then + _err "Can not get record id to remove." + return 1 + fi - if ! _uno_rest DELETE "my/products/$h/dns/records/$record_id"; then - _err "Delete record error." - return 1 - fi - _contains "$response" "\"status\": 200" + if ! _uno_rest DELETE "my/products/$h/dns/records/$record_id"; then + _err "Delete record error." + return 1 + fi + _contains "$response" "\"status\": 200" + done fi - } #################### Private functions below ##################################