mirror of
				https://github.com/hiskang/acme.sh
				synced 2025-10-31 02:17:18 +00:00 
			
		
		
		
	Merge branch 'dev' into dnsapi/dns_dnsever
This commit is contained in:
		
						commit
						3ecc797d8e
					
				
							
								
								
									
										30
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								README.md
									
									
									
									
									
								
							| @ -25,7 +25,7 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa) | |||||||
| 
 | 
 | ||||||
| # [中文说明](https://github.com/Neilpang/acme.sh/wiki/%E8%AF%B4%E6%98%8E) | # [中文说明](https://github.com/Neilpang/acme.sh/wiki/%E8%AF%B4%E6%98%8E) | ||||||
| 
 | 
 | ||||||
| # Who are using **acme.sh** | # Who: | ||||||
| - [FreeBSD.org](https://blog.crashed.org/letsencrypt-in-freebsd-org/) | - [FreeBSD.org](https://blog.crashed.org/letsencrypt-in-freebsd-org/) | ||||||
| - [ruby-china.org](https://ruby-china.org/topics/31983) | - [ruby-china.org](https://ruby-china.org/topics/31983) | ||||||
| - [Proxmox](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x_and_newer)) | - [Proxmox](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x_and_newer)) | ||||||
| @ -74,8 +74,9 @@ https://github.com/Neilpang/acmetest | |||||||
| - Webroot mode | - Webroot mode | ||||||
| - Standalone mode | - Standalone mode | ||||||
| - Apache mode | - Apache mode | ||||||
| - Nginx mode ( Beta ) | - Nginx mode | ||||||
| - DNS mode | - DNS mode | ||||||
|  | - [DNS alias mode](https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode) | ||||||
| - [Stateless mode](https://github.com/Neilpang/acme.sh/wiki/Stateless-Mode) | - [Stateless mode](https://github.com/Neilpang/acme.sh/wiki/Stateless-Mode) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -204,6 +205,8 @@ Install/copy the cert/key to the production Apache or Nginx path. | |||||||
| The cert will be renewed every **60** days by default (which is configurable). Once the cert is renewed, the Apache/Nginx service will be reloaded automatically by the command: `service apache2 force-reload` or `service nginx force-reload`. | The cert will be renewed every **60** days by default (which is configurable). Once the cert is renewed, the Apache/Nginx service will be reloaded automatically by the command: `service apache2 force-reload` or `service nginx force-reload`. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | **Please take care:  The reloadcmd is very important. The cert can be automatically renewed, but, without a correct 'reloadcmd' the cert may not be flushed to your server(like nginx or apache), then your website will not be able to show renewed cert in 60 days.** | ||||||
|  | 
 | ||||||
| # 4. Use Standalone server to issue cert | # 4. Use Standalone server to issue cert | ||||||
| 
 | 
 | ||||||
| **(requires you to be root/sudoer or have permission to listen on port 80 (TCP))** | **(requires you to be root/sudoer or have permission to listen on port 80 (TCP))** | ||||||
| @ -238,7 +241,7 @@ More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert | |||||||
| 
 | 
 | ||||||
| If you are running a web server, Apache or Nginx, it is recommended to use the `Webroot mode`. | If you are running a web server, Apache or Nginx, it is recommended to use the `Webroot mode`. | ||||||
| 
 | 
 | ||||||
| Particularly, if you are running an Apache server, you should use Apache mode instead. This mode doesn't write any files to your web root folder. | Particularly, if you are running an Apache server, you can use Apache mode instead. This mode doesn't write any files to your web root folder. | ||||||
| 
 | 
 | ||||||
| Just set string "apache" as the second argument and it will force use of apache plugin automatically. | Just set string "apache" as the second argument and it will force use of apache plugin automatically. | ||||||
| 
 | 
 | ||||||
| @ -246,6 +249,10 @@ Just set string "apache" as the second argument and it will force use of apache | |||||||
| acme.sh --issue --apache -d example.com -d www.example.com -d cp.example.com | acme.sh --issue --apache -d example.com -d www.example.com -d cp.example.com | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | **This apache mode is only to issue the cert, it will not change your apache config files.  | ||||||
|  | You will need to configure your website config files to use the cert by yourself. | ||||||
|  | We don't want to mess your apache server, don't worry.** | ||||||
|  | 
 | ||||||
| More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert | More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert | ||||||
| 
 | 
 | ||||||
| # 7. Use Nginx mode | # 7. Use Nginx mode | ||||||
| @ -266,6 +273,10 @@ So, the config is not changed. | |||||||
| acme.sh --issue --nginx -d example.com -d www.example.com -d cp.example.com | acme.sh --issue --nginx -d example.com -d www.example.com -d cp.example.com | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | **This nginx mode is only to issue the cert, it will not change your nginx config files.  | ||||||
|  | You will need to configure your website config files to use the cert by yourself. | ||||||
|  | We don't want to mess your nginx server, don't worry.** | ||||||
|  | 
 | ||||||
| More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert | More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert | ||||||
| 
 | 
 | ||||||
| # 8. Automatic DNS API integration | # 8. Automatic DNS API integration | ||||||
| @ -315,6 +326,7 @@ You don't have to do anything manually! | |||||||
| 1. Azure DNS | 1. Azure DNS | ||||||
| 1. selectel.com(selectel.ru) DNS API | 1. selectel.com(selectel.ru) DNS API | ||||||
| 1. zonomi.com DNS API | 1. zonomi.com DNS API | ||||||
|  | 1. DreamHost.com API | ||||||
| 1. DNSEver(https://www.dnsever.com) | 1. DNSEver(https://www.dnsever.com) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -322,11 +334,11 @@ You don't have to do anything manually! | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| And:  | And:  | ||||||
| 
 | 
 | ||||||
| 1. lexicon DNS API: https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api | **lexicon DNS API: https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api | ||||||
|    (DigitalOcean, DNSimple, DNSMadeEasy, DNSPark, EasyDNS, Namesilo, NS1, PointHQ, Rage4 and Vultr etc.) |    (DigitalOcean, DNSimple, DNSMadeEasy, DNSPark, EasyDNS, Namesilo, NS1, PointHQ, Rage4 and Vultr etc.)** | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| **More APIs coming soon...** | **More APIs coming soon...** | ||||||
| @ -337,7 +349,7 @@ For more details: [How to use DNS API](dnsapi) | |||||||
| 
 | 
 | ||||||
| # 9. Use DNS manual mode: | # 9. Use DNS manual mode: | ||||||
| 
 | 
 | ||||||
| If your dns provider doesn't support any api access, you will have to add the txt record by your hand. | If your dns provider doesn't support any api access, you can add the txt record by your hand. | ||||||
| 
 | 
 | ||||||
| ```bash | ```bash | ||||||
| acme.sh --issue --dns -d example.com -d www.example.com -d cp.example.com | acme.sh --issue --dns -d example.com -d www.example.com -d cp.example.com | ||||||
| @ -375,7 +387,7 @@ Ok, it's done. | |||||||
| 
 | 
 | ||||||
| And we support them too! | And we support them too! | ||||||
| 
 | 
 | ||||||
| Just set the `length` parameter with a prefix `ec-`. | Just set the `keylength` parameter with a prefix `ec-`. | ||||||
| 
 | 
 | ||||||
| For example: | For example: | ||||||
| 
 | 
 | ||||||
| @ -391,7 +403,7 @@ acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-256 | |||||||
| acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength ec-256 | acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength ec-256 | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| Please look at the last parameter above. | Please look at the `keylength` parameter above. | ||||||
| 
 | 
 | ||||||
| Valid values are: | Valid values are: | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										108
									
								
								acme.sh
									
									
									
									
									
								
							
							
						
						
									
										108
									
								
								acme.sh
									
									
									
									
									
								
							| @ -47,6 +47,7 @@ DEFAULT_DNS_SLEEP=120 | |||||||
| NO_VALUE="no" | NO_VALUE="no" | ||||||
| 
 | 
 | ||||||
| W_TLS="tls" | W_TLS="tls" | ||||||
|  | DNS_ALIAS_PREFIX="=" | ||||||
| 
 | 
 | ||||||
| MODE_STATELESS="stateless" | MODE_STATELESS="stateless" | ||||||
| 
 | 
 | ||||||
| @ -105,6 +106,8 @@ _PREPARE_LINK="https://github.com/Neilpang/acme.sh/wiki/Install-preparations" | |||||||
| 
 | 
 | ||||||
| _STATELESS_WIKI="https://github.com/Neilpang/acme.sh/wiki/Stateless-Mode" | _STATELESS_WIKI="https://github.com/Neilpang/acme.sh/wiki/Stateless-Mode" | ||||||
| 
 | 
 | ||||||
|  | _DNS_ALIAS_WIKI="https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode" | ||||||
|  | 
 | ||||||
| _DNS_MANUAL_ERR="The dns manual mode can not renew automatically, you must issue it again manually. You'd better use the other modes instead." | _DNS_MANUAL_ERR="The dns manual mode can not renew automatically, you must issue it again manually. You'd better use the other modes instead." | ||||||
| 
 | 
 | ||||||
| _DNS_MANUAL_WARN="It seems that you are using dns manual mode. please take care: $_DNS_MANUAL_ERR" | _DNS_MANUAL_WARN="It seems that you are using dns manual mode. please take care: $_DNS_MANUAL_ERR" | ||||||
| @ -1281,6 +1284,7 @@ _create_account_key() { | |||||||
|   else |   else | ||||||
|     #generate account key |     #generate account key | ||||||
|     _createkey "$length" "$ACCOUNT_KEY_PATH" |     _createkey "$length" "$ACCOUNT_KEY_PATH" | ||||||
|  |     chmod 600 "$ACCOUNT_KEY_PATH" | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| @ -1838,7 +1842,7 @@ _send_signed_request() { | |||||||
|     _body="$response" |     _body="$response" | ||||||
|     if [ "$needbase64" ]; then |     if [ "$needbase64" ]; then | ||||||
|       _body="$(echo "$_body" | _dbase64)" |       _body="$(echo "$_body" | _dbase64)" | ||||||
|       _debug2 _body "$_body" |       _debug3 _body "$_body" | ||||||
|     fi |     fi | ||||||
| 
 | 
 | ||||||
|     if _contains "$_body" "JWS has invalid anti-replay nonce"; then |     if _contains "$_body" "JWS has invalid anti-replay nonce"; then | ||||||
| @ -2006,9 +2010,19 @@ _startserver() { | |||||||
|     _NC="$_NC -6" |     _NC="$_NC -6" | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   _debug "_NC" "$_NC" |   if [ "$DEBUG" ] && [ "$DEBUG" -gt "1" ]; then | ||||||
|   #todo  listen address |     _NC="$_NC -d -d -v" | ||||||
|   $_NC TCP-LISTEN:$Le_HTTPPort,crlf,reuseaddr,fork SYSTEM:"sleep 0.5; echo HTTP/1.1 200 OK; echo ; echo  $content; echo;" & |   fi | ||||||
|  | 
 | ||||||
|  |   SOCAT_OPTIONS=TCP-LISTEN:$Le_HTTPPort,crlf,reuseaddr,fork | ||||||
|  | 
 | ||||||
|  |   #Adding bind to local-address | ||||||
|  |   if [ "$ncaddr" ]; then | ||||||
|  |     SOCAT_OPTIONS="$SOCAT_OPTIONS,bind=${ncaddr}" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _debug "_NC" "$_NC $SOCAT_OPTIONS" | ||||||
|  |   $_NC $SOCAT_OPTIONS SYSTEM:"sleep 1; echo HTTP/1.0 200 OK; echo ; echo  $content; echo;" & | ||||||
|   serverproc="$!" |   serverproc="$!" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -2848,8 +2862,9 @@ _clearupdns() { | |||||||
|     _debug "skip dns." |     _debug "skip dns." | ||||||
|     return |     return | ||||||
|   fi |   fi | ||||||
| 
 |   _info "Removing DNS records." | ||||||
|   ventries=$(echo "$vlist" | tr ',' ' ') |   ventries=$(echo "$vlist" | tr ',' ' ') | ||||||
|  |   _alias_index=1 | ||||||
|   for ventry in $ventries; do |   for ventry in $ventries; do | ||||||
|     d=$(echo "$ventry" | cut -d "$sep" -f 1) |     d=$(echo "$ventry" | cut -d "$sep" -f 1) | ||||||
|     keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2) |     keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2) | ||||||
| @ -2863,7 +2878,7 @@ _clearupdns() { | |||||||
|     fi |     fi | ||||||
| 
 | 
 | ||||||
|     if [ "$vtype" != "$VTYPE_DNS" ]; then |     if [ "$vtype" != "$VTYPE_DNS" ]; then | ||||||
|       _info "Skip $d for $vtype" |       _debug "Skip $d for $vtype" | ||||||
|       continue |       continue | ||||||
|     fi |     fi | ||||||
| 
 | 
 | ||||||
| @ -2891,7 +2906,19 @@ _clearupdns() { | |||||||
|       if _startswith "$_dns_root_d" "*."; then |       if _startswith "$_dns_root_d" "*."; then | ||||||
|         _dns_root_d="$(echo "$_dns_root_d" | sed 's/*.//')" |         _dns_root_d="$(echo "$_dns_root_d" | sed 's/*.//')" | ||||||
|       fi |       fi | ||||||
|  | 
 | ||||||
|  |       _d_alias="$(_getfield "$_challenge_alias" "$_alias_index")" | ||||||
|  |       _alias_index="$(_math "$_alias_index" + 1)" | ||||||
|  |       _debug "_d_alias" "$_d_alias" | ||||||
|  |       if [ "$_d_alias" ]; then | ||||||
|  |         if _startswith "$_d_alias" "$DNS_ALIAS_PREFIX"; then | ||||||
|  |           txtdomain="$(echo "$_d_alias" | sed "s/$DNS_ALIAS_PREFIX//")" | ||||||
|  |         else | ||||||
|  |           txtdomain="_acme-challenge.$_d_alias" | ||||||
|  |         fi | ||||||
|  |       else | ||||||
|         txtdomain="_acme-challenge.$_dns_root_d" |         txtdomain="_acme-challenge.$_dns_root_d" | ||||||
|  |       fi | ||||||
| 
 | 
 | ||||||
|       if ! $rmcommand "$txtdomain" "$txt"; then |       if ! $rmcommand "$txtdomain" "$txt"; then | ||||||
|         _err "Error removing txt for domain:$txtdomain" |         _err "Error removing txt for domain:$txtdomain" | ||||||
| @ -3373,7 +3400,7 @@ issue() { | |||||||
|   _post_hook="${11}" |   _post_hook="${11}" | ||||||
|   _renew_hook="${12}" |   _renew_hook="${12}" | ||||||
|   _local_addr="${13}" |   _local_addr="${13}" | ||||||
| 
 |   _challenge_alias="${14}" | ||||||
|   #remove these later. |   #remove these later. | ||||||
|   if [ "$_web_roots" = "dns-cf" ]; then |   if [ "$_web_roots" = "dns-cf" ]; then | ||||||
|     _web_roots="dns_cf" |     _web_roots="dns_cf" | ||||||
| @ -3426,6 +3453,11 @@ issue() { | |||||||
|   else |   else | ||||||
|     _cleardomainconf "Le_LocalAddress" |     _cleardomainconf "Le_LocalAddress" | ||||||
|   fi |   fi | ||||||
|  |   if [ "$_challenge_alias" ]; then | ||||||
|  |     _savedomainconf "Le_ChallengeAlias" "$_challenge_alias" | ||||||
|  |   else | ||||||
|  |     _cleardomainconf "Le_ChallengeAlias" | ||||||
|  |   fi | ||||||
| 
 | 
 | ||||||
|   Le_API="$ACME_DIRECTORY" |   Le_API="$ACME_DIRECTORY" | ||||||
|   _savedomainconf "Le_API" "$Le_API" |   _savedomainconf "Le_API" "$Le_API" | ||||||
| @ -3598,6 +3630,10 @@ $_authorizations_map" | |||||||
|       _debug entry "$entry" |       _debug entry "$entry" | ||||||
|       if [ -z "$entry" ]; then |       if [ -z "$entry" ]; then | ||||||
|         _err "Error, can not get domain token entry $d" |         _err "Error, can not get domain token entry $d" | ||||||
|  |         _supported_vtypes="$(echo "$response" | _egrep_o "\"challenges\":\[[^]]*]" | tr '{' "\n" | grep type | cut -d '"' -f 4 | tr "\n" ' ')" | ||||||
|  |         if [ "$_supported_vtypes" ]; then | ||||||
|  |           _err "The supported validation types are: $_supported_vtypes, but you specified: $vtype" | ||||||
|  |         fi | ||||||
|         _clearup |         _clearup | ||||||
|         _on_issue_err "$_post_hook" |         _on_issue_err "$_post_hook" | ||||||
|         return 1 |         return 1 | ||||||
| @ -3643,6 +3679,7 @@ $_authorizations_map" | |||||||
|     #add entry |     #add entry | ||||||
|     dnsadded="" |     dnsadded="" | ||||||
|     ventries=$(echo "$vlist" | tr "$dvsep" ' ') |     ventries=$(echo "$vlist" | tr "$dvsep" ' ') | ||||||
|  |     _alias_index=1 | ||||||
|     for ventry in $ventries; do |     for ventry in $ventries; do | ||||||
|       d=$(echo "$ventry" | cut -d "$sep" -f 1) |       d=$(echo "$ventry" | cut -d "$sep" -f 1) | ||||||
|       keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2) |       keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2) | ||||||
| @ -3660,7 +3697,18 @@ $_authorizations_map" | |||||||
|         if _startswith "$_dns_root_d" "*."; then |         if _startswith "$_dns_root_d" "*."; then | ||||||
|           _dns_root_d="$(echo "$_dns_root_d" | sed 's/*.//')" |           _dns_root_d="$(echo "$_dns_root_d" | sed 's/*.//')" | ||||||
|         fi |         fi | ||||||
|  |         _d_alias="$(_getfield "$_challenge_alias" "$_alias_index")" | ||||||
|  |         _alias_index="$(_math "$_alias_index" + 1)" | ||||||
|  |         _debug "_d_alias" "$_d_alias" | ||||||
|  |         if [ "$_d_alias" ]; then | ||||||
|  |           if _startswith "$_d_alias" "$DNS_ALIAS_PREFIX"; then | ||||||
|  |             txtdomain="$(echo "$_d_alias" | sed "s/$DNS_ALIAS_PREFIX//")" | ||||||
|  |           else | ||||||
|  |             txtdomain="_acme-challenge.$_d_alias" | ||||||
|  |           fi | ||||||
|  |         else | ||||||
|           txtdomain="_acme-challenge.$_dns_root_d" |           txtdomain="_acme-challenge.$_dns_root_d" | ||||||
|  |         fi | ||||||
|         _debug txtdomain "$txtdomain" |         _debug txtdomain "$txtdomain" | ||||||
|         txt="$(printf "%s" "$keyauthorization" | _digest "sha256" | _url_replace)" |         txt="$(printf "%s" "$keyauthorization" | _digest "sha256" | _url_replace)" | ||||||
|         _debug txt "$txt" |         _debug txt "$txt" | ||||||
| @ -4213,7 +4261,7 @@ renew() { | |||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   IS_RENEW="1" |   IS_RENEW="1" | ||||||
|   issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath" "$Le_PreHook" "$Le_PostHook" "$Le_RenewHook" "$Le_LocalAddress" |   issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath" "$Le_PreHook" "$Le_PostHook" "$Le_RenewHook" "$Le_LocalAddress" "$Le_ChallengeAlias" | ||||||
|   res="$?" |   res="$?" | ||||||
|   if [ "$res" != "0" ]; then |   if [ "$res" != "0" ]; then | ||||||
|     return "$res" |     return "$res" | ||||||
| @ -4277,6 +4325,17 @@ signcsr() { | |||||||
|     return 1 |     return 1 | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|  |   _real_cert="$3" | ||||||
|  |   _real_key="$4" | ||||||
|  |   _real_ca="$5" | ||||||
|  |   _reload_cmd="$6" | ||||||
|  |   _real_fullchain="$7" | ||||||
|  |   _pre_hook="${8}" | ||||||
|  |   _post_hook="${9}" | ||||||
|  |   _renew_hook="${10}" | ||||||
|  |   _local_addr="${11}" | ||||||
|  |   _challenge_alias="${12}" | ||||||
|  | 
 | ||||||
|   _csrsubj=$(_readSubjectFromCSR "$_csrfile") |   _csrsubj=$(_readSubjectFromCSR "$_csrfile") | ||||||
|   if [ "$?" != "0" ]; then |   if [ "$?" != "0" ]; then | ||||||
|     _err "Can not read subject from csr: $_csrfile" |     _err "Can not read subject from csr: $_csrfile" | ||||||
| @ -4322,7 +4381,7 @@ signcsr() { | |||||||
|   _info "Copy csr to: $CSR_PATH" |   _info "Copy csr to: $CSR_PATH" | ||||||
|   cp "$_csrfile" "$CSR_PATH" |   cp "$_csrfile" "$CSR_PATH" | ||||||
| 
 | 
 | ||||||
|   issue "$_csrW" "$_csrsubj" "$_csrdomainlist" "$_csrkeylength" |   issue "$_csrW" "$_csrsubj" "$_csrdomainlist" "$_csrkeylength" "$_real_cert" "$_real_key" "$_real_ca" "$_reload_cmd" "$_real_fullchain" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_addr" "$_challenge_alias" | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -4545,7 +4604,7 @@ _installcert() { | |||||||
|       cat "$CERT_KEY_PATH" >"$_real_key" |       cat "$CERT_KEY_PATH" >"$_real_key" | ||||||
|     else |     else | ||||||
|       cat "$CERT_KEY_PATH" >"$_real_key" |       cat "$CERT_KEY_PATH" >"$_real_key" | ||||||
|       chmod 700 "$_real_key" |       chmod 600 "$_real_key" | ||||||
|     fi |     fi | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
| @ -5058,7 +5117,7 @@ _installalias() { | |||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| # nocron confighome | # nocron confighome noprofile | ||||||
| install() { | install() { | ||||||
| 
 | 
 | ||||||
|   if [ -z "$LE_WORKING_DIR" ]; then |   if [ -z "$LE_WORKING_DIR" ]; then | ||||||
| @ -5067,6 +5126,7 @@ install() { | |||||||
| 
 | 
 | ||||||
|   _nocron="$1" |   _nocron="$1" | ||||||
|   _c_home="$2" |   _c_home="$2" | ||||||
|  |   _noprofile="$3" | ||||||
|   if ! _initpath; then |   if ! _initpath; then | ||||||
|     _err "Install failed." |     _err "Install failed." | ||||||
|     return 1 |     return 1 | ||||||
| @ -5132,7 +5192,7 @@ install() { | |||||||
| 
 | 
 | ||||||
|   _info "Installed to $LE_WORKING_DIR/$PROJECT_ENTRY" |   _info "Installed to $LE_WORKING_DIR/$PROJECT_ENTRY" | ||||||
| 
 | 
 | ||||||
|   if [ "$IN_CRON" != "1" ]; then |   if [ "$IN_CRON" != "1" ] && [ -z "$_noprofile" ]; then | ||||||
|     _installalias "$_c_home" |     _installalias "$_c_home" | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
| @ -5296,6 +5356,8 @@ Commands: | |||||||
| 
 | 
 | ||||||
| Parameters: | Parameters: | ||||||
|   --domain, -d   domain.tld         Specifies a domain, used to issue, renew or revoke etc. |   --domain, -d   domain.tld         Specifies a domain, used to issue, renew or revoke etc. | ||||||
|  |   --challenge-alias domain.tld      The challenge domain alias for DNS alias mode: $_DNS_ALIAS_WIKI | ||||||
|  |   --domain-alias domain.tld         The domain alias for DNS alias mode: $_DNS_ALIAS_WIKI | ||||||
|   --force, -f                       Used to force to install or force to renew a cert immediately. |   --force, -f                       Used to force to install or force to renew a cert immediately. | ||||||
|   --staging, --test                 Use staging server, just for test. |   --staging, --test                 Use staging server, just for test. | ||||||
|   --debug                           Output debug info. |   --debug                           Output debug info. | ||||||
| @ -5358,10 +5420,11 @@ Parameters: | |||||||
|   " |   " | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| # nocron | # nocron noprofile | ||||||
| _installOnline() { | _installOnline() { | ||||||
|   _info "Installing from online archive." |   _info "Installing from online archive." | ||||||
|   _nocron="$1" |   _nocron="$1" | ||||||
|  |   _noprofile="$2" | ||||||
|   if [ ! "$BRANCH" ]; then |   if [ ! "$BRANCH" ]; then | ||||||
|     BRANCH="master" |     BRANCH="master" | ||||||
|   fi |   fi | ||||||
| @ -5382,7 +5445,7 @@ _installOnline() { | |||||||
| 
 | 
 | ||||||
|     cd "$PROJECT_NAME-$BRANCH" |     cd "$PROJECT_NAME-$BRANCH" | ||||||
|     chmod +x $PROJECT_ENTRY |     chmod +x $PROJECT_ENTRY | ||||||
|     if ./$PROJECT_ENTRY install "$_nocron"; then |     if ./$PROJECT_ENTRY install "$_nocron" "" "$_noprofile"; then | ||||||
|       _info "Install success!" |       _info "Install success!" | ||||||
|     fi |     fi | ||||||
| 
 | 
 | ||||||
| @ -5398,7 +5461,7 @@ upgrade() { | |||||||
|     _initpath |     _initpath | ||||||
|     export LE_WORKING_DIR |     export LE_WORKING_DIR | ||||||
|     cd "$LE_WORKING_DIR" |     cd "$LE_WORKING_DIR" | ||||||
|     _installOnline "nocron" |     _installOnline "nocron" "noprofile" | ||||||
|   ); then |   ); then | ||||||
|     _info "Upgrade success!" |     _info "Upgrade success!" | ||||||
|     exit 0 |     exit 0 | ||||||
| @ -5446,6 +5509,7 @@ _process() { | |||||||
|   _domain="" |   _domain="" | ||||||
|   _altdomains="$NO_VALUE" |   _altdomains="$NO_VALUE" | ||||||
|   _webroot="" |   _webroot="" | ||||||
|  |   _challenge_alias="" | ||||||
|   _keylength="" |   _keylength="" | ||||||
|   _accountkeylength="" |   _accountkeylength="" | ||||||
|   _cert_file="" |   _cert_file="" | ||||||
| @ -5635,6 +5699,16 @@ _process() { | |||||||
|         fi |         fi | ||||||
|         shift |         shift | ||||||
|         ;; |         ;; | ||||||
|  |       --challenge-alias) | ||||||
|  |         cvalue="$2" | ||||||
|  |         _challenge_alias="$_challenge_alias$cvalue," | ||||||
|  |         shift | ||||||
|  |         ;; | ||||||
|  |       --domain-alias) | ||||||
|  |         cvalue="$DNS_ALIAS_PREFIX$2" | ||||||
|  |         _challenge_alias="$_challenge_alias$cvalue," | ||||||
|  |         shift | ||||||
|  |         ;; | ||||||
|       --standalone) |       --standalone) | ||||||
|         wvalue="$NO_VALUE" |         wvalue="$NO_VALUE" | ||||||
|         if [ -z "$_webroot" ]; then |         if [ -z "$_webroot" ]; then | ||||||
| @ -5956,13 +6030,13 @@ _process() { | |||||||
|     uninstall) uninstall "$_nocron" ;; |     uninstall) uninstall "$_nocron" ;; | ||||||
|     upgrade) upgrade ;; |     upgrade) upgrade ;; | ||||||
|     issue) |     issue) | ||||||
|       issue "$_webroot" "$_domain" "$_altdomains" "$_keylength" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_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" "$_challenge_alias" | ||||||
|       ;; |       ;; | ||||||
|     deploy) |     deploy) | ||||||
|       deploy "$_domain" "$_deploy_hook" "$_ecc" |       deploy "$_domain" "$_deploy_hook" "$_ecc" | ||||||
|       ;; |       ;; | ||||||
|     signcsr) |     signcsr) | ||||||
|       signcsr "$_csr" "$_webroot" |       signcsr "$_csr" "$_webroot" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" "$_challenge_alias" | ||||||
|       ;; |       ;; | ||||||
|     showcsr) |     showcsr) | ||||||
|       showcsr "$_csr" "$_domain" |       showcsr "$_csr" "$_domain" | ||||||
|  | |||||||
							
								
								
									
										143
									
								
								deploy/README.md
									
									
									
									
									
								
							
							
						
						
									
										143
									
								
								deploy/README.md
									
									
									
									
									
								
							| @ -31,7 +31,146 @@ acme.sh --deploy -d ftp.example.com --deploy-hook kong | |||||||
| 
 | 
 | ||||||
| ## 3. Deploy the cert to remote server through SSH access | ## 3. Deploy the cert to remote server through SSH access | ||||||
| 
 | 
 | ||||||
| (TODO) | The ssh deploy plugin allows you to deploy certificates to a remote host | ||||||
|  | using SSH command to connect to the remote server.  The ssh plugin is invoked | ||||||
|  | with the following command... | ||||||
|  | 
 | ||||||
|  | ```sh | ||||||
|  | acme.sh --deploy -d example.com --deploy-hook ssh | ||||||
|  | ``` | ||||||
|  | Prior to running this for the first time you must tell the plugin where | ||||||
|  | and how to deploy the certificates.  This is done by exporting the following | ||||||
|  | environment variables.  This is not required for subsequent runs as the | ||||||
|  | values are stored by acme.sh in the domain configuration files. | ||||||
|  | 
 | ||||||
|  | Required... | ||||||
|  | ``` | ||||||
|  | export DEPLOY_SSH_USER=username | ||||||
|  | ``` | ||||||
|  | Optional... | ||||||
|  | ``` | ||||||
|  | export DEPLOY_SSH_CMD=custom ssh command | ||||||
|  | export DEPLOY_SSH_SERVER=url or ip address of remote host | ||||||
|  | export DEPLOY_SSH_KEYFILE=filename for private key | ||||||
|  | export DEPLOY_SSH_CERTFILE=filename for certificate file | ||||||
|  | export DEPLOY_SSH_CAFILE=filename for intermediate CA file | ||||||
|  | export DEPLOY_SSH_FULLCHAIN=filename for fullchain file | ||||||
|  | export DEPLOY_SSH_REMOTE_CMD=command to execute on remote host | ||||||
|  | export DEPLOY_SSH_BACKUP=yes or no | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | **DEPLOY_SSH_USER** | ||||||
|  | Username at the remote host that SSH will login with. Note that | ||||||
|  | SSH must be able to login to remote host without a password... SSH Keys | ||||||
|  | must have been exchanged with the remote host. Validate and test that you | ||||||
|  | can login to USER@URL from the host running acme.sh before using this script. | ||||||
|  | 
 | ||||||
|  | The USER@URL at the remote server must also have has permissions to write to | ||||||
|  | the target location of the certificate files and to execute any commands | ||||||
|  | (e.g. to stop/start services). | ||||||
|  | 
 | ||||||
|  | **DEPLOY_SSH_CMD** | ||||||
|  | You can customize the ssh command used to connect to the remote host. For example | ||||||
|  | if you need to connect to a specific port at the remote server you can set this | ||||||
|  | to, for example, "ssh -p 22" or to use `sshpass` to provide password inline | ||||||
|  | instead of exchanging ssh keys (this is not recommended, using keys is | ||||||
|  | more secure). | ||||||
|  | 
 | ||||||
|  | **DEPLOY_SSH_SERVER** | ||||||
|  | URL or IP Address of the remote server.  If not provided then the domain | ||||||
|  | name provided on the acme.sh --deploy command line is used. | ||||||
|  | 
 | ||||||
|  | **DEPLOY_SSH_KEYFILE** | ||||||
|  | Target filename for the private key issued by LetsEncrypt. | ||||||
|  | 
 | ||||||
|  | **DEPLOY_SSH_CERTFILE** | ||||||
|  | Target filename for the certificate issued by LetsEncrypt. | ||||||
|  | If this is the same as the previous filename (for keyfile) then it is | ||||||
|  | appended to the same file. | ||||||
|  | 
 | ||||||
|  | **DEPLOY_SSH_CAFILE** | ||||||
|  | Target filename for the CA intermediate certificate issued by LetsEncrypt. | ||||||
|  | If this is the same as a previous filename (for keyfile or certfile) then | ||||||
|  | it is appended to the same file. | ||||||
|  | 
 | ||||||
|  | **DEPLOY_SSH_FULLCHAIN** | ||||||
|  | Target filename for the fullchain certificate issued by LetsEncrypt. | ||||||
|  | If this is the same as a previous filename (for keyfile, certfile or | ||||||
|  | cafile) then it is appended to the same file. | ||||||
|  | 
 | ||||||
|  | **DEPLOY_SSH_REMOTE_CMD** | ||||||
|  | Command to execute on the remote server after copying any certificates.  This | ||||||
|  | could be any additional command required for example to stop and restart | ||||||
|  | the service. | ||||||
|  | 
 | ||||||
|  | **DEPLOY_SSH_BACKUP** | ||||||
|  | Before writing a certificate file to the remote server the existing | ||||||
|  | certificate will be copied to a backup directory on the remote server. | ||||||
|  | These are placed in a hidden directory in the home directory of the SSH | ||||||
|  | user | ||||||
|  | ```sh | ||||||
|  | ~/.acme_ssh_deploy/[domain name]-backup-[timestamp] | ||||||
|  | ``` | ||||||
|  | Any backups older than 180 days will be deleted when new certificates | ||||||
|  | are deployed.  This defaults to "yes" set to "no" to disable backup. | ||||||
|  | 
 | ||||||
|  | ###Examples using SSH deploy | ||||||
|  | The following example illustrates deploying certificates to a QNAP NAS | ||||||
|  | (tested with QTS version 4.2.3) | ||||||
|  | 
 | ||||||
|  | ```sh | ||||||
|  | export DEPLOY_SSH_USER="admin" | ||||||
|  | export DEPLOY_SSH_KEYFILE="/etc/stunnel/stunnel.pem" | ||||||
|  | export DEPLOY_SSH_CERTFILE="/etc/stunnel/stunnel.pem" | ||||||
|  | export DEPLOY_SSH_CAFILE="/etc/stunnel/uca.pem" | ||||||
|  | export DEPLOY_SSH_REMOTE_CMD="/etc/init.d/stunnel.sh restart" | ||||||
|  | 
 | ||||||
|  | acme.sh --deploy -d qnap.example.com --deploy-hook ssh | ||||||
|  | ``` | ||||||
|  | Note how in this example both the private key and certificate point to | ||||||
|  | the same file.  This will result in the certificate being appended | ||||||
|  | to the same file as the private key... a common requirement of several | ||||||
|  | services. | ||||||
|  | 
 | ||||||
|  | The next example illustrates deploying certificates to a Unifi | ||||||
|  | Controller (tested with version 5.4.11). | ||||||
|  | 
 | ||||||
|  | ```sh | ||||||
|  | export DEPLOY_SSH_USER="root" | ||||||
|  | export DEPLOY_SSH_KEYFILE="/var/lib/unifi/unifi.example.com.key" | ||||||
|  | export DEPLOY_SSH_FULLCHAIN="/var/lib/unifi/unifi.example.com.cer" | ||||||
|  | export DEPLOY_SSH_REMOTE_CMD="openssl pkcs12 -export \ | ||||||
|  |    -inkey /var/lib/unifi/unifi.example.com.key \ | ||||||
|  |    -in /var/lib/unifi/unifi.example.com.cer \ | ||||||
|  |    -out /var/lib/unifi/unifi.example.com.p12 \ | ||||||
|  |    -name ubnt -password pass:temppass \ | ||||||
|  |  && keytool -importkeystore -deststorepass aircontrolenterprise \ | ||||||
|  |    -destkeypass aircontrolenterprise \ | ||||||
|  |    -destkeystore /var/lib/unifi/keystore \ | ||||||
|  |    -srckeystore /var/lib/unifi/unifi.example.com.p12 \ | ||||||
|  |    -srcstoretype PKCS12 -srcstorepass temppass -alias ubnt -noprompt \ | ||||||
|  |  && service unifi restart" | ||||||
|  | 
 | ||||||
|  | acme.sh --deploy -d unifi.example.com --deploy-hook ssh | ||||||
|  | ``` | ||||||
|  | In this example we execute several commands on the remote host | ||||||
|  | after the certificate files have been copied... to generate a pkcs12 file | ||||||
|  | compatible with Unifi, to import it into the Unifi keystore and then finally | ||||||
|  | to restart the service. | ||||||
|  | 
 | ||||||
|  | Note also that once the certificate is imported | ||||||
|  | into the keystore the individual certificate files are no longer | ||||||
|  | required. We could if we desired delete those files immediately. If we | ||||||
|  | do that then we should disable backup at the remote host (as there are | ||||||
|  | no files to backup -- they were erased during deployment). For example... | ||||||
|  | ```sh | ||||||
|  | export DEPLOY_SSH_BACKUP=no | ||||||
|  | # modify the end of the remote command... | ||||||
|  | && rm /var/lib/unifi/unifi.example.com.key \ | ||||||
|  |       /var/lib/unifi/unifi.example.com.cer \ | ||||||
|  |       /var/lib/unifi/unifi.example.com.p12 \ | ||||||
|  | && service unifi restart | ||||||
|  | ``` | ||||||
| 
 | 
 | ||||||
| ## 4. Deploy the cert to local vsftpd server | ## 4. Deploy the cert to local vsftpd server | ||||||
| 
 | 
 | ||||||
| @ -94,7 +233,7 @@ DEPLOY_CPANEL_USER is required only if you run the script as root and it should | |||||||
| export DEPLOY_CPANEL_USER=username | export DEPLOY_CPANEL_USER=username | ||||||
| acme.sh  --deploy  -d example.com  --deploy-hook cpanel_uapi | acme.sh  --deploy  -d example.com  --deploy-hook cpanel_uapi | ||||||
| ``` | ``` | ||||||
| Please note, that the cpanel_uapi hook will deploy only the first domain when your certificate will automatically renew. Therefore you should issue a separete certificate for each domain.  | Please note, that the cpanel_uapi hook will deploy only the first domain when your certificate will automatically renew. Therefore you should issue a separate certificate for each domain.  | ||||||
| 
 | 
 | ||||||
| ## 8. Deploy the cert to your FRITZ!Box router | ## 8. Deploy the cert to your FRITZ!Box router | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										205
									
								
								deploy/ssh.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								deploy/ssh.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,205 @@ | |||||||
|  | #!/usr/bin/env sh | ||||||
|  | 
 | ||||||
|  | # Script to deploy certificates to remote server by SSH | ||||||
|  | # Note that SSH must be able to login to remote host without a password... | ||||||
|  | # SSH Keys must have been exchanged with the remote host.  Validate and | ||||||
|  | # test that you can login to USER@SERVER from the host running acme.sh before | ||||||
|  | # using this script. | ||||||
|  | # | ||||||
|  | # The following variables exported from environment will be used. | ||||||
|  | # If not set then values previously saved in domain.conf file are used. | ||||||
|  | # | ||||||
|  | # Only a username is required.  All others are optional. | ||||||
|  | # | ||||||
|  | # 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 | ||||||
|  | # export DEPLOY_SSH_KEYFILE="/etc/stunnel/stunnel.pem" | ||||||
|  | # export DEPLOY_SSH_CERTFILE="/etc/stunnel/stunnel.pem" | ||||||
|  | # export DEPLOY_SSH_CAFILE="/etc/stunnel/uca.pem" | ||||||
|  | # export DEPLOY_SSH_FULLCHAIN="" | ||||||
|  | # export DEPLOY_SSH_REMOTE_CMD="/etc/init.d/stunnel.sh restart" | ||||||
|  | # export DEPLOY_SSH_BACKUP=""  # yes or no, default to yes | ||||||
|  | # | ||||||
|  | ########  Public functions ##################### | ||||||
|  | 
 | ||||||
|  | #domain keyfile certfile cafile fullchain | ||||||
|  | ssh_deploy() { | ||||||
|  |   _cdomain="$1" | ||||||
|  |   _ckey="$2" | ||||||
|  |   _ccert="$3" | ||||||
|  |   _cca="$4" | ||||||
|  |   _cfullchain="$5" | ||||||
|  |   _cmdstr="" | ||||||
|  |   _homedir='~' | ||||||
|  |   _backupprefix="$_homedir/.acme_ssh_deploy/$_cdomain-backup" | ||||||
|  |   _backupdir="$_backupprefix-$(_utc_date | tr ' ' '-')" | ||||||
|  | 
 | ||||||
|  |   if [ -f "$DOMAIN_CONF" ]; then | ||||||
|  |     # shellcheck disable=SC1090 | ||||||
|  |     . "$DOMAIN_CONF" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _debug _cdomain "$_cdomain" | ||||||
|  |   _debug _ckey "$_ckey" | ||||||
|  |   _debug _ccert "$_ccert" | ||||||
|  |   _debug _cca "$_cca" | ||||||
|  |   _debug _cfullchain "$_cfullchain" | ||||||
|  | 
 | ||||||
|  |   # USER is required to login by SSH to remote host. | ||||||
|  |   if [ -z "$DEPLOY_SSH_USER" ]; then | ||||||
|  |     if [ -z "$Le_Deploy_ssh_user" ]; then | ||||||
|  |       _err "DEPLOY_SSH_USER not defined." | ||||||
|  |       return 1 | ||||||
|  |     fi | ||||||
|  |   else | ||||||
|  |     Le_Deploy_ssh_user="$DEPLOY_SSH_USER" | ||||||
|  |     _savedomainconf Le_Deploy_ssh_user "$Le_Deploy_ssh_user" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   # SERVER is optional. If not provided then use _cdomain | ||||||
|  |   if [ -n "$DEPLOY_SSH_SERVER" ]; then | ||||||
|  |     Le_Deploy_ssh_server="$DEPLOY_SSH_SERVER" | ||||||
|  |     _savedomainconf Le_Deploy_ssh_server "$Le_Deploy_ssh_server" | ||||||
|  |   elif [ -z "$Le_Deploy_ssh_server" ]; then | ||||||
|  |     Le_Deploy_ssh_server="$_cdomain" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   # CMD is optional. If not provided then use ssh | ||||||
|  |   if [ -n "$DEPLOY_SSH_CMD" ]; then | ||||||
|  |     Le_Deploy_ssh_cmd="$DEPLOY_SSH_CMD" | ||||||
|  |     _savedomainconf Le_Deploy_ssh_cmd "$Le_Deploy_ssh_cmd" | ||||||
|  |   elif [ -z "$Le_Deploy_ssh_cmd" ]; then | ||||||
|  |     Le_Deploy_ssh_cmd="ssh" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   # BACKUP is optional. If not provided then default to yes | ||||||
|  |   if [ "$DEPLOY_SSH_BACKUP" = "no" ]; then | ||||||
|  |     Le_Deploy_ssh_backup="no" | ||||||
|  |   elif [ -z "$Le_Deploy_ssh_backup" ]; then | ||||||
|  |     Le_Deploy_ssh_backup="yes" | ||||||
|  |   fi | ||||||
|  |   _savedomainconf Le_Deploy_ssh_backup "$Le_Deploy_ssh_backup" | ||||||
|  | 
 | ||||||
|  |   _info "Deploy certificates to remote server $Le_Deploy_ssh_user@$Le_Deploy_ssh_server" | ||||||
|  | 
 | ||||||
|  |   # KEYFILE is optional. | ||||||
|  |   # If provided then private key will be copied to provided filename. | ||||||
|  |   if [ -n "$DEPLOY_SSH_KEYFILE" ]; then | ||||||
|  |     Le_Deploy_ssh_keyfile="$DEPLOY_SSH_KEYFILE" | ||||||
|  |     _savedomainconf Le_Deploy_ssh_keyfile "$Le_Deploy_ssh_keyfile" | ||||||
|  |   fi | ||||||
|  |   if [ -n "$Le_Deploy_ssh_keyfile" ]; then | ||||||
|  |     if [ "$Le_Deploy_ssh_backup" = "yes" ]; then | ||||||
|  |       # backup file we are about to overwrite. | ||||||
|  |       _cmdstr="$_cmdstr cp $Le_Deploy_ssh_keyfile $_backupdir >/dev/null;" | ||||||
|  |     fi | ||||||
|  |     # copy new certificate into file. | ||||||
|  |     _cmdstr="$_cmdstr echo \"$(cat "$_ckey")\" > $Le_Deploy_ssh_keyfile;" | ||||||
|  |     _info "will copy private key to remote file $Le_Deploy_ssh_keyfile" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   # CERTFILE is optional. | ||||||
|  |   # If provided then private key 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" | ||||||
|  |   fi | ||||||
|  |   if [ -n "$Le_Deploy_ssh_certfile" ]; then | ||||||
|  |     _pipe=">" | ||||||
|  |     if [ "$Le_Deploy_ssh_certfile" = "$Le_Deploy_ssh_keyfile" ]; then | ||||||
|  |       # if filename is same as previous file then append. | ||||||
|  |       _pipe=">>" | ||||||
|  |     elif [ "$Le_Deploy_ssh_backup" = "yes" ]; then | ||||||
|  |       # backup file we are about to overwrite. | ||||||
|  |       _cmdstr="$_cmdstr cp $Le_Deploy_ssh_certfile $_backupdir >/dev/null;" | ||||||
|  |     fi | ||||||
|  |     # copy new certificate into file. | ||||||
|  |     _cmdstr="$_cmdstr echo \"$(cat "$_ccert")\" $_pipe $Le_Deploy_ssh_certfile;" | ||||||
|  |     _info "will copy certificate to remote file $Le_Deploy_ssh_certfile" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   # CAFILE is optional. | ||||||
|  |   # If provided then CA intermediate certificate will be copied or appended to provided filename. | ||||||
|  |   if [ -n "$DEPLOY_SSH_CAFILE" ]; then | ||||||
|  |     Le_Deploy_ssh_cafile="$DEPLOY_SSH_CAFILE" | ||||||
|  |     _savedomainconf Le_Deploy_ssh_cafile "$Le_Deploy_ssh_cafile" | ||||||
|  |   fi | ||||||
|  |   if [ -n "$Le_Deploy_ssh_cafile" ]; then | ||||||
|  |     _pipe=">" | ||||||
|  |     if [ "$Le_Deploy_ssh_cafile" = "$Le_Deploy_ssh_keyfile" ] \ | ||||||
|  |       || [ "$Le_Deploy_ssh_cafile" = "$Le_Deploy_ssh_certfile" ]; then | ||||||
|  |       # if filename is same as previous file then append. | ||||||
|  |       _pipe=">>" | ||||||
|  |     elif [ "$Le_Deploy_ssh_backup" = "yes" ]; then | ||||||
|  |       # backup file we are about to overwrite. | ||||||
|  |       _cmdstr="$_cmdstr cp $Le_Deploy_ssh_cafile $_backupdir >/dev/null;" | ||||||
|  |     fi | ||||||
|  |     # copy new certificate into file. | ||||||
|  |     _cmdstr="$_cmdstr echo \"$(cat "$_cca")\" $_pipe $Le_Deploy_ssh_cafile;" | ||||||
|  |     _info "will copy CA file to remote file $Le_Deploy_ssh_cafile" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   # FULLCHAIN is optional. | ||||||
|  |   # If provided then fullchain certificate will be copied or appended to provided filename. | ||||||
|  |   if [ -n "$DEPLOY_SSH_FULLCHAIN" ]; then | ||||||
|  |     Le_Deploy_ssh_fullchain="$DEPLOY_SSH_FULLCHAIN" | ||||||
|  |     _savedomainconf Le_Deploy_ssh_fullchain "$Le_Deploy_ssh_fullchain" | ||||||
|  |   fi | ||||||
|  |   if [ -n "$Le_Deploy_ssh_fullchain" ]; then | ||||||
|  |     _pipe=">" | ||||||
|  |     if [ "$Le_Deploy_ssh_fullchain" = "$Le_Deploy_ssh_keyfile" ] \ | ||||||
|  |       || [ "$Le_Deploy_ssh_fullchain" = "$Le_Deploy_ssh_certfile" ] \ | ||||||
|  |       || [ "$Le_Deploy_ssh_fullchain" = "$Le_Deploy_ssh_cafile" ]; then | ||||||
|  |       # if filename is same as previous file then append. | ||||||
|  |       _pipe=">>" | ||||||
|  |     elif [ "$Le_Deploy_ssh_backup" = "yes" ]; then | ||||||
|  |       # backup file we are about to overwrite. | ||||||
|  |       _cmdstr="$_cmdstr cp $Le_Deploy_ssh_fullchain $_backupdir >/dev/null;" | ||||||
|  |     fi | ||||||
|  |     # copy new certificate into file. | ||||||
|  |     _cmdstr="$_cmdstr echo \"$(cat "$_cfullchain")\" $_pipe $Le_Deploy_ssh_fullchain;" | ||||||
|  |     _info "will copy fullchain to remote file $Le_Deploy_ssh_fullchain" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   # REMOTE_CMD is optional. | ||||||
|  |   # If provided then this command will be executed on remote host. | ||||||
|  |   if [ -n "$DEPLOY_SSH_REMOTE_CMD" ]; then | ||||||
|  |     Le_Deploy_ssh_remote_cmd="$DEPLOY_SSH_REMOTE_CMD" | ||||||
|  |     _savedomainconf Le_Deploy_ssh_remote_cmd "$Le_Deploy_ssh_remote_cmd" | ||||||
|  |   fi | ||||||
|  |   if [ -n "$Le_Deploy_ssh_remote_cmd" ]; then | ||||||
|  |     _cmdstr="$_cmdstr $Le_Deploy_ssh_remote_cmd;" | ||||||
|  |     _info "Will execute remote command $Le_Deploy_ssh_remote_cmd" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   if [ -z "$_cmdstr" ]; then | ||||||
|  |     _err "No remote commands to excute. Failed to deploy certificates to remote server" | ||||||
|  |     return 1 | ||||||
|  |   elif [ "$Le_Deploy_ssh_backup" = "yes" ]; then | ||||||
|  |     # run cleanup on the backup directory, erase all older | ||||||
|  |     # than 180 days (15552000 seconds). | ||||||
|  |     _cmdstr="{ now=\"\$(date -u +%s)\"; for fn in $_backupprefix*; \ | ||||||
|  | do if [ -d \"\$fn\" ] && [ \"\$(expr \$now - \$(date -ur \$fn +%s) )\" -ge \"15552000\" ]; \ | ||||||
|  | then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; done; }; $_cmdstr" | ||||||
|  |     # Alternate version of above... _cmdstr="find $_backupprefix* -type d -mtime +180 2>/dev/null | xargs rm -rf; $_cmdstr" | ||||||
|  |     # Create our backup directory for overwritten cert files. | ||||||
|  |     _cmdstr="mkdir -p $_backupdir; $_cmdstr" | ||||||
|  |     _info "Backup of old certificate files will be placed in remote directory $_backupdir" | ||||||
|  |     _info "Backup directories erased after 180 days." | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _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 | ||||||
|  |   $Le_Deploy_ssh_cmd -T "$Le_Deploy_ssh_user@$Le_Deploy_ssh_server" sh -c "'$_cmdstr'" | ||||||
|  |   _ret="$?" | ||||||
|  | 
 | ||||||
|  |   if [ "$_ret" != "0" ]; then | ||||||
|  |     _err "Error code $_ret returned from $Le_Deploy_ssh_cmd" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   return $_ret | ||||||
|  | } | ||||||
| @ -22,6 +22,8 @@ strongswan_deploy() { | |||||||
|     _ipsec=/usr/sbin/ipsec |     _ipsec=/usr/sbin/ipsec | ||||||
|   elif [ -x /usr/sbin/strongswan ]; then |   elif [ -x /usr/sbin/strongswan ]; then | ||||||
|     _ipsec=/usr/sbin/strongswan |     _ipsec=/usr/sbin/strongswan | ||||||
|  |   elif [ -x /usr/local/sbin/ipsec ]; then | ||||||
|  |     _ipsec=/usr/local/sbin/ipsec | ||||||
|   else |   else | ||||||
|     _err "no strongswan or ipsec command is detected" |     _err "no strongswan or ipsec command is detected" | ||||||
|     return 1 |     return 1 | ||||||
|  | |||||||
							
								
								
									
										56
									
								
								deploy/vault_cli.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								deploy/vault_cli.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | |||||||
|  | #!/usr/bin/env sh | ||||||
|  | 
 | ||||||
|  | # Here is a script to deploy cert to hashicorp vault | ||||||
|  | # (https://www.vaultproject.io/) | ||||||
|  | #  | ||||||
|  | # it requires the vault binary to be available in PATH, and the following | ||||||
|  | # environment variables: | ||||||
|  | #  | ||||||
|  | # VAULT_PREFIX - this contains the prefix path in vault | ||||||
|  | # VAULT_ADDR - vault requires this to find your vault server | ||||||
|  | # | ||||||
|  | # additionally, you need to ensure that VAULT_TOKEN is avialable or | ||||||
|  | # `vault auth` has applied the appropriate authorization for the vault binary | ||||||
|  | # to access the vault server | ||||||
|  | 
 | ||||||
|  | #returns 0 means success, otherwise error. | ||||||
|  | 
 | ||||||
|  | ########  Public functions ##################### | ||||||
|  | 
 | ||||||
|  | #domain keyfile certfile cafile fullchain | ||||||
|  | vault_cli_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" | ||||||
|  | 
 | ||||||
|  |   # validate required env vars | ||||||
|  |   if [ -z "$VAULT_PREFIX" ]; then | ||||||
|  |     _err "VAULT_PREFIX needs to be defined (contains prefix path in vault)" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   if [ -z "$VAULT_ADDR" ]; then | ||||||
|  |     _err "VAULT_ADDR needs to be defined (contains vault connection address)" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   VAULT_CMD=$(which vault) | ||||||
|  |   if [ ! $? ]; then | ||||||
|  |     _err "cannot find vault binary!" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/cert.pem" value=@"$_ccert" || return 1 | ||||||
|  |   $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/cert.key" value=@"$_ckey" || return 1 | ||||||
|  |   $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/fullchain.pem" value=@"$_cfullchain" || return 1 | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -354,7 +354,7 @@ acme.sh --issue --dns dns_gandi_livedns -d example.com -d www.example.com | |||||||
| First, generate a TSIG key for updating the zone. | First, generate a TSIG key for updating the zone. | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| keymgr tsig generate acme_key algorithm hmac-sha512 > /etc/knot/acme.key | keymgr tsig generate -t acme_key hmac-sha512 > /etc/knot/acme.key | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| Include this key in your knot configuration file. | Include this key in your knot configuration file. | ||||||
| @ -409,10 +409,13 @@ acme.sh --issue --dns dns_dgon -d example.com -d www.example.com | |||||||
| 
 | 
 | ||||||
| ## 21. Use ClouDNS.net API | ## 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/ | You need to set the HTTP API user ID and password credentials. See: https://www.cloudns.net/wiki/article/42/. For security reasons, it's recommended to use a sub user ID that only has access to the necessary zones, as a regular API user has access to your entire account. | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| export CLOUDNS_AUTH_ID=XXXXX | # Use this for a sub auth ID | ||||||
|  | export CLOUDNS_SUB_AUTH_ID=XXXXX | ||||||
|  | # Use this for a regular auth ID | ||||||
|  | #export CLOUDNS_AUTH_ID=XXXXX | ||||||
| export CLOUDNS_AUTH_PASSWORD="YYYYYYYYY" | export CLOUDNS_AUTH_PASSWORD="YYYYYYYYY" | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| @ -741,7 +744,20 @@ acme.sh --issue --dns dns_zonomi -d example.com -d www.example.com | |||||||
| 
 | 
 | ||||||
| The `ZM_Key` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. | The `ZM_Key` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. | ||||||
| 
 | 
 | ||||||
| ## 40. Use DNSEver (https://www.dnsever.com/) | ## 40. Use DreamHost DNS API | ||||||
|  | 
 | ||||||
|  | DNS API keys may be created at https://panel.dreamhost.com/?tree=home.api. | ||||||
|  | Ensure the created key has add and remove privelages. | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | export DH_API_Key="<api key>" | ||||||
|  | acme.sh --issue --dns dns_dreamhost -d example.com -d www.example.com | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | The 'DH_API_KEY' will be saved in `~/.acme.sh/account.conf` and will | ||||||
|  | be reused when needed. | ||||||
|  | 
 | ||||||
|  | ## 41. Use DNSEver (https://www.dnsever.com/) | ||||||
| 
 | 
 | ||||||
| You will need your login credentials (ID+PW) to the DNSEver, and export them before you run acme.sh: | You will need your login credentials (ID+PW) to the DNSEver, and export them before you run acme.sh: | ||||||
| ``` | ``` | ||||||
|  | |||||||
| @ -10,6 +10,8 @@ dns_ali_add() { | |||||||
|   fulldomain=$1 |   fulldomain=$1 | ||||||
|   txtvalue=$2 |   txtvalue=$2 | ||||||
| 
 | 
 | ||||||
|  |   Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}" | ||||||
|  |   Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}" | ||||||
|   if [ -z "$Ali_Key" ] || [ -z "$Ali_Secret" ]; then |   if [ -z "$Ali_Key" ] || [ -z "$Ali_Secret" ]; then | ||||||
|     Ali_Key="" |     Ali_Key="" | ||||||
|     Ali_Secret="" |     Ali_Secret="" | ||||||
| @ -18,8 +20,8 @@ dns_ali_add() { | |||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   #save the api key and secret to the account conf file. |   #save the api key and secret to the account conf file. | ||||||
|   _saveaccountconf Ali_Key "$Ali_Key" |   _saveaccountconf_mutable Ali_Key "$Ali_Key" | ||||||
|   _saveaccountconf Ali_Secret "$Ali_Secret" |   _saveaccountconf_mutable Ali_Secret "$Ali_Secret" | ||||||
| 
 | 
 | ||||||
|   _debug "First detect the root zone" |   _debug "First detect the root zone" | ||||||
|   if ! _get_root "$fulldomain"; then |   if ! _get_root "$fulldomain"; then | ||||||
| @ -32,6 +34,15 @@ dns_ali_add() { | |||||||
| 
 | 
 | ||||||
| dns_ali_rm() { | dns_ali_rm() { | ||||||
|   fulldomain=$1 |   fulldomain=$1 | ||||||
|  |   txtvalue=$2 | ||||||
|  |   Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}" | ||||||
|  |   Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}" | ||||||
|  | 
 | ||||||
|  |   _debug "First detect the root zone" | ||||||
|  |   if ! _get_root "$fulldomain"; then | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|   _clean |   _clean | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -76,16 +87,14 @@ _ali_rest() { | |||||||
|     return 1 |     return 1 | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|  |   _debug2 response "$response" | ||||||
|   if [ -z "$2" ]; then |   if [ -z "$2" ]; then | ||||||
|     message="$(printf "%s" "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")" |     message="$(echo "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")" | ||||||
|     if [ -n "$message" ]; then |     if [ "$message" ]; then | ||||||
|       _err "$message" |       _err "$message" | ||||||
|       return 1 |       return 1 | ||||||
|     fi |     fi | ||||||
|   fi |   fi | ||||||
| 
 |  | ||||||
|   _debug2 response "$response" |  | ||||||
|   return 0 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| _ali_urlencode() { | _ali_urlencode() { | ||||||
| @ -112,12 +121,14 @@ _ali_nonce() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| _check_exist_query() { | _check_exist_query() { | ||||||
|  |   _qdomain="$1" | ||||||
|  |   _qsubdomain="$2" | ||||||
|   query='' |   query='' | ||||||
|   query=$query'AccessKeyId='$Ali_Key |   query=$query'AccessKeyId='$Ali_Key | ||||||
|   query=$query'&Action=DescribeDomainRecords' |   query=$query'&Action=DescribeDomainRecords' | ||||||
|   query=$query'&DomainName='$1 |   query=$query'&DomainName='$_qdomain | ||||||
|   query=$query'&Format=json' |   query=$query'&Format=json' | ||||||
|   query=$query'&RRKeyWord=_acme-challenge' |   query=$query'&RRKeyWord='$_qsubdomain | ||||||
|   query=$query'&SignatureMethod=HMAC-SHA1' |   query=$query'&SignatureMethod=HMAC-SHA1' | ||||||
|   query=$query"&SignatureNonce=$(_ali_nonce)" |   query=$query"&SignatureNonce=$(_ali_nonce)" | ||||||
|   query=$query'&SignatureVersion=1.0' |   query=$query'&SignatureVersion=1.0' | ||||||
| @ -169,17 +180,21 @@ _describe_records_query() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| _clean() { | _clean() { | ||||||
|   _check_exist_query "$_domain" |   _check_exist_query "$_domain" "$_sub_domain" | ||||||
|   if ! _ali_rest "Check exist records" "ignore"; then |   if ! _ali_rest "Check exist records" "ignore"; then | ||||||
|     return 1 |     return 1 | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   records="$(echo "$response" -n | _egrep_o "\"RecordId\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")" |   record_id="$(echo "$response" | tr '{' "\n" | grep "$_sub_domain" | grep "$txtvalue" | tr "," "\n" | grep RecordId | cut -d '"' -f 4)" | ||||||
|   printf "%s" "$records" \ |   _debug2 record_id "$record_id" | ||||||
|     | while read -r record_id; do | 
 | ||||||
|  |   if [ -z "$record_id" ]; then | ||||||
|  |     _debug "record not found, skip" | ||||||
|  |   else | ||||||
|     _delete_record_query "$record_id" |     _delete_record_query "$record_id" | ||||||
|     _ali_rest "Delete record $record_id" "ignore" |     _ali_rest "Delete record $record_id" "ignore" | ||||||
|     done |   fi | ||||||
|  | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| _timestamp() { | _timestamp() { | ||||||
|  | |||||||
| @ -21,6 +21,11 @@ dns_aws_add() { | |||||||
| 
 | 
 | ||||||
|   AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-$(_readaccountconf_mutable AWS_ACCESS_KEY_ID)}" |   AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-$(_readaccountconf_mutable AWS_ACCESS_KEY_ID)}" | ||||||
|   AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable AWS_SECRET_ACCESS_KEY)}" |   AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable AWS_SECRET_ACCESS_KEY)}" | ||||||
|  | 
 | ||||||
|  |   if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then | ||||||
|  |     _use_container_role || _use_instance_role | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|   if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then |   if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then | ||||||
|     AWS_ACCESS_KEY_ID="" |     AWS_ACCESS_KEY_ID="" | ||||||
|     AWS_SECRET_ACCESS_KEY="" |     AWS_SECRET_ACCESS_KEY="" | ||||||
| @ -29,9 +34,11 @@ dns_aws_add() { | |||||||
|     return 1 |     return 1 | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   #save for future use |   #save for future use, unless using a role which will be fetched as needed | ||||||
|  |   if [ -z "$_using_role" ]; then | ||||||
|     _saveaccountconf_mutable AWS_ACCESS_KEY_ID "$AWS_ACCESS_KEY_ID" |     _saveaccountconf_mutable AWS_ACCESS_KEY_ID "$AWS_ACCESS_KEY_ID" | ||||||
|     _saveaccountconf_mutable AWS_SECRET_ACCESS_KEY "$AWS_SECRET_ACCESS_KEY" |     _saveaccountconf_mutable AWS_SECRET_ACCESS_KEY "$AWS_SECRET_ACCESS_KEY" | ||||||
|  |   fi | ||||||
| 
 | 
 | ||||||
|   _debug "First detect the root zone" |   _debug "First detect the root zone" | ||||||
|   if ! _get_root "$fulldomain"; then |   if ! _get_root "$fulldomain"; then | ||||||
| @ -42,7 +49,26 @@ dns_aws_add() { | |||||||
|   _debug _sub_domain "$_sub_domain" |   _debug _sub_domain "$_sub_domain" | ||||||
|   _debug _domain "$_domain" |   _debug _domain "$_domain" | ||||||
| 
 | 
 | ||||||
|   _aws_tmpl_xml="<ChangeResourceRecordSetsRequest xmlns=\"https://route53.amazonaws.com/doc/2013-04-01/\"><ChangeBatch><Changes><Change><Action>UPSERT</Action><ResourceRecordSet><Name>$fulldomain</Name><Type>TXT</Type><TTL>300</TTL><ResourceRecords><ResourceRecord><Value>\"$txtvalue\"</Value></ResourceRecord></ResourceRecords></ResourceRecordSet></Change></Changes></ChangeBatch></ChangeResourceRecordSetsRequest>" |   _info "Geting existing records for $fulldomain" | ||||||
|  |   if ! aws_rest GET "2013-04-01$_domain_id/rrset" "name=$fulldomain&type=TXT"; then | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   if _contains "$response" "<Name>$fulldomain.</Name>"; then | ||||||
|  |     _resource_record="$(echo "$response" | sed 's/<ResourceRecordSet>/"/g' | tr '"' "\n" | grep "<Name>$fulldomain.</Name>" | _egrep_o "<ResourceRecords.*</ResourceRecords>" | sed "s/<ResourceRecords>//" | sed "s#</ResourceRecords>##")" | ||||||
|  |     _debug "_resource_record" "$_resource_record" | ||||||
|  |   else | ||||||
|  |     _debug "single new add" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   if [ "$_resource_record" ] && _contains "$response" "$txtvalue"; then | ||||||
|  |     _info "The txt record already exists, skip" | ||||||
|  |     return 0 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _debug "Adding records" | ||||||
|  | 
 | ||||||
|  |   _aws_tmpl_xml="<ChangeResourceRecordSetsRequest xmlns=\"https://route53.amazonaws.com/doc/2013-04-01/\"><ChangeBatch><Changes><Change><Action>UPSERT</Action><ResourceRecordSet><Name>$fulldomain</Name><Type>TXT</Type><TTL>300</TTL><ResourceRecords>$_resource_record<ResourceRecord><Value>\"$txtvalue\"</Value></ResourceRecord></ResourceRecords></ResourceRecordSet></Change></Changes></ChangeBatch></ChangeResourceRecordSetsRequest>" | ||||||
| 
 | 
 | ||||||
|   if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then |   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 success." | ||||||
| @ -59,6 +85,11 @@ dns_aws_rm() { | |||||||
| 
 | 
 | ||||||
|   AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-$(_readaccountconf_mutable AWS_ACCESS_KEY_ID)}" |   AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-$(_readaccountconf_mutable AWS_ACCESS_KEY_ID)}" | ||||||
|   AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable AWS_SECRET_ACCESS_KEY)}" |   AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable AWS_SECRET_ACCESS_KEY)}" | ||||||
|  | 
 | ||||||
|  |   if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then | ||||||
|  |     _use_container_role || _use_instance_role | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|   _debug "First detect the root zone" |   _debug "First detect the root zone" | ||||||
|   if ! _get_root "$fulldomain"; then |   if ! _get_root "$fulldomain"; then | ||||||
|     _err "invalid domain" |     _err "invalid domain" | ||||||
| @ -68,7 +99,20 @@ dns_aws_rm() { | |||||||
|   _debug _sub_domain "$_sub_domain" |   _debug _sub_domain "$_sub_domain" | ||||||
|   _debug _domain "$_domain" |   _debug _domain "$_domain" | ||||||
| 
 | 
 | ||||||
|   _aws_tmpl_xml="<ChangeResourceRecordSetsRequest xmlns=\"https://route53.amazonaws.com/doc/2013-04-01/\"><ChangeBatch><Changes><Change><Action>DELETE</Action><ResourceRecordSet><ResourceRecords><ResourceRecord><Value>\"$txtvalue\"</Value></ResourceRecord></ResourceRecords><Name>$fulldomain.</Name><Type>TXT</Type><TTL>300</TTL></ResourceRecordSet></Change></Changes></ChangeBatch></ChangeResourceRecordSetsRequest>" |   _info "Geting existing records for $fulldomain" | ||||||
|  |   if ! aws_rest GET "2013-04-01$_domain_id/rrset" "name=$fulldomain&type=TXT"; then | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   if _contains "$response" "<Name>$fulldomain.</Name>"; then | ||||||
|  |     _resource_record="$(echo "$response" | sed 's/<ResourceRecordSet>/"/g' | tr '"' "\n" | grep "<Name>$fulldomain.</Name>" | _egrep_o "<ResourceRecords.*</ResourceRecords>" | sed "s/<ResourceRecords>//" | sed "s#</ResourceRecords>##")" | ||||||
|  |     _debug "_resource_record" "$_resource_record" | ||||||
|  |   else | ||||||
|  |     _debug "no records exists, skip" | ||||||
|  |     return 0 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _aws_tmpl_xml="<ChangeResourceRecordSetsRequest xmlns=\"https://route53.amazonaws.com/doc/2013-04-01/\"><ChangeBatch><Changes><Change><Action>DELETE</Action><ResourceRecordSet><ResourceRecords>$_resource_record</ResourceRecords><Name>$fulldomain.</Name><Type>TXT</Type><TTL>300</TTL></ResourceRecordSet></Change></Changes></ChangeBatch></ChangeResourceRecordSetsRequest>" | ||||||
| 
 | 
 | ||||||
|   if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then |   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 success." | ||||||
| @ -87,7 +131,6 @@ _get_root() { | |||||||
|   p=1 |   p=1 | ||||||
| 
 | 
 | ||||||
|   if aws_rest GET "2013-04-01/hostedzone"; then |   if aws_rest GET "2013-04-01/hostedzone"; then | ||||||
|     _debug "response" "$response" |  | ||||||
|     while true; do |     while true; do | ||||||
|       h=$(printf "%s" "$domain" | cut -d . -f $i-100) |       h=$(printf "%s" "$domain" | cut -d . -f $i-100) | ||||||
|       _debug2 "Checking domain: $h" |       _debug2 "Checking domain: $h" | ||||||
| @ -131,6 +174,55 @@ _get_root() { | |||||||
|   return 1 |   return 1 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | _use_container_role() { | ||||||
|  |   # automatically set if running inside ECS | ||||||
|  |   if [ -z "$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" ]; then | ||||||
|  |     _debug "No ECS environment variable detected" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  |   _use_metadata "169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | _use_instance_role() { | ||||||
|  |   _url="http://169.254.169.254/latest/meta-data/iam/security-credentials/" | ||||||
|  |   _debug "_url" "$_url" | ||||||
|  |   if ! _get "$_url" true 1 | _head_n 1 | grep -Fq 200; then | ||||||
|  |     _debug "Unable to fetch IAM role from instance metadata" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  |   _aws_role=$(_get "$_url" "" 1) | ||||||
|  |   _debug "_aws_role" "$_aws_role" | ||||||
|  |   _use_metadata "$_url$_aws_role" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | _use_metadata() { | ||||||
|  |   _aws_creds="$( | ||||||
|  |     _get "$1" "" 1 \ | ||||||
|  |       | _normalizeJson \ | ||||||
|  |       | tr '{,}' '\n' \ | ||||||
|  |       | while read -r _line; do | ||||||
|  |         _key="$(echo "${_line%%:*}" | tr -d '"')" | ||||||
|  |         _value="${_line#*:}" | ||||||
|  |         _debug3 "_key" "$_key" | ||||||
|  |         _secure_debug3 "_value" "$_value" | ||||||
|  |         case "$_key" in | ||||||
|  |           AccessKeyId) echo "AWS_ACCESS_KEY_ID=$_value" ;; | ||||||
|  |           SecretAccessKey) echo "AWS_SECRET_ACCESS_KEY=$_value" ;; | ||||||
|  |           Token) echo "AWS_SESSION_TOKEN=$_value" ;; | ||||||
|  |         esac | ||||||
|  |       done \ | ||||||
|  |         | paste -sd' ' - | ||||||
|  |   )" | ||||||
|  |   _secure_debug "_aws_creds" "$_aws_creds" | ||||||
|  | 
 | ||||||
|  |   if [ -z "$_aws_creds" ]; then | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   eval "$_aws_creds" | ||||||
|  |   _using_role=true | ||||||
|  | } | ||||||
|  | 
 | ||||||
| #method uri qstr data | #method uri qstr data | ||||||
| aws_rest() { | aws_rest() { | ||||||
|   mtd="$1" |   mtd="$1" | ||||||
| @ -236,6 +328,7 @@ aws_rest() { | |||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   _ret="$?" |   _ret="$?" | ||||||
|  |   _debug2 response "$response" | ||||||
|   if [ "$_ret" = "0" ]; then |   if [ "$_ret" = "0" ]; then | ||||||
|     if _contains "$response" "<ErrorResponse"; then |     if _contains "$response" "<ErrorResponse"; then | ||||||
|       _err "Response error:$response" |       _err "Response error:$response" | ||||||
|  | |||||||
| @ -1,5 +1,7 @@ | |||||||
| #!/usr/bin/env sh | #!/usr/bin/env sh | ||||||
| 
 | 
 | ||||||
|  | WIKI="https://github.com/Neilpang/acme.sh/wiki/How-to-use-Azure-DNS" | ||||||
|  | 
 | ||||||
| ########  Public functions ##################### | ########  Public functions ##################### | ||||||
| 
 | 
 | ||||||
| # Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" | # Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" | ||||||
| @ -69,12 +71,36 @@ dns_azure_add() { | |||||||
| 
 | 
 | ||||||
|   acmeRecordURI="https://management.azure.com$(printf '%s' "$_domain_id" | sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01" |   acmeRecordURI="https://management.azure.com$(printf '%s' "$_domain_id" | sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01" | ||||||
|   _debug "$acmeRecordURI" |   _debug "$acmeRecordURI" | ||||||
|   body="{\"properties\": {\"TTL\": 3600, \"TXTRecords\": [{\"value\": [\"$txtvalue\"]}]}}" |   # Get existing TXT record | ||||||
|  |   _azure_rest GET "$acmeRecordURI" "" "$accesstoken" | ||||||
|  |   values="{\"value\":[\"$txtvalue\"]}" | ||||||
|  |   timestamp="$(_time)" | ||||||
|  |   if [ "$_code" = "200" ]; then | ||||||
|  |     vlist="$(echo "$response" | _egrep_o "\"value\"\s*:\s*\[\s*\"[^\"]*\"\s*]" | cut -d : -f 2 | tr -d "[]\"")" | ||||||
|  |     _debug "existing TXT found" | ||||||
|  |     _debug "$vlist" | ||||||
|  |     existingts="$(echo "$response" | _egrep_o "\"acmetscheck\"\s*:\s*\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d "\"")" | ||||||
|  |     if [ -z "$existingts" ]; then | ||||||
|  |       # the record was not created by acme.sh. Copy the exisiting entires | ||||||
|  |       existingts=$timestamp | ||||||
|  |     fi | ||||||
|  |     _diff="$(_math "$timestamp - $existingts")" | ||||||
|  |     _debug "existing txt age: $_diff" | ||||||
|  |     # only use recently added records and discard if older than 2 hours because they are probably orphaned | ||||||
|  |     if [ "$_diff" -lt 7200 ]; then | ||||||
|  |       _debug "existing txt value: $vlist" | ||||||
|  |       for v in $vlist; do | ||||||
|  |         values="$values ,{\"value\":[\"$v\"]}" | ||||||
|  |       done | ||||||
|  |     fi | ||||||
|  |   fi | ||||||
|  |   # Add the txtvalue TXT Record | ||||||
|  |   body="{\"properties\":{\"metadata\":{\"acmetscheck\":\"$timestamp\"},\"TTL\":10, \"TXTRecords\":[$values]}}" | ||||||
|   _azure_rest PUT "$acmeRecordURI" "$body" "$accesstoken" |   _azure_rest PUT "$acmeRecordURI" "$body" "$accesstoken" | ||||||
|   if [ "$_code" = "200" ] || [ "$_code" = '201' ]; then |   if [ "$_code" = "200" ] || [ "$_code" = '201' ]; then | ||||||
|     _info "validation record added" |     _info "validation value added" | ||||||
|   else |   else | ||||||
|     _err "error adding validation record ($_code)" |     _err "error adding validation value ($_code)" | ||||||
|     return 1 |     return 1 | ||||||
|   fi |   fi | ||||||
| } | } | ||||||
| @ -141,7 +167,20 @@ dns_azure_rm() { | |||||||
| 
 | 
 | ||||||
|   acmeRecordURI="https://management.azure.com$(printf '%s' "$_domain_id" | sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01" |   acmeRecordURI="https://management.azure.com$(printf '%s' "$_domain_id" | sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01" | ||||||
|   _debug "$acmeRecordURI" |   _debug "$acmeRecordURI" | ||||||
|   body="{\"properties\": {\"TTL\": 3600, \"TXTRecords\": [{\"value\": [\"$txtvalue\"]}]}}" |   # Get existing TXT record | ||||||
|  |   _azure_rest GET "$acmeRecordURI" "" "$accesstoken" | ||||||
|  |   timestamp="$(_time)" | ||||||
|  |   if [ "$_code" = "200" ]; then | ||||||
|  |     vlist="$(echo "$response" | _egrep_o "\"value\"\s*:\s*\[\s*\"[^\"]*\"\s*]" | cut -d : -f 2 | tr -d "[]\"" | grep -v "$txtvalue")" | ||||||
|  |     values="" | ||||||
|  |     comma="" | ||||||
|  |     for v in $vlist; do | ||||||
|  |       values="$values$comma{\"value\":[\"$v\"]}" | ||||||
|  |       comma="," | ||||||
|  |     done | ||||||
|  |     if [ -z "$values" ]; then | ||||||
|  |       # No values left remove record | ||||||
|  |       _debug "removing validation record completely $acmeRecordURI" | ||||||
|       _azure_rest DELETE "$acmeRecordURI" "" "$accesstoken" |       _azure_rest DELETE "$acmeRecordURI" "" "$accesstoken" | ||||||
|       if [ "$_code" = "200" ] || [ "$_code" = '204' ]; then |       if [ "$_code" = "200" ] || [ "$_code" = '204' ]; then | ||||||
|         _info "validation record removed" |         _info "validation record removed" | ||||||
| @ -149,6 +188,18 @@ dns_azure_rm() { | |||||||
|         _err "error removing validation record ($_code)" |         _err "error removing validation record ($_code)" | ||||||
|         return 1 |         return 1 | ||||||
|       fi |       fi | ||||||
|  |     else | ||||||
|  |       # Remove only txtvalue from the TXT Record | ||||||
|  |       body="{\"properties\":{\"metadata\":{\"acmetscheck\":\"$timestamp\"},\"TTL\":10, \"TXTRecords\":[$values]}}" | ||||||
|  |       _azure_rest PUT "$acmeRecordURI" "$body" "$accesstoken" | ||||||
|  |       if [ "$_code" = "200" ] || [ "$_code" = '201' ]; then | ||||||
|  |         _info "validation value removed" | ||||||
|  |       else | ||||||
|  |         _err "error removing validation value ($_code)" | ||||||
|  |         return 1 | ||||||
|  |       fi | ||||||
|  |     fi | ||||||
|  |   fi | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ###################  Private functions below ################################## | ###################  Private functions below ################################## | ||||||
| @ -159,52 +210,92 @@ _azure_rest() { | |||||||
|   data="$3" |   data="$3" | ||||||
|   accesstoken="$4" |   accesstoken="$4" | ||||||
| 
 | 
 | ||||||
|  |   MAX_REQUEST_RETRY_TIMES=5 | ||||||
|  |   _request_retry_times=0 | ||||||
|  |   while [ "${_request_retry_times}" -lt "$MAX_REQUEST_RETRY_TIMES" ]; do | ||||||
|  |     _debug3 _request_retry_times "$_request_retry_times" | ||||||
|     export _H1="authorization: Bearer $accesstoken" |     export _H1="authorization: Bearer $accesstoken" | ||||||
|     export _H2="accept: application/json" |     export _H2="accept: application/json" | ||||||
|     export _H3="Content-Type: application/json" |     export _H3="Content-Type: application/json" | ||||||
| 
 |     # clear headers from previous request to avoid getting wrong http code on timeouts | ||||||
|  |     :>"$HTTP_HEADER" | ||||||
|     _debug "$ep" |     _debug "$ep" | ||||||
|     if [ "$m" != "GET" ]; then |     if [ "$m" != "GET" ]; then | ||||||
|     _debug data "$data" |       _secure_debug2 "data $data" | ||||||
|       response="$(_post "$data" "$ep" "" "$m")" |       response="$(_post "$data" "$ep" "" "$m")" | ||||||
|     else |     else | ||||||
|       response="$(_get "$ep")" |       response="$(_get "$ep")" | ||||||
|     fi |     fi | ||||||
|   _debug2 response "$response" |     _secure_debug2 "response $response" | ||||||
| 
 |  | ||||||
|     _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")" |     _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")" | ||||||
|   _debug2 "http response code $_code" |     _debug "http response code $_code" | ||||||
| 
 |     if [ "$_code" = "401" ]; then | ||||||
|   if [ "$?" != "0" ]; then |       # we have an invalid access token set to expired | ||||||
|     _err "error $ep" |       _saveaccountconf_mutable AZUREDNS_TOKENVALIDTO "0" | ||||||
|  |       _err "access denied make sure your Azure settings are correct. See $WIKI" | ||||||
|       return 1 |       return 1 | ||||||
|     fi |     fi | ||||||
|  |     # See https://docs.microsoft.com/en-us/azure/architecture/best-practices/retry-service-specific#general-rest-and-retry-guidelines for retryable HTTP codes | ||||||
|  |     if [ "$?" != "0" ] || [ -z "$_code" ] || [ "$_code" = "408" ] || [ "$_code" = "500" ] || [ "$_code" = "503" ] || [ "$_code" = "504" ]; then | ||||||
|  |       _request_retry_times="$(_math "$_request_retry_times" + 1)" | ||||||
|  |       _info "REST call error $_code retrying $ep in $_request_retry_times s" | ||||||
|  |       _sleep "$_request_retry_times" | ||||||
|  |       continue | ||||||
|  |     fi | ||||||
|  |     break | ||||||
|  |   done | ||||||
|  |   if [ "$_request_retry_times" = "$MAX_REQUEST_RETRY_TIMES" ]; then | ||||||
|  |     _err "Error Azure REST called was retried $MAX_REQUEST_RETRY_TIMES times." | ||||||
|  |     _err "Calling $ep failed." | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  |   response="$(echo "$response" | _normalizeJson)" | ||||||
|   return 0 |   return 0 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ## Ref: https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-service-to-service#request-an-access-token | ## Ref: https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-service-to-service#request-an-access-token | ||||||
| _azure_getaccess_token() { | _azure_getaccess_token() { | ||||||
|   TENANTID=$1 |   tenantID=$1 | ||||||
|   clientID=$2 |   clientID=$2 | ||||||
|   clientSecret=$3 |   clientSecret=$3 | ||||||
| 
 | 
 | ||||||
|  |   accesstoken="${AZUREDNS_BEARERTOKEN:-$(_readaccountconf_mutable AZUREDNS_BEARERTOKEN)}" | ||||||
|  |   expires_on="${AZUREDNS_TOKENVALIDTO:-$(_readaccountconf_mutable AZUREDNS_TOKENVALIDTO)}" | ||||||
|  | 
 | ||||||
|  |   # can we reuse the bearer token? | ||||||
|  |   if [ -n "$accesstoken" ] && [ -n "$expires_on" ]; then | ||||||
|  |     if [ "$(_time)" -lt "$expires_on" ]; then | ||||||
|  |       # brearer token is still valid - reuse it | ||||||
|  |       _debug "reusing bearer token" | ||||||
|  |       printf "%s" "$accesstoken" | ||||||
|  |       return 0 | ||||||
|  |     else | ||||||
|  |       _debug "bearer token expired" | ||||||
|  |     fi | ||||||
|  |   fi | ||||||
|  |   _debug "getting new bearer token" | ||||||
|  | 
 | ||||||
|   export _H1="accept: application/json" |   export _H1="accept: application/json" | ||||||
|   export _H2="Content-Type: application/x-www-form-urlencoded" |   export _H2="Content-Type: application/x-www-form-urlencoded" | ||||||
| 
 | 
 | ||||||
|   body="resource=$(printf "%s" 'https://management.core.windows.net/' | _url_encode)&client_id=$(printf "%s" "$clientID" | _url_encode)&client_secret=$(printf "%s" "$clientSecret" | _url_encode)&grant_type=client_credentials" |   body="resource=$(printf "%s" 'https://management.core.windows.net/' | _url_encode)&client_id=$(printf "%s" "$clientID" | _url_encode)&client_secret=$(printf "%s" "$clientSecret" | _url_encode)&grant_type=client_credentials" | ||||||
|   _debug data "$body" |   _secure_debug2 "data $body" | ||||||
|   response="$(_post "$body" "https://login.windows.net/$TENANTID/oauth2/token" "" "POST")" |   response="$(_post "$body" "https://login.microsoftonline.com/$tenantID/oauth2/token" "" "POST")" | ||||||
|  |   _secure_debug2 "response $response" | ||||||
|  |   response="$(echo "$response" | _normalizeJson)" | ||||||
|   accesstoken=$(echo "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") |   accesstoken=$(echo "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") | ||||||
|   _debug2 "response $response" |   expires_on=$(echo "$response" | _egrep_o "\"expires_on\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") | ||||||
| 
 | 
 | ||||||
|   if [ -z "$accesstoken" ]; then |   if [ -z "$accesstoken" ]; then | ||||||
|     _err "no acccess token received" |     _err "no acccess token received. Check your Azure settings see $WIKI" | ||||||
|     return 1 |     return 1 | ||||||
|   fi |   fi | ||||||
|   if [ "$?" != "0" ]; then |   if [ "$?" != "0" ]; then | ||||||
|     _err "error $response" |     _err "error $response" | ||||||
|     return 1 |     return 1 | ||||||
|   fi |   fi | ||||||
|  |   _saveaccountconf_mutable AZUREDNS_BEARERTOKEN "$accesstoken" | ||||||
|  |   _saveaccountconf_mutable AZUREDNS_TOKENVALIDTO "$expires_on" | ||||||
|   printf "%s" "$accesstoken" |   printf "%s" "$accesstoken" | ||||||
|   return 0 |   return 0 | ||||||
| } | } | ||||||
| @ -222,7 +313,6 @@ _get_root() { | |||||||
|   ## Per https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits#dns-limits you are limited to 100 Zone/subscriptions anyways |   ## Per https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits#dns-limits you are limited to 100 Zone/subscriptions anyways | ||||||
|   ## |   ## | ||||||
|   _azure_rest GET "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Network/dnszones?api-version=2017-09-01" "" "$accesstoken" |   _azure_rest GET "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Network/dnszones?api-version=2017-09-01" "" "$accesstoken" | ||||||
| 
 |  | ||||||
|   # Find matching domain name is Json response |   # Find matching domain name is Json response | ||||||
|   while true; do |   while true; do | ||||||
|     h=$(printf "%s" "$domain" | cut -d . -f $i-100) |     h=$(printf "%s" "$domain" | cut -d . -f $i-100) | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ | |||||||
| # Repository: https://github.com/ClouDNS/acme.sh/ | # Repository: https://github.com/ClouDNS/acme.sh/ | ||||||
| 
 | 
 | ||||||
| #CLOUDNS_AUTH_ID=XXXXX | #CLOUDNS_AUTH_ID=XXXXX | ||||||
|  | #CLOUDNS_SUB_AUTH_ID=XXXXX | ||||||
| #CLOUDNS_AUTH_PASSWORD="YYYYYYYYY" | #CLOUDNS_AUTH_PASSWORD="YYYYYYYYY" | ||||||
| CLOUDNS_API="https://api.cloudns.net" | CLOUDNS_API="https://api.cloudns.net" | ||||||
| 
 | 
 | ||||||
| @ -25,14 +26,11 @@ dns_cloudns_add() { | |||||||
| 
 | 
 | ||||||
|   host="$(echo "$1" | sed "s/\.$zone\$//")" |   host="$(echo "$1" | sed "s/\.$zone\$//")" | ||||||
|   record=$2 |   record=$2 | ||||||
|   record_id=$(_dns_cloudns_get_record_id "$zone" "$host") |  | ||||||
| 
 | 
 | ||||||
|   _debug zone "$zone" |   _debug zone "$zone" | ||||||
|   _debug host "$host" |   _debug host "$host" | ||||||
|   _debug record "$record" |   _debug record "$record" | ||||||
|   _debug record_id "$record_id" |  | ||||||
| 
 | 
 | ||||||
|   if [ -z "$record_id" ]; then |  | ||||||
|   _info "Adding the TXT record for $1" |   _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" |   _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 |   if ! _contains "$response" "\"status\":\"Success\""; then | ||||||
| @ -40,15 +38,6 @@ dns_cloudns_add() { | |||||||
|     return 1 |     return 1 | ||||||
|   fi |   fi | ||||||
|   _info "Added." |   _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 |   return 0 | ||||||
| } | } | ||||||
| @ -71,22 +60,32 @@ dns_cloudns_rm() { | |||||||
| 
 | 
 | ||||||
|   host="$(echo "$1" | sed "s/\.$zone\$//")" |   host="$(echo "$1" | sed "s/\.$zone\$//")" | ||||||
|   record=$2 |   record=$2 | ||||||
|   record_id=$(_dns_cloudns_get_record_id "$zone" "$host") |  | ||||||
| 
 | 
 | ||||||
|  |   _dns_cloudns_http_api_call "dns/records.json" "domain-name=$zone&host=$host&type=TXT" | ||||||
|  |   if ! _contains "$response" "\"id\":"; then | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   for i in $(echo "$response" | tr '{' "\n" | grep "$record"); do | ||||||
|  |     record_id=$(echo "$i" | tr ',' "\n" | grep -E '^"id"' | sed -re 's/^\"id\"\:\"([0-9]+)\"$/\1/g') | ||||||
|  | 
 | ||||||
|  |     if [ ! -z "$record_id" ]; then | ||||||
|       _debug zone "$zone" |       _debug zone "$zone" | ||||||
|       _debug host "$host" |       _debug host "$host" | ||||||
|       _debug record "$record" |       _debug record "$record" | ||||||
|       _debug record_id "$record_id" |       _debug record_id "$record_id" | ||||||
| 
 | 
 | ||||||
|   if [ ! -z "$record_id" ]; then |  | ||||||
|       _info "Deleting the TXT record for $1" |       _info "Deleting the TXT record for $1" | ||||||
|       _dns_cloudns_http_api_call "dns/delete-record.json" "domain-name=$zone&record-id=$record_id" |       _dns_cloudns_http_api_call "dns/delete-record.json" "domain-name=$zone&record-id=$record_id" | ||||||
|  | 
 | ||||||
|       if ! _contains "$response" "\"status\":\"Success\""; then |       if ! _contains "$response" "\"status\":\"Success\""; then | ||||||
|         _err "The TXT record for $1 cannot be deleted." |         _err "The TXT record for $1 cannot be deleted." | ||||||
|       return 1 |       else | ||||||
|     fi |  | ||||||
|         _info "Deleted." |         _info "Deleted." | ||||||
|       fi |       fi | ||||||
|  |     fi | ||||||
|  |   done | ||||||
|  | 
 | ||||||
|   return 0 |   return 0 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -97,17 +96,19 @@ _dns_cloudns_init_check() { | |||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   CLOUDNS_AUTH_ID="${CLOUDNS_AUTH_ID:-$(_readaccountconf_mutable CLOUDNS_AUTH_ID)}" |   CLOUDNS_AUTH_ID="${CLOUDNS_AUTH_ID:-$(_readaccountconf_mutable CLOUDNS_AUTH_ID)}" | ||||||
|  |   CLOUDNS_SUB_AUTH_ID="${CLOUDNS_SUB_AUTH_ID:-$(_readaccountconf_mutable CLOUDNS_SUB_AUTH_ID)}" | ||||||
|   CLOUDNS_AUTH_PASSWORD="${CLOUDNS_AUTH_PASSWORD:-$(_readaccountconf_mutable CLOUDNS_AUTH_PASSWORD)}" |   CLOUDNS_AUTH_PASSWORD="${CLOUDNS_AUTH_PASSWORD:-$(_readaccountconf_mutable CLOUDNS_AUTH_PASSWORD)}" | ||||||
|   if [ -z "$CLOUDNS_AUTH_ID" ] || [ -z "$CLOUDNS_AUTH_PASSWORD" ]; then |   if [ -z "$CLOUDNS_AUTH_ID$CLOUDNS_SUB_AUTH_ID" ] || [ -z "$CLOUDNS_AUTH_PASSWORD" ]; then | ||||||
|     CLOUDNS_AUTH_ID="" |     CLOUDNS_AUTH_ID="" | ||||||
|  |     CLOUDNS_SUB_AUTH_ID="" | ||||||
|     CLOUDNS_AUTH_PASSWORD="" |     CLOUDNS_AUTH_PASSWORD="" | ||||||
|     _err "You don't specify cloudns api id and password yet." |     _err "You don't specify cloudns api id and password yet." | ||||||
|     _err "Please create you id and password and try again." |     _err "Please create you id and password and try again." | ||||||
|     return 1 |     return 1 | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   if [ -z "$CLOUDNS_AUTH_ID" ]; then |   if [ -z "$CLOUDNS_AUTH_ID" ] && [ -z "$CLOUDNS_SUB_AUTH_ID" ]; then | ||||||
|     _err "CLOUDNS_AUTH_ID is not configured" |     _err "CLOUDNS_AUTH_ID or CLOUDNS_SUB_AUTH_ID is not configured" | ||||||
|     return 1 |     return 1 | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
| @ -125,6 +126,7 @@ _dns_cloudns_init_check() { | |||||||
| 
 | 
 | ||||||
|   # save the api id and password to the account conf file. |   # save the api id and password to the account conf file. | ||||||
|   _saveaccountconf_mutable CLOUDNS_AUTH_ID "$CLOUDNS_AUTH_ID" |   _saveaccountconf_mutable CLOUDNS_AUTH_ID "$CLOUDNS_AUTH_ID" | ||||||
|  |   _saveaccountconf_mutable CLOUDNS_SUB_AUTH_ID "$CLOUDNS_SUB_AUTH_ID" | ||||||
|   _saveaccountconf_mutable CLOUDNS_AUTH_PASSWORD "$CLOUDNS_AUTH_PASSWORD" |   _saveaccountconf_mutable CLOUDNS_AUTH_PASSWORD "$CLOUDNS_AUTH_PASSWORD" | ||||||
| 
 | 
 | ||||||
|   CLOUDNS_INIT_CHECK_COMPLETED=1 |   CLOUDNS_INIT_CHECK_COMPLETED=1 | ||||||
| @ -155,30 +157,28 @@ _dns_cloudns_get_zone_name() { | |||||||
|   return 1 |   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() { | _dns_cloudns_http_api_call() { | ||||||
|   method=$1 |   method=$1 | ||||||
| 
 | 
 | ||||||
|   _debug CLOUDNS_AUTH_ID "$CLOUDNS_AUTH_ID" |   _debug CLOUDNS_AUTH_ID "$CLOUDNS_AUTH_ID" | ||||||
|  |   _debug CLOUDNS_SUB_AUTH_ID "$CLOUDNS_SUB_AUTH_ID" | ||||||
|   _debug CLOUDNS_AUTH_PASSWORD "$CLOUDNS_AUTH_PASSWORD" |   _debug CLOUDNS_AUTH_PASSWORD "$CLOUDNS_AUTH_PASSWORD" | ||||||
| 
 | 
 | ||||||
|   if [ -z "$2" ]; then |   if [ ! -z "$CLOUDNS_SUB_AUTH_ID" ]; then | ||||||
|     data="auth-id=$CLOUDNS_AUTH_ID&auth-password=$CLOUDNS_AUTH_PASSWORD" |     auth_user="sub-auth-id=$CLOUDNS_SUB_AUTH_ID" | ||||||
|   else |   else | ||||||
|     data="auth-id=$CLOUDNS_AUTH_ID&auth-password=$CLOUDNS_AUTH_PASSWORD&$2" |     auth_user="auth-id=$CLOUDNS_AUTH_ID" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   if [ -z "$2" ]; then | ||||||
|  |     data="$auth_user&auth-password=$CLOUDNS_AUTH_PASSWORD" | ||||||
|  |   else | ||||||
|  |     data="$auth_user&auth-password=$CLOUDNS_AUTH_PASSWORD&$2" | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   response="$(_get "$CLOUDNS_API/$method?$data")" |   response="$(_get "$CLOUDNS_API/$method?$data")" | ||||||
| 
 | 
 | ||||||
|   _debug2 response "$response" |   _debug response "$response" | ||||||
| 
 | 
 | ||||||
|   return 0 |   return 0 | ||||||
| } | } | ||||||
|  | |||||||
| @ -36,33 +36,18 @@ dns_cx_add() { | |||||||
|     return 1 |     return 1 | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   existing_records "$_domain" "$_sub_domain" |  | ||||||
|   _debug count "$count" |  | ||||||
|   if [ "$?" != "0" ]; then |  | ||||||
|     _err "Error get existing records." |  | ||||||
|     return 1 |  | ||||||
|   fi |  | ||||||
| 
 |  | ||||||
|   if [ "$count" = "0" ]; then |  | ||||||
|   add_record "$_domain" "$_sub_domain" "$txtvalue" |   add_record "$_domain" "$_sub_domain" "$txtvalue" | ||||||
|   else |  | ||||||
|     update_record "$_domain" "$_sub_domain" "$txtvalue" |  | ||||||
|   fi |  | ||||||
| 
 |  | ||||||
|   if [ "$?" = "0" ]; then |  | ||||||
|     return 0 |  | ||||||
|   fi |  | ||||||
|   return 1 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #fulldomain | #fulldomain txtvalue | ||||||
| dns_cx_rm() { | dns_cx_rm() { | ||||||
|   fulldomain=$1 |   fulldomain=$1 | ||||||
|  |   txtvalue=$2 | ||||||
|   REST_API="$CX_Api" |   REST_API="$CX_Api" | ||||||
|   if _get_root "$fulldomain"; then |   if _get_root "$fulldomain"; then | ||||||
|     record_id="" |     record_id="" | ||||||
|     existing_records "$_domain" "$_sub_domain" |     existing_records "$_domain" "$_sub_domain" "$txtvalue" | ||||||
|     if ! [ "$record_id" = "" ]; then |     if [ "$record_id" ]; then | ||||||
|       _rest DELETE "record/$record_id/$_domain_id" "{}" |       _rest DELETE "record/$record_id/$_domain_id" "{}" | ||||||
|       _info "Deleted record ${fulldomain}" |       _info "Deleted record ${fulldomain}" | ||||||
|     fi |     fi | ||||||
| @ -77,7 +62,6 @@ existing_records() { | |||||||
|   _debug "Getting txt records" |   _debug "Getting txt records" | ||||||
|   root=$1 |   root=$1 | ||||||
|   sub=$2 |   sub=$2 | ||||||
|   count=0 |  | ||||||
|   if ! _rest GET "record/$_domain_id?:domain_id?host_id=0&offset=0&row_num=100"; then |   if ! _rest GET "record/$_domain_id?:domain_id?host_id=0&offset=0&row_num=100"; then | ||||||
|     return 1 |     return 1 | ||||||
|   fi |   fi | ||||||
| @ -89,7 +73,6 @@ existing_records() { | |||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   if printf "%s" "$response" | grep '"type":"TXT"' >/dev/null; then |   if printf "%s" "$response" | grep '"type":"TXT"' >/dev/null; then | ||||||
|     count=1 |  | ||||||
|     record_id=$(printf "%s\n" "$seg" | _egrep_o '"record_id":"[^"]*"' | cut -d : -f 2 | tr -d \" | _head_n 1) |     record_id=$(printf "%s\n" "$seg" | _egrep_o '"record_id":"[^"]*"' | cut -d : -f 2 | tr -d \" | _head_n 1) | ||||||
|     _debug record_id "$record_id" |     _debug record_id "$record_id" | ||||||
|     return 0 |     return 0 | ||||||
| @ -114,23 +97,6 @@ add_record() { | |||||||
|   return 0 |   return 0 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #update the txt record |  | ||||||
| #Usage: root sub txtvalue |  | ||||||
| update_record() { |  | ||||||
|   root=$1 |  | ||||||
|   sub=$2 |  | ||||||
|   txtvalue=$3 |  | ||||||
|   fulldomain="$sub.$root" |  | ||||||
| 
 |  | ||||||
|   _info "Updating record" |  | ||||||
| 
 |  | ||||||
|   if _rest PUT "record/$record_id" "{\"domain_id\": $_domain_id, \"host\":\"$_sub_domain\", \"value\":\"$txtvalue\", \"type\":\"TXT\",\"ttl\":600, \"line_id\":1}"; then |  | ||||||
|     return 0 |  | ||||||
|   fi |  | ||||||
| 
 |  | ||||||
|   return 1 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ####################  Private functions below ################################## | ####################  Private functions below ################################## | ||||||
| #_acme-challenge.www.domain.com | #_acme-challenge.www.domain.com | ||||||
| #returns | #returns | ||||||
|  | |||||||
| @ -15,6 +15,8 @@ dns_dp_add() { | |||||||
|   fulldomain=$1 |   fulldomain=$1 | ||||||
|   txtvalue=$2 |   txtvalue=$2 | ||||||
| 
 | 
 | ||||||
|  |   DP_Id="${DP_Id:-$(_readaccountconf_mutable DP_Id)}" | ||||||
|  |   DP_Key="${DP_Key:-$(_readaccountconf_mutable DP_Key)}" | ||||||
|   if [ -z "$DP_Id" ] || [ -z "$DP_Key" ]; then |   if [ -z "$DP_Id" ] || [ -z "$DP_Key" ]; then | ||||||
|     DP_Id="" |     DP_Id="" | ||||||
|     DP_Key="" |     DP_Key="" | ||||||
| @ -24,8 +26,8 @@ dns_dp_add() { | |||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   #save the api key and email to the account conf file. |   #save the api key and email to the account conf file. | ||||||
|   _saveaccountconf DP_Id "$DP_Id" |   _saveaccountconf_mutable DP_Id "$DP_Id" | ||||||
|   _saveaccountconf DP_Key "$DP_Key" |   _saveaccountconf_mutable DP_Key "$DP_Key" | ||||||
| 
 | 
 | ||||||
|   _debug "First detect the root zone" |   _debug "First detect the root zone" | ||||||
|   if ! _get_root "$fulldomain"; then |   if ! _get_root "$fulldomain"; then | ||||||
| @ -33,24 +35,18 @@ dns_dp_add() { | |||||||
|     return 1 |     return 1 | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   existing_records "$_domain" "$_sub_domain" |  | ||||||
|   _debug count "$count" |  | ||||||
|   if [ "$?" != "0" ]; then |  | ||||||
|     _err "Error get existing records." |  | ||||||
|     return 1 |  | ||||||
|   fi |  | ||||||
| 
 |  | ||||||
|   if [ "$count" = "0" ]; then |  | ||||||
|   add_record "$_domain" "$_sub_domain" "$txtvalue" |   add_record "$_domain" "$_sub_domain" "$txtvalue" | ||||||
|   else | 
 | ||||||
|     update_record "$_domain" "$_sub_domain" "$txtvalue" |  | ||||||
|   fi |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #fulldomain txtvalue | #fulldomain txtvalue | ||||||
| dns_dp_rm() { | dns_dp_rm() { | ||||||
|   fulldomain=$1 |   fulldomain=$1 | ||||||
|   txtvalue=$2 |   txtvalue=$2 | ||||||
|  | 
 | ||||||
|  |   DP_Id="${DP_Id:-$(_readaccountconf_mutable DP_Id)}" | ||||||
|  |   DP_Key="${DP_Key:-$(_readaccountconf_mutable DP_Key)}" | ||||||
|  | 
 | ||||||
|   _debug "First detect the root zone" |   _debug "First detect the root zone" | ||||||
|   if ! _get_root "$fulldomain"; then |   if ! _get_root "$fulldomain"; then | ||||||
|     _err "invalid domain" |     _err "invalid domain" | ||||||
| @ -83,37 +79,6 @@ dns_dp_rm() { | |||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #usage:  root  sub |  | ||||||
| #return if the sub record already exists. |  | ||||||
| #echos the existing records count. |  | ||||||
| # '0' means doesn't exist |  | ||||||
| existing_records() { |  | ||||||
|   _debug "Getting txt records" |  | ||||||
|   root=$1 |  | ||||||
|   sub=$2 |  | ||||||
| 
 |  | ||||||
|   if ! _rest POST "Record.List" "login_token=$DP_Id,$DP_Key&domain_id=$_domain_id&sub_domain=$_sub_domain"; then |  | ||||||
|     return 1 |  | ||||||
|   fi |  | ||||||
| 
 |  | ||||||
|   if _contains "$response" 'No records'; then |  | ||||||
|     count=0 |  | ||||||
|     return 0 |  | ||||||
|   fi |  | ||||||
| 
 |  | ||||||
|   if _contains "$response" "Action completed successful"; then |  | ||||||
|     count=$(printf "%s" "$response" | grep -c '<type>TXT</type>' | tr -d ' ') |  | ||||||
|     record_id=$(printf "%s" "$response" | grep '^<id>' | tail -1 | cut -d '>' -f 2 | cut -d '<' -f 1) |  | ||||||
|     _debug record_id "$record_id" |  | ||||||
|     return 0 |  | ||||||
|   else |  | ||||||
|     _err "get existing records error." |  | ||||||
|     return 1 |  | ||||||
|   fi |  | ||||||
| 
 |  | ||||||
|   count=0 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #add the txt record. | #add the txt record. | ||||||
| #usage: root  sub  txtvalue | #usage: root  sub  txtvalue | ||||||
| add_record() { | add_record() { | ||||||
| @ -128,34 +93,7 @@ add_record() { | |||||||
|     return 1 |     return 1 | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   if _contains "$response" "Action completed successful"; then |   _contains "$response" "Action completed successful" || _contains "$response" "Domain record already exists" | ||||||
| 
 |  | ||||||
|     return 0 |  | ||||||
|   fi |  | ||||||
| 
 |  | ||||||
|   return 1 #error |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #update the txt record |  | ||||||
| #Usage: root sub txtvalue |  | ||||||
| update_record() { |  | ||||||
|   root=$1 |  | ||||||
|   sub=$2 |  | ||||||
|   txtvalue=$3 |  | ||||||
|   fulldomain="$sub.$root" |  | ||||||
| 
 |  | ||||||
|   _info "Updating record" |  | ||||||
| 
 |  | ||||||
|   if ! _rest POST "Record.Modify" "login_token=$DP_Id,$DP_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain&record_type=TXT&value=$txtvalue&record_line=默认&record_id=$record_id"; then |  | ||||||
|     return 1 |  | ||||||
|   fi |  | ||||||
| 
 |  | ||||||
|   if _contains "$response" "Action completed successful"; then |  | ||||||
| 
 |  | ||||||
|     return 0 |  | ||||||
|   fi |  | ||||||
| 
 |  | ||||||
|   return 1 #error |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ####################  Private functions below ################################## | ####################  Private functions below ################################## | ||||||
|  | |||||||
							
								
								
									
										97
									
								
								dnsapi/dns_dreamhost.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								dnsapi/dns_dreamhost.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,97 @@ | |||||||
|  | #!/usr/bin/env sh | ||||||
|  | 
 | ||||||
|  | #Author: RhinoLance | ||||||
|  | #Report Bugs here: https://github.com/RhinoLance/acme.sh | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | #define the api endpoint | ||||||
|  | DH_API_ENDPOINT="https://api.dreamhost.com/" | ||||||
|  | querystring="" | ||||||
|  | 
 | ||||||
|  | ########  Public functions ##################### | ||||||
|  | 
 | ||||||
|  | #Usage: dns_myapi_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" | ||||||
|  | dns_dreamhost_add() { | ||||||
|  |   fulldomain=$1 | ||||||
|  |   txtvalue=$2 | ||||||
|  | 
 | ||||||
|  |   if ! validate "$fulldomain" "$txtvalue"; then | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   querystring="key=$DH_API_KEY&cmd=dns-add_record&record=$fulldomain&type=TXT&value=$txtvalue" | ||||||
|  |   if ! submit "$querystring"; then | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #Usage: fulldomain txtvalue | ||||||
|  | #Remove the txt record after validation. | ||||||
|  | dns_dreamhost_rm() { | ||||||
|  |   fulldomain=$1 | ||||||
|  |   txtvalue=$2 | ||||||
|  | 
 | ||||||
|  |   if ! validate "$fulldomain" "$txtvalue"; then | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   querystring="key=$DH_API_KEY&cmd=dns-remove_record&record=$fulldomain&type=TXT&value=$txtvalue" | ||||||
|  |   if ! submit "$querystring"; then | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ####################  Private functions below ################################## | ||||||
|  | 
 | ||||||
|  | #send the command to the api endpoint. | ||||||
|  | submit() { | ||||||
|  |   querystring=$1 | ||||||
|  | 
 | ||||||
|  |   url="$DH_API_ENDPOINT?$querystring" | ||||||
|  | 
 | ||||||
|  |   _debug url "$url" | ||||||
|  | 
 | ||||||
|  |   if ! response="$(_get "$url")"; then | ||||||
|  |     _err "Error <$1>" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   if [ -z "$2" ]; then | ||||||
|  |     message="$(echo "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")" | ||||||
|  |     if [ -n "$message" ]; then | ||||||
|  |       _err "$message" | ||||||
|  |       return 1 | ||||||
|  |     fi | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _debug response "$response" | ||||||
|  | 
 | ||||||
|  |   return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #check that we have a valid API Key | ||||||
|  | validate() { | ||||||
|  |   fulldomain=$1 | ||||||
|  |   txtvalue=$2 | ||||||
|  | 
 | ||||||
|  |   _info "Using dreamhost" | ||||||
|  |   _debug fulldomain "$fulldomain" | ||||||
|  |   _debug txtvalue "$txtvalue" | ||||||
|  | 
 | ||||||
|  |   #retrieve the API key from the environment variable if it exists, otherwise look for a saved key. | ||||||
|  |   DH_API_KEY="${DH_API_KEY:-$(_readaccountconf_mutable DH_API_KEY)}" | ||||||
|  | 
 | ||||||
|  |   if [ -z "$DH_API_KEY" ]; then | ||||||
|  |     DH_API_KEY="" | ||||||
|  |     _err "You didn't specify the DreamHost api key yet (export DH_API_KEY=\"<api key>\")" | ||||||
|  |     _err "Please login to your control panel, create a key and try again." | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   #save the api key to the account conf file. | ||||||
|  |   _saveaccountconf_mutable DH_API_KEY "$DH_API_KEY" | ||||||
|  | } | ||||||
| @ -15,6 +15,8 @@ dns_gd_add() { | |||||||
|   fulldomain=$1 |   fulldomain=$1 | ||||||
|   txtvalue=$2 |   txtvalue=$2 | ||||||
| 
 | 
 | ||||||
|  |   GD_Key="${GD_Key:-$(_readaccountconf_mutable GD_Key)}" | ||||||
|  |   GD_Secret="${GD_Secret:-$(_readaccountconf_mutable GD_Secret)}" | ||||||
|   if [ -z "$GD_Key" ] || [ -z "$GD_Secret" ]; then |   if [ -z "$GD_Key" ] || [ -z "$GD_Secret" ]; then | ||||||
|     GD_Key="" |     GD_Key="" | ||||||
|     GD_Secret="" |     GD_Secret="" | ||||||
| @ -24,8 +26,8 @@ dns_gd_add() { | |||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   #save the api key and email to the account conf file. |   #save the api key and email to the account conf file. | ||||||
|   _saveaccountconf GD_Key "$GD_Key" |   _saveaccountconf_mutable GD_Key "$GD_Key" | ||||||
|   _saveaccountconf GD_Secret "$GD_Secret" |   _saveaccountconf_mutable GD_Secret "$GD_Secret" | ||||||
| 
 | 
 | ||||||
|   _debug "First detect the root zone" |   _debug "First detect the root zone" | ||||||
|   if ! _get_root "$fulldomain"; then |   if ! _get_root "$fulldomain"; then | ||||||
| @ -36,8 +38,27 @@ dns_gd_add() { | |||||||
|   _debug _sub_domain "$_sub_domain" |   _debug _sub_domain "$_sub_domain" | ||||||
|   _debug _domain "$_domain" |   _debug _domain "$_domain" | ||||||
| 
 | 
 | ||||||
|  |   _debug "Getting existing records" | ||||||
|  |   if ! _gd_rest GET "domains/$_domain/records/TXT/$_sub_domain"; then | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   if _contains "$response" "$txtvalue"; then | ||||||
|  |     _info "The record is existing, skip" | ||||||
|  |     return 0 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _add_data="{\"data\":\"$txtvalue\"}" | ||||||
|  |   for t in $(echo "$response" | tr '{' "\n" | grep "\"name\":\"$_sub_domain\"" | tr ',' "\n" | grep '"data"' | cut -d : -f 2); do | ||||||
|  |     _debug2 t "$t" | ||||||
|  |     if [ "$t" ]; then | ||||||
|  |       _add_data="$_add_data,{\"data\":$t}" | ||||||
|  |     fi | ||||||
|  |   done | ||||||
|  |   _debug2 _add_data "$_add_data" | ||||||
|  | 
 | ||||||
|   _info "Adding record" |   _info "Adding record" | ||||||
|   if _gd_rest PUT "domains/$_domain/records/TXT/$_sub_domain" "[{\"data\":\"$txtvalue\"}]"; then |   if _gd_rest PUT "domains/$_domain/records/TXT/$_sub_domain" "[$_add_data]"; then | ||||||
|     if [ "$response" = "{}" ]; then |     if [ "$response" = "{}" ]; then | ||||||
|       _info "Added, sleeping 10 seconds" |       _info "Added, sleeping 10 seconds" | ||||||
|       _sleep 10 |       _sleep 10 | ||||||
| @ -56,7 +77,47 @@ dns_gd_add() { | |||||||
| #fulldomain | #fulldomain | ||||||
| dns_gd_rm() { | dns_gd_rm() { | ||||||
|   fulldomain=$1 |   fulldomain=$1 | ||||||
|  |   txtvalue=$2 | ||||||
| 
 | 
 | ||||||
|  |   GD_Key="${GD_Key:-$(_readaccountconf_mutable GD_Key)}" | ||||||
|  |   GD_Secret="${GD_Secret:-$(_readaccountconf_mutable GD_Secret)}" | ||||||
|  | 
 | ||||||
|  |   _debug "First detect the root zone" | ||||||
|  |   if ! _get_root "$fulldomain"; then | ||||||
|  |     _err "invalid domain" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _debug _sub_domain "$_sub_domain" | ||||||
|  |   _debug _domain "$_domain" | ||||||
|  | 
 | ||||||
|  |   _debug "Getting existing records" | ||||||
|  |   if ! _gd_rest GET "domains/$_domain/records/TXT/$_sub_domain"; then | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   if ! _contains "$response" "$txtvalue"; then | ||||||
|  |     _info "The record is not existing, skip" | ||||||
|  |     return 0 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _add_data="" | ||||||
|  |   for t in $(echo "$response" | tr '{' "\n" | grep "\"name\":\"$_sub_domain\"" | tr ',' "\n" | grep '"data"' | cut -d : -f 2); do | ||||||
|  |     _debug2 t "$t" | ||||||
|  |     if [ "$t" ] && [ "$t" != "\"$txtvalue\"" ]; then | ||||||
|  |       if [ "$_add_data" ]; then | ||||||
|  |         _add_data="$_add_data,{\"data\":$t}" | ||||||
|  |       else | ||||||
|  |         _add_data="{\"data\":$t}" | ||||||
|  |       fi | ||||||
|  |     fi | ||||||
|  |   done | ||||||
|  |   if [ -z "$_add_data" ]; then | ||||||
|  |     _add_data="{\"data\":\"\"}" | ||||||
|  |   fi | ||||||
|  |   _debug2 _add_data "$_add_data" | ||||||
|  | 
 | ||||||
|  |   _gd_rest PUT "domains/$_domain/records/TXT/$_sub_domain" "[$_add_data]" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ####################  Private functions below ################################## | ####################  Private functions below ################################## | ||||||
|  | |||||||
| @ -75,17 +75,19 @@ dns_he_rm() { | |||||||
|   body="$body&hosted_dns_zoneid=$_zone_id" |   body="$body&hosted_dns_zoneid=$_zone_id" | ||||||
|   body="$body&menu=edit_zone" |   body="$body&menu=edit_zone" | ||||||
|   body="$body&hosted_dns_editzone=" |   body="$body&hosted_dns_editzone=" | ||||||
|   domain_regex="$(echo "$_full_domain" | sed 's/\./\\./g')" # escape dots |  | ||||||
|   _record_id=$(_post "$body" "https://dns.he.net/" \ |  | ||||||
|     | tr -d '\n' \ |  | ||||||
|     | _egrep_o "data=\""${_txt_value}"([^>]+>){6}[^<]+<[^;]+;deleteRecord\('[0-9]+','${domain_regex}','TXT'\)" \ |  | ||||||
|     | _egrep_o "[0-9]+','${domain_regex}','TXT'\)$" \ |  | ||||||
|     | _egrep_o "^[0-9]+" |  | ||||||
|   ) |  | ||||||
|   # The series of egreps above could have been done a bit shorter but |  | ||||||
|   #  I wanted to double-check whether it's the correct record (in case |  | ||||||
|   #  HE changes their website somehow). |  | ||||||
| 
 | 
 | ||||||
|  |   response="$(_post "$body" "https://dns.he.net/")" | ||||||
|  |   _debug2 "response" "$response" | ||||||
|  |   if ! _contains "$response" "$_txt_value"; then | ||||||
|  |     _debug "The txt record is not found, just skip" | ||||||
|  |     return 0 | ||||||
|  |   fi | ||||||
|  |   _record_id="$(echo "$response" | tr -d "#" | sed "s/<tr/#<tr/g" | tr -d "\n" | tr "#" "\n" | grep "$_full_domain" | grep '"dns_tr"' | grep "$_txt_value" | cut -d '"' -f 4)" | ||||||
|  |   _debug2 _record_id "$_record_id" | ||||||
|  |   if [ -z "$_record_id" ]; then | ||||||
|  |     _err "Can not find record id" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|   # Remove the record |   # Remove the record | ||||||
|   body="email=${HE_Username}&pass=${HE_Password}" |   body="email=${HE_Username}&pass=${HE_Password}" | ||||||
|   body="$body&menu=edit_zone" |   body="$body&menu=edit_zone" | ||||||
| @ -108,41 +110,26 @@ dns_he_rm() { | |||||||
| 
 | 
 | ||||||
| ########################## PRIVATE FUNCTIONS ########################### | ########################## PRIVATE FUNCTIONS ########################### | ||||||
| 
 | 
 | ||||||
| #-- _find_zone() ------------------------------------------------------- |  | ||||||
| # Returns the most specific zone found in administration interface. |  | ||||||
| # |  | ||||||
| # Example: |  | ||||||
| # |  | ||||||
| # _find_zone first.second.third.co.uk |  | ||||||
| # |  | ||||||
| # ... will return the first zone that exists in admin out of these: |  | ||||||
| # - "first.second.third.co.uk" |  | ||||||
| # - "second.third.co.uk" |  | ||||||
| # - "third.co.uk" |  | ||||||
| # - "co.uk" <-- unlikely |  | ||||||
| # - "uk"    <-' |  | ||||||
| # |  | ||||||
| # (another approach would be something like this: |  | ||||||
| #   https://github.com/hlandau/acme/blob/master/_doc/dns.hook |  | ||||||
| #   - that's better if there are multiple pages. It's so much simpler. |  | ||||||
| # ) |  | ||||||
| 
 |  | ||||||
| _find_zone() { | _find_zone() { | ||||||
| 
 |  | ||||||
|   _domain="$1" |   _domain="$1" | ||||||
| 
 |  | ||||||
|   body="email=${HE_Username}&pass=${HE_Password}" |   body="email=${HE_Username}&pass=${HE_Password}" | ||||||
|   _matches=$(_post "$body" "https://dns.he.net/" \ |   response="$(_post "$body" "https://dns.he.net/")" | ||||||
|     | _egrep_o "delete_dom.*name=\"[^\"]+\" value=\"[0-9]+" |   _debug2 response "$response" | ||||||
|   ) |   _table="$(echo "$response" | tr -d "#" | sed "s/<table/#<table/g" | tr -d "\n" | tr "#" "\n" | grep 'id="domains_table"')" | ||||||
|  |   _debug2 _table "$_table" | ||||||
|  |   _matches="$(echo "$_table" | sed "s/<tr/#<tr/g" | tr "#" "\n" | grep 'alt="edit"' | tr -d " " | sed "s/<td/#<td/g" | tr "#" "\n" | grep 'hosted_dns_zoneid')" | ||||||
|  |   _debug2 _matches "$_matches" | ||||||
|   # Zone names and zone IDs are in same order |   # Zone names and zone IDs are in same order | ||||||
|   _zone_ids=$(echo "$_matches" | cut -d '"' -f 5) |   _zone_ids=$(echo "$_matches" | _egrep_o "hosted_dns_zoneid=[0-9]*&" | cut -d = -f 2 | tr -d '&') | ||||||
|   _zone_names=$(echo "$_matches" | cut -d '"' -f 3) |   _zone_names=$(echo "$_matches" | _egrep_o "name=.*onclick" | cut -d '"' -f 2) | ||||||
|   _debug2 "These are the zones on this HE account:" |   _debug2 "These are the zones on this HE account:" | ||||||
|   _debug2 "$_zone_names" |   _debug2 "$_zone_names" | ||||||
|   _debug2 "And these are their respective IDs:" |   _debug2 "And these are their respective IDs:" | ||||||
|   _debug2 "$_zone_ids" |   _debug2 "$_zone_ids" | ||||||
| 
 |   if [ -z "$_zone_names" ] || [ -z "$_zone_ids" ]; then | ||||||
|  |     _err "Can not get zone names." | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|   # Walk through all possible zone names |   # Walk through all possible zone names | ||||||
|   _strip_counter=1 |   _strip_counter=1 | ||||||
|   while true; do |   while true; do | ||||||
| @ -156,17 +143,10 @@ _find_zone() { | |||||||
| 
 | 
 | ||||||
|     _debug "Looking for zone \"${_attempted_zone}\"" |     _debug "Looking for zone \"${_attempted_zone}\"" | ||||||
| 
 | 
 | ||||||
|     # Take care of "." and only match whole lines. Note that grep -F |     line_num="$(echo "$_zone_names" | grep -n "$_attempted_zone" | cut -d : -f 1)" | ||||||
|     # cannot be used because there's no way to make it match whole |  | ||||||
|     # lines. |  | ||||||
|     regex="^$(echo "$_attempted_zone" | sed 's/\./\\./g')$" |  | ||||||
|     line_num=$(echo "$_zone_names" \ |  | ||||||
|       | grep -n "$regex" \ |  | ||||||
|       | cut -d : -f 1 |  | ||||||
|     ) |  | ||||||
| 
 | 
 | ||||||
|     if [ -n "$line_num" ]; then |     if [ "$line_num" ]; then | ||||||
|       _zone_id=$(echo "$_zone_ids" | sed "${line_num}q;d") |       _zone_id=$(echo "$_zone_ids" | sed -n "${line_num}p") | ||||||
|       _debug "Found relevant zone \"$_attempted_zone\" with id \"$_zone_id\" - will be used for domain \"$_domain\"." |       _debug "Found relevant zone \"$_attempted_zone\" with id \"$_zone_id\" - will be used for domain \"$_domain\"." | ||||||
|       return 0 |       return 0 | ||||||
|     fi |     fi | ||||||
|  | |||||||
| @ -8,7 +8,6 @@ | |||||||
| #LUA_Email="user@luadns.net" | #LUA_Email="user@luadns.net" | ||||||
| 
 | 
 | ||||||
| LUA_Api="https://api.luadns.com/v1" | LUA_Api="https://api.luadns.com/v1" | ||||||
| LUA_auth=$(printf "%s" "$LUA_Email:$LUA_Key" | _base64) |  | ||||||
| 
 | 
 | ||||||
| ########  Public functions ##################### | ########  Public functions ##################### | ||||||
| 
 | 
 | ||||||
| @ -17,6 +16,10 @@ dns_lua_add() { | |||||||
|   fulldomain=$1 |   fulldomain=$1 | ||||||
|   txtvalue=$2 |   txtvalue=$2 | ||||||
| 
 | 
 | ||||||
|  |   LUA_Key="${LUA_Key:-$(_readaccountconf_mutable LUA_Key)}" | ||||||
|  |   LUA_Email="${LUA_Email:-$(_readaccountconf_mutable LUA_Email)}" | ||||||
|  |   LUA_auth=$(printf "%s" "$LUA_Email:$LUA_Key" | _base64) | ||||||
|  | 
 | ||||||
|   if [ -z "$LUA_Key" ] || [ -z "$LUA_Email" ]; then |   if [ -z "$LUA_Key" ] || [ -z "$LUA_Email" ]; then | ||||||
|     LUA_Key="" |     LUA_Key="" | ||||||
|     LUA_Email="" |     LUA_Email="" | ||||||
| @ -26,8 +29,8 @@ dns_lua_add() { | |||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   #save the api key and email to the account conf file. |   #save the api key and email to the account conf file. | ||||||
|   _saveaccountconf LUA_Key "$LUA_Key" |   _saveaccountconf_mutable LUA_Key "$LUA_Key" | ||||||
|   _saveaccountconf LUA_Email "$LUA_Email" |   _saveaccountconf_mutable LUA_Email "$LUA_Email" | ||||||
| 
 | 
 | ||||||
|   _debug "First detect the root zone" |   _debug "First detect the root zone" | ||||||
|   if ! _get_root "$fulldomain"; then |   if ! _get_root "$fulldomain"; then | ||||||
| @ -38,17 +41,6 @@ dns_lua_add() { | |||||||
|   _debug _sub_domain "$_sub_domain" |   _debug _sub_domain "$_sub_domain" | ||||||
|   _debug _domain "$_domain" |   _debug _domain "$_domain" | ||||||
| 
 | 
 | ||||||
|   _debug "Getting txt records" |  | ||||||
|   _LUA_rest GET "zones/${_domain_id}/records" |  | ||||||
| 
 |  | ||||||
|   if ! _contains "$response" "\"id\":"; then |  | ||||||
|     _err "Error" |  | ||||||
|     return 1 |  | ||||||
|   fi |  | ||||||
| 
 |  | ||||||
|   count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$fulldomain.\",\"type\":\"TXT\"" | wc -l | tr -d " ") |  | ||||||
|   _debug count "$count" |  | ||||||
|   if [ "$count" = "0" ]; then |  | ||||||
|   _info "Adding record" |   _info "Adding record" | ||||||
|   if _LUA_rest POST "zones/$_domain_id/records" "{\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"content\":\"$txtvalue\",\"ttl\":120}"; then |   if _LUA_rest POST "zones/$_domain_id/records" "{\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"content\":\"$txtvalue\",\"ttl\":120}"; then | ||||||
|     if _contains "$response" "$fulldomain"; then |     if _contains "$response" "$fulldomain"; then | ||||||
| @ -60,28 +52,16 @@ dns_lua_add() { | |||||||
|       return 1 |       return 1 | ||||||
|     fi |     fi | ||||||
|   fi |   fi | ||||||
|     _err "Add txt record error." |  | ||||||
|   else |  | ||||||
|     _info "Updating record" |  | ||||||
|     record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*,\"name\":\"$fulldomain.\",\"type\":\"TXT\"" | _head_n 1 | cut -d: -f2 | cut -d, -f1) |  | ||||||
|     _debug "record_id" "$record_id" |  | ||||||
| 
 |  | ||||||
|     _LUA_rest PUT "zones/$_domain_id/records/$record_id" "{\"id\":$record_id,\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"content\":\"$txtvalue\",\"zone_id\":$_domain_id,\"ttl\":120}" |  | ||||||
|     if [ "$?" = "0" ] && _contains "$response" "updated_at"; then |  | ||||||
|       _info "Updated!" |  | ||||||
|       #todo: check if the record takes effect |  | ||||||
|       return 0 |  | ||||||
|     fi |  | ||||||
|     _err "Update error" |  | ||||||
|     return 1 |  | ||||||
|   fi |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #fulldomain | #fulldomain | ||||||
| dns_lua_rm() { | dns_lua_rm() { | ||||||
|   fulldomain=$1 |   fulldomain=$1 | ||||||
|   txtvalue=$2 |   txtvalue=$2 | ||||||
|  | 
 | ||||||
|  |   LUA_Key="${LUA_Key:-$(_readaccountconf_mutable LUA_Key)}" | ||||||
|  |   LUA_Email="${LUA_Email:-$(_readaccountconf_mutable LUA_Email)}" | ||||||
|  |   LUA_auth=$(printf "%s" "$LUA_Email:$LUA_Key" | _base64) | ||||||
|   _debug "First detect the root zone" |   _debug "First detect the root zone" | ||||||
|   if ! _get_root "$fulldomain"; then |   if ! _get_root "$fulldomain"; then | ||||||
|     _err "invalid domain" |     _err "invalid domain" | ||||||
|  | |||||||
| @ -43,9 +43,6 @@ dns_me_add() { | |||||||
|     return 1 |     return 1 | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   count=$(printf "%s\n" "$response" | _egrep_o "\"totalRecords\":[^,]*" | cut -d : -f 2) |  | ||||||
|   _debug count "$count" |  | ||||||
|   if [ "$count" = "0" ]; then |  | ||||||
|   _info "Adding record" |   _info "Adding record" | ||||||
|   if _me_rest POST "$_domain_id/records/" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":\"$txtvalue\",\"gtdLocation\":\"DEFAULT\",\"ttl\":120}"; then |   if _me_rest POST "$_domain_id/records/" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":\"$txtvalue\",\"gtdLocation\":\"DEFAULT\",\"ttl\":120}"; then | ||||||
|     if printf -- "%s" "$response" | grep \"id\": >/dev/null; then |     if printf -- "%s" "$response" | grep \"id\": >/dev/null; then | ||||||
| @ -57,21 +54,6 @@ dns_me_add() { | |||||||
|       return 1 |       return 1 | ||||||
|     fi |     fi | ||||||
|   fi |   fi | ||||||
|     _err "Add txt record error." |  | ||||||
|   else |  | ||||||
|     _info "Updating record" |  | ||||||
|     record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*" | cut -d : -f 2 | head -n 1) |  | ||||||
|     _debug "record_id" "$record_id" |  | ||||||
| 
 |  | ||||||
|     _me_rest PUT "$_domain_id/records/$record_id/" "{\"id\":\"$record_id\",\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":\"$txtvalue\",\"gtdLocation\":\"DEFAULT\",\"ttl\":120}" |  | ||||||
|     if [ "$?" = "0" ]; then |  | ||||||
|       _info "Updated" |  | ||||||
|       #todo: check if the record takes effect |  | ||||||
|       return 0 |  | ||||||
|     fi |  | ||||||
|     _err "Update error" |  | ||||||
|     return 1 |  | ||||||
|   fi |  | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -96,7 +78,7 @@ dns_me_rm() { | |||||||
|   if [ "$count" = "0" ]; then |   if [ "$count" = "0" ]; then | ||||||
|     _info "Don't need to remove." |     _info "Don't need to remove." | ||||||
|   else |   else | ||||||
|     record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*" | cut -d : -f 2 | head -n 1) |     record_id=$(printf "%s\n" "$response" | _egrep_o ",\"value\":\"..$txtvalue..\",\"id\":[^,]*" | cut -d : -f 3 | head -n 1) | ||||||
|     _debug "record_id" "$record_id" |     _debug "record_id" "$record_id" | ||||||
|     if [ -z "$record_id" ]; then |     if [ -z "$record_id" ]; then | ||||||
|       _err "Can not get record id to remove." |       _err "Can not get record id to remove." | ||||||
| @ -152,7 +134,7 @@ _me_rest() { | |||||||
|   data="$3" |   data="$3" | ||||||
|   _debug "$ep" |   _debug "$ep" | ||||||
| 
 | 
 | ||||||
|   cdate=$(date -u +"%a, %d %b %Y %T %Z") |   cdate=$(LANG=C date -u +"%a, %d %b %Y %T %Z") | ||||||
|   hmac=$(printf "%s" "$cdate" | _hmac sha1 "$(printf "%s" "$ME_Secret" | _hex_dump | tr -d " ")" hex) |   hmac=$(printf "%s" "$cdate" | _hmac sha1 "$(printf "%s" "$ME_Secret" | _hex_dump | tr -d " ")" hex) | ||||||
| 
 | 
 | ||||||
|   export _H1="x-dnsme-apiKey: $ME_Key" |   export _H1="x-dnsme-apiKey: $ME_Key" | ||||||
|  | |||||||
| @ -59,10 +59,10 @@ dns_nsone_add() { | |||||||
|     _err "Add txt record error." |     _err "Add txt record error." | ||||||
|   else |   else | ||||||
|     _info "Updating record" |     _info "Updating record" | ||||||
|     record_id=$(printf "%s\n" "$response" | _egrep_o "\"domain\":\"$fulldomain.\",[^{]*\"type\":\"TXT\",\"id\":\"[^,]*\"" | _head_n 1 | cut -d: -f7 | cut -d, -f1) |     prev_txt=$(printf "%s\n" "$response" | _egrep_o "\"domain\":\"$fulldomain\",\"short_answers\":\[\"[^,]*\]" | _head_n 1 | cut -d: -f3 | cut -d, -f1) | ||||||
|     _debug "record_id" "$record_id" |     _debug "prev_txt" "$prev_txt" | ||||||
| 
 | 
 | ||||||
|     _nsone_rest POST "zones/$_domain/$fulldomain/TXT" "{\"answers\": [{\"answer\": [\"$txtvalue\"]}],\"type\": \"TXT\",\"domain\":\"$fulldomain\",\"zone\": \"$_domain\"}" |     _nsone_rest POST "zones/$_domain/$fulldomain/TXT" "{\"answers\": [{\"answer\": [\"$txtvalue\"]},{\"answer\": $prev_txt}],\"type\": \"TXT\",\"domain\":\"$fulldomain\",\"zone\": \"$_domain\"}" | ||||||
|     if [ "$?" = "0" ] && _contains "$response" "$fulldomain"; then |     if [ "$?" = "0" ] && _contains "$response" "$fulldomain"; then | ||||||
|       _info "Updated!" |       _info "Updated!" | ||||||
|       #todo: check if the record takes effect |       #todo: check if the record takes effect | ||||||
|  | |||||||
| @ -79,6 +79,9 @@ _ovh_get_api() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| _initAuth() { | _initAuth() { | ||||||
|  |   OVH_AK="${OVH_AK:-$(_readaccountconf_mutable OVH_AK)}" | ||||||
|  |   OVH_AS="${OVH_AS:-$(_readaccountconf_mutable OVH_AS)}" | ||||||
|  | 
 | ||||||
|   if [ -z "$OVH_AK" ] || [ -z "$OVH_AS" ]; then |   if [ -z "$OVH_AK" ] || [ -z "$OVH_AS" ]; then | ||||||
|     OVH_AK="" |     OVH_AK="" | ||||||
|     OVH_AS="" |     OVH_AS="" | ||||||
| @ -87,21 +90,26 @@ _initAuth() { | |||||||
|     return 1 |     return 1 | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   #save the api key and email to the account conf file. |   if [ "$OVH_AK" != "$(_readaccountconf OVH_AK)" ]; then | ||||||
|   _saveaccountconf OVH_AK "$OVH_AK" |     _info "It seems that your ovh key is changed, let's clear consumer key first." | ||||||
|   _saveaccountconf OVH_AS "$OVH_AS" |     _clearaccountconf OVH_CK | ||||||
|  |   fi | ||||||
|  |   _saveaccountconf_mutable OVH_AK "$OVH_AK" | ||||||
|  |   _saveaccountconf_mutable OVH_AS "$OVH_AS" | ||||||
| 
 | 
 | ||||||
|  |   OVH_END_POINT="${OVH_END_POINT:-$(_readaccountconf_mutable OVH_END_POINT)}" | ||||||
|   if [ -z "$OVH_END_POINT" ]; then |   if [ -z "$OVH_END_POINT" ]; then | ||||||
|     OVH_END_POINT="ovh-eu" |     OVH_END_POINT="ovh-eu" | ||||||
|   fi |   fi | ||||||
|   _info "Using OVH endpoint: $OVH_END_POINT" |   _info "Using OVH endpoint: $OVH_END_POINT" | ||||||
|   if [ "$OVH_END_POINT" != "ovh-eu" ]; then |   if [ "$OVH_END_POINT" != "ovh-eu" ]; then | ||||||
|     _saveaccountconf OVH_END_POINT "$OVH_END_POINT" |     _saveaccountconf_mutable OVH_END_POINT "$OVH_END_POINT" | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   OVH_API="$(_ovh_get_api $OVH_END_POINT)" |   OVH_API="$(_ovh_get_api $OVH_END_POINT)" | ||||||
|   _debug OVH_API "$OVH_API" |   _debug OVH_API "$OVH_API" | ||||||
| 
 | 
 | ||||||
|  |   OVH_CK="${OVH_CK:-$(_readaccountconf_mutable OVH_CK)}" | ||||||
|   if [ -z "$OVH_CK" ]; then |   if [ -z "$OVH_CK" ]; then | ||||||
|     _info "OVH consumer key is empty, Let's get one:" |     _info "OVH consumer key is empty, Let's get one:" | ||||||
|     if ! _ovh_authentication; then |     if ! _ovh_authentication; then | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ dns_yandex_add() { | |||||||
|   _PDD_credentials || return 1 |   _PDD_credentials || return 1 | ||||||
|   export _H1="PddToken: $PDD_Token" |   export _H1="PddToken: $PDD_Token" | ||||||
| 
 | 
 | ||||||
|   curDomain=$(_PDD_get_domain "$fulldomain") |   _PDD_get_domain "$fulldomain" | ||||||
|   _debug "Found suitable domain in pdd: $curDomain" |   _debug "Found suitable domain in pdd: $curDomain" | ||||||
|   curData="domain=${curDomain}&type=TXT&subdomain=${curSubdomain}&ttl=360&content=${txtvalue}" |   curData="domain=${curDomain}&type=TXT&subdomain=${curSubdomain}&ttl=360&content=${txtvalue}" | ||||||
|   curUri="https://pddimp.yandex.ru/api2/admin/dns/add" |   curUri="https://pddimp.yandex.ru/api2/admin/dns/add" | ||||||
| @ -33,7 +33,7 @@ dns_yandex_rm() { | |||||||
|   record_id=$(pdd_get_record_id "${fulldomain}") |   record_id=$(pdd_get_record_id "${fulldomain}") | ||||||
|   _debug "Result: $record_id" |   _debug "Result: $record_id" | ||||||
| 
 | 
 | ||||||
|   curDomain=$(_PDD_get_domain "$fulldomain") |   _PDD_get_domain "$fulldomain" | ||||||
|   _debug "Found suitable domain in pdd: $curDomain" |   _debug "Found suitable domain in pdd: $curDomain" | ||||||
| 
 | 
 | ||||||
|   curUri="https://pddimp.yandex.ru/api2/admin/dns/del" |   curUri="https://pddimp.yandex.ru/api2/admin/dns/del" | ||||||
| @ -72,8 +72,8 @@ _PDD_get_domain() { | |||||||
|       if [ "$d" = "$__t" ]; then |       if [ "$d" = "$__t" ]; then | ||||||
|         p=$(_math $k - 1) |         p=$(_math $k - 1) | ||||||
|         curSubdomain="$(echo "$fulldomain" | cut -d . -f "1-$p")" |         curSubdomain="$(echo "$fulldomain" | cut -d . -f "1-$p")" | ||||||
|         echo "$__t" |         curDomain="$__t" | ||||||
|         return |         return 0 | ||||||
|       fi |       fi | ||||||
|     done |     done | ||||||
|     k=$(_math $k + 1) |     k=$(_math $k + 1) | ||||||
| @ -96,7 +96,7 @@ _PDD_credentials() { | |||||||
| pdd_get_record_id() { | pdd_get_record_id() { | ||||||
|   fulldomain="${1}" |   fulldomain="${1}" | ||||||
| 
 | 
 | ||||||
|   curDomain=$(_PDD_get_domain "$fulldomain") |   _PDD_get_domain "$fulldomain" | ||||||
|   _debug "Found suitable domain in pdd: $curDomain" |   _debug "Found suitable domain in pdd: $curDomain" | ||||||
| 
 | 
 | ||||||
|   curUri="https://pddimp.yandex.ru/api2/admin/dns/list?domain=${curDomain}" |   curUri="https://pddimp.yandex.ru/api2/admin/dns/list?domain=${curDomain}" | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user