mirror of
				https://github.com/hiskang/acme.sh
				synced 2025-10-31 10:27:22 +00:00 
			
		
		
		
	Merge remote-tracking branch 'upstream/master'
This commit is contained in:
		
						commit
						e82ea94bb6
					
				
							
								
								
									
										9
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | |||||||
|  | <!-- | ||||||
|  | 
 | ||||||
|  | Do NOT send pull request to `master` branch. | ||||||
|  | 
 | ||||||
|  | Please send to `dev` branch instead. | ||||||
|  | 
 | ||||||
|  | Any PR to `master` branch will NOT be merged. | ||||||
|  | 
 | ||||||
|  | --> | ||||||
							
								
								
									
										40
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								.travis.yml
									
									
									
									
									
								
							| @ -1,4 +1,9 @@ | |||||||
| language: shell | language: shell | ||||||
|  | sudo: required | ||||||
|  | 
 | ||||||
|  | os: | ||||||
|  |   - linux | ||||||
|  |   - osx | ||||||
| 
 | 
 | ||||||
| env: | env: | ||||||
|   global: |   global: | ||||||
| @ -11,12 +16,37 @@ addons: | |||||||
|     packages: |     packages: | ||||||
|     - shellcheck |     - shellcheck | ||||||
| 
 | 
 | ||||||
|  | install: | ||||||
|  |   - if [ "$TRAVIS_OS_NAME" = 'osx' ]; then | ||||||
|  |       brew update && brew install openssl; | ||||||
|  |       brew info openssl; | ||||||
|  |       ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; | ||||||
|  |       ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/; | ||||||
|  |       ln -s /usr/local/Cellar/openssl/1.0.2j/bin/openssl /usr/local/openssl; | ||||||
|  |       _old_path="$PATH"; | ||||||
|  |       echo "PATH=$PATH"; | ||||||
|  |       export PATH=""; | ||||||
|  |       export OPENSSL_BIN="/usr/local/openssl"; | ||||||
|  |       openssl version 2>&1 || true; | ||||||
|  |       $OPENSSL_BIN version 2>&1 || true; | ||||||
|  |       export PATH="$_old_path"; | ||||||
|  |     fi | ||||||
|  |    | ||||||
| script: | script: | ||||||
|   - curl -sSL $SHFMT_URL -o ~/shfmt |   - echo "TEST_LOCAL=$TEST_LOCAL" | ||||||
|   - chmod +x ~/shfmt |   - echo "NGROK_TOKEN=$(echo "$NGROK_TOKEN" | wc -c)" | ||||||
|   - shellcheck -V |   - command -V openssl && openssl version | ||||||
|   - shellcheck -e SC2021,SC2126,SC2034 **/*.sh && echo "shellcheck OK" |   - if [ "$TRAVIS_OS_NAME" = "linux" ]; then curl -sSL $SHFMT_URL -o ~/shfmt ; fi | ||||||
|   - ~/shfmt -l -w -i 2 . && echo "shfmt OK" || git diff --exit-code || (echo "Run shfmt to fix the formatting issues" && false) |   - if [ "$TRAVIS_OS_NAME" = "linux" ]; then chmod +x ~/shfmt ; fi | ||||||
|  |   - if [ "$TRAVIS_OS_NAME" = "linux" ]; then ~/shfmt -l -w -i 2 . ; fi | ||||||
|  |   - if [ "$TRAVIS_OS_NAME" = "linux" ]; then git diff --exit-code && echo "shfmt OK" ; fi | ||||||
|  |   - if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck -V ; fi | ||||||
|  |   - if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck **/*.sh && echo "shellcheck OK" ; fi | ||||||
|  |   - cd .. | ||||||
|  |   - git clone https://github.com/Neilpang/acmetest.git && cp -r acme.sh acmetest/ && cd acmetest | ||||||
|  |   - if [ "$TRAVIS_OS_NAME" = "linux" -a "$NGROK_TOKEN" ]; then sudo NGROK_TOKEN="$NGROK_TOKEN" ./letest.sh ; fi | ||||||
|  |   - if [ "$TRAVIS_OS_NAME" = "osx" -a "$NGROK_TOKEN" ]; then sudo NGROK_TOKEN="$NGROK_TOKEN" OPENSSL_BIN="$OPENSSL_BIN" ./letest.sh ; fi | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| matrix: | matrix: | ||||||
|   fast_finish: true |   fast_finish: true | ||||||
|  | |||||||
							
								
								
									
										211
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										211
									
								
								README.md
									
									
									
									
									
								
							| @ -1,21 +1,26 @@ | |||||||
| # An ACME Shell script: acme.sh [](https://travis-ci.org/Neilpang/acme.sh) | # An ACME Shell script: acme.sh [](https://travis-ci.org/Neilpang/acme.sh) | ||||||
| - An ACME protocol client written purely in Shell (Unix shell) language. | - An ACME protocol client written purely in Shell (Unix shell) language. | ||||||
| - Fully ACME protocol implementation. | - Full ACME protocol implementation. | ||||||
| - Simple, powerful and very easy to use. You only need 3 minutes to learn. | - Simple, powerful and very easy to use. You only need 3 minutes to learn it. | ||||||
| - Bash, dash and sh compatible. | - Bash, dash and sh compatible. | ||||||
| - Simplest shell script for Let's Encrypt free certificate client. | - Simplest shell script for Let's Encrypt free certificate client. | ||||||
| - Purely written in Shell with no dependencies on python or Let's Encrypt official client. | - Purely written in Shell with no dependencies on python or the official Let's Encrypt client. | ||||||
| - Just one script, to issue, renew and install your certificates automatically. | - Just one script to issue, renew and install your certificates automatically. | ||||||
| - DOES NOT require `root/sudoer` access. | - DOES NOT require `root/sudoer` access. | ||||||
| 
 | 
 | ||||||
| It's probably the `easiest&smallest&smartest` shell script to automatically issue & renew the free certificates from Let's Encrypt. | It's probably the `easiest&smallest&smartest` shell script to automatically issue & renew the free certificates from Let's Encrypt. | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| Wiki: https://github.com/Neilpang/acme.sh/wiki | Wiki: https://github.com/Neilpang/acme.sh/wiki | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | 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) | ||||||
| 
 | 
 | ||||||
| #Tested OS | 
 | ||||||
|  | # Tested OS | ||||||
|  | 
 | ||||||
| | NO | Status| Platform| | | NO | Status| Platform| | ||||||
| |----|-------|---------| | |----|-------|---------| | ||||||
| |1|[](https://github.com/Neilpang/letest#here-are-the-latest-status)| Ubuntu | |1|[](https://github.com/Neilpang/letest#here-are-the-latest-status)| Ubuntu | ||||||
| @ -37,42 +42,41 @@ Wiki: https://github.com/Neilpang/acme.sh/wiki | |||||||
| |17|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/Neilpang/acme.sh/wiki/How-to-run-on-OpenWRT) | |17|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/Neilpang/acme.sh/wiki/How-to-run-on-OpenWRT) | ||||||
| |18|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|SunOS/Solaris | |18|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|SunOS/Solaris | ||||||
| |19|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|Gentoo Linux | |19|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|Gentoo Linux | ||||||
|  | |20|[](https://travis-ci.org/Neilpang/acme.sh)|Mac OSX | ||||||
| 
 | 
 | ||||||
| For all build statuses, check our [daily build project](https://github.com/Neilpang/acmetest): | For all build statuses, check our [daily build project](https://github.com/Neilpang/acmetest): | ||||||
| 
 | 
 | ||||||
| https://github.com/Neilpang/acmetest | https://github.com/Neilpang/acmetest | ||||||
| 
 | 
 | ||||||
| # Supported Mode |  | ||||||
| 
 | 
 | ||||||
| 1. Webroot mode | # Supported modes | ||||||
| 2. Standalone mode |  | ||||||
| 3. Apache mode |  | ||||||
| 4. Dns mode |  | ||||||
| 
 | 
 | ||||||
|  | - Webroot mode | ||||||
|  | - Standalone mode | ||||||
|  | - Apache mode | ||||||
|  | - DNS mode | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # 1. How to install | # 1. How to install | ||||||
| 
 | 
 | ||||||
| ### 1. Install online: | ### 1. Install online | ||||||
| 
 | 
 | ||||||
| Check this project: https://github.com/Neilpang/get.acme.sh | Check this project: https://github.com/Neilpang/get.acme.sh | ||||||
| 
 | 
 | ||||||
| ```bash | ```bash | ||||||
| curl https://get.acme.sh | sh | curl https://get.acme.sh | sh | ||||||
| 
 |  | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| Or: | Or: | ||||||
| 
 | 
 | ||||||
| ```bash | ```bash | ||||||
| wget -O -  https://get.acme.sh | sh | wget -O -  https://get.acme.sh | sh | ||||||
| 
 |  | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ### 2. Or, Install from git: | ### 2. Or, Install from git | ||||||
| 
 | 
 | ||||||
| Clone this project:  | Clone this project and launch installation: | ||||||
| 
 | 
 | ||||||
| ```bash | ```bash | ||||||
| git clone https://github.com/Neilpang/acme.sh.git | git clone https://github.com/Neilpang/acme.sh.git | ||||||
| @ -82,14 +86,14 @@ cd ./acme.sh | |||||||
| 
 | 
 | ||||||
| You `don't have to be root` then, although `it is recommended`. | You `don't have to be root` then, although `it is recommended`. | ||||||
| 
 | 
 | ||||||
| Advanced Installation:  https://github.com/Neilpang/acme.sh/wiki/How-to-install | Advanced Installation: https://github.com/Neilpang/acme.sh/wiki/How-to-install | ||||||
| 
 | 
 | ||||||
| The installer will perform 3 actions: | The installer will perform 3 actions: | ||||||
| 
 | 
 | ||||||
| 1. Create and copy `acme.sh` to your home dir (`$HOME`):  `~/.acme.sh/`. | 1. Create and copy `acme.sh` to your home dir (`$HOME`): `~/.acme.sh/`. | ||||||
| All certs will be placed in this folder. | All certs will be placed in this folder too. | ||||||
| 2. Create alias for: `acme.sh=~/.acme.sh/acme.sh`. | 2. Create alias for: `acme.sh=~/.acme.sh/acme.sh`. | ||||||
| 3. Create everyday cron job to check and renew the cert if needed. | 3. Create daily cron job to check and renew the certs if needed. | ||||||
| 
 | 
 | ||||||
| Cron entry example: | Cron entry example: | ||||||
| 
 | 
 | ||||||
| @ -97,18 +101,17 @@ Cron entry example: | |||||||
| 0 0 * * * "/home/user/.acme.sh"/acme.sh --cron --home "/home/user/.acme.sh" > /dev/null | 0 0 * * * "/home/user/.acme.sh"/acme.sh --cron --home "/home/user/.acme.sh" > /dev/null | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| After the installation, you must close current terminal and reopen again to make the alias take effect. | After the installation, you must close the current terminal and reopen it to make the alias take effect. | ||||||
|  | 
 | ||||||
|  | Ok, you are ready to issue certs now. | ||||||
| 
 | 
 | ||||||
| Ok, you are ready to issue cert now. |  | ||||||
| Show help message: | Show help message: | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| 
 |  | ||||||
| root@v1:~# acme.sh -h | root@v1:~# acme.sh -h | ||||||
| 
 |  | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| # 2. Just issue a cert: | # 2. Just issue a cert | ||||||
| 
 | 
 | ||||||
| **Example 1:** Single domain. | **Example 1:** Single domain. | ||||||
| 
 | 
 | ||||||
| @ -124,51 +127,54 @@ acme.sh --issue -d example.com -d www.example.com -d cp.example.com -w /home/www | |||||||
| 
 | 
 | ||||||
| The parameter `/home/wwwroot/example.com` is the web root folder. You **MUST** have `write access` to this folder. | The parameter `/home/wwwroot/example.com` is the web root folder. You **MUST** have `write access` to this folder. | ||||||
| 
 | 
 | ||||||
| Second argument **"example.com"** is the main domain you want to issue cert for. | Second argument **"example.com"** is the main domain you want to issue the cert for. | ||||||
| You must have at least a domain there. | You must have at least one domain there. | ||||||
| 
 | 
 | ||||||
| You must point and bind all the domains to the same webroot dir: `/home/wwwroot/example.com`. | You must point and bind all the domains to the same webroot dir: `/home/wwwroot/example.com`. | ||||||
| 
 | 
 | ||||||
| Generate/issued certs will be placed in `~/.acme.sh/example.com/` | Generated/issued certs will be placed in `~/.acme.sh/example.com/` | ||||||
| 
 | 
 | ||||||
| The issued cert will be renewed every **60** days automatically. | The issued cert will be renewed automatically every **60** days. | ||||||
| 
 | 
 | ||||||
| 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 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # 3. Install the issued cert to apache/nginx etc. | # 3. Install the issued cert to Apache/Nginx etc. | ||||||
| 
 | 
 | ||||||
| After you issue a cert, you probably want to install/copy the cert to your nginx/apache or other servers.  | After you issue a cert, you probably want to install/copy the cert to your Apache/Nginx or other servers. | ||||||
| You **MUST** use this command to copy the certs to the target files,  **Do NOT** use the certs files in **.acme.sh/** folder, they are for internal use only, the folder structure may change in future. | You **MUST** use this command to copy the certs to the target files, **DO NOT** use the certs files in **~/.acme.sh/** folder, they are for internal use only, the folder structure may change in the future. | ||||||
| 
 | 
 | ||||||
| **nginx** example | **Apache** example: | ||||||
| ```bash | ```bash | ||||||
| acme.sh --installcert -d example.com \ | acme.sh --installcert -d example.com \ | ||||||
| --keypath  /path/to/keyfile/in/nginx/key.pem  \ | --certpath      /path/to/certfile/in/apache/cert.pem  \ | ||||||
| --fullchainpath path/to/fullchain/nginx/cert.pem \ | --keypath       /path/to/keyfile/in/apache/key.pem  \ | ||||||
| --reloadcmd  "service nginx restart" | --fullchainpath /path/to/fullchain/certfile/apache/fullchain.pem \ | ||||||
|  | --reloadcmd     "service apache2 force-reload" | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| **apache** example | **Nginx** example: | ||||||
| ```bash | ```bash | ||||||
| acme.sh --installcert -d example.com \ | acme.sh --installcert -d example.com \ | ||||||
| --certpath /path/to/certfile/in/apache/cert.pem  \ | --keypath       /path/to/keyfile/in/nginx/key.pem  \ | ||||||
| --keypath  /path/to/keyfile/in/apache/key.pem  \ | --fullchainpath /path/to/fullchain/nginx/cert.pem \ | ||||||
| --fullchainpath path/to/fullchain/certfile/apache/fullchain.pem \ | --reloadcmd     "service nginx force-reload" | ||||||
| --reloadcmd  "service apache2 restart" |  | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| Only the domain is required, all the other parameters are optional. | Only the domain is required, all the other parameters are optional. | ||||||
| 
 | 
 | ||||||
| Install/copy the issued cert/key to the production apache or nginx path. | The ownership and permission info of existing files are preserved. You may want to precreate the files to have defined ownership and permission. | ||||||
|  | 
 | ||||||
|  | Install/copy the issued 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 restarted automatically by the command: `service apache2 restart` or `service nginx restart`. | ||||||
| 
 | 
 | ||||||
| The cert will be `renewed every **60** days by default` (which is configurable). Once the cert is renewed, the apache/nginx will be automatically reloaded by the command: `service apache2 reload` or `service nginx reload`. |  | ||||||
| 
 | 
 | ||||||
| # 4. Use Standalone server to issue cert | # 4. Use Standalone server to issue cert | ||||||
| 
 | 
 | ||||||
| **(requires you be root/sudoer, or you have permission to listen tcp 80 port)** | **(requires you to be root/sudoer or have permission to listen on port 80 (TCP))** | ||||||
| 
 | 
 | ||||||
| The tcp `80` port **MUST** be free to listen, otherwise you will be prompted to free the `80` port and try again. | Port `80` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again. | ||||||
| 
 | 
 | ||||||
| ```bash | ```bash | ||||||
| acme.sh --issue --standalone -d example.com -d www.example.com -d cp.example.com | acme.sh --issue --standalone -d example.com -d www.example.com -d cp.example.com | ||||||
| @ -176,13 +182,14 @@ acme.sh --issue --standalone -d example.com -d www.example.com -d cp.example.com | |||||||
| 
 | 
 | ||||||
| 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 | ||||||
| 
 | 
 | ||||||
| # 5. Use Standalone tls server to issue cert |  | ||||||
| 
 | 
 | ||||||
| **(requires you be root/sudoer, or you have permission to listen tcp 443 port)** | # 5. Use Standalone TLS server to issue cert | ||||||
|  | 
 | ||||||
|  | **(requires you to be root/sudoer or have permission to listen on port 443 (TCP))** | ||||||
| 
 | 
 | ||||||
| acme.sh supports `tls-sni-01` validation. | acme.sh supports `tls-sni-01` validation. | ||||||
| 
 | 
 | ||||||
| The tcp `443` port **MUST** be free to listen, otherwise you will be prompted to free the `443` port and try again. | Port `443` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again. | ||||||
| 
 | 
 | ||||||
| ```bash | ```bash | ||||||
| acme.sh --issue --tls -d example.com -d www.example.com -d cp.example.com | acme.sh --issue --tls -d example.com -d www.example.com -d cp.example.com | ||||||
| @ -190,31 +197,33 @@ acme.sh --issue --tls -d example.com -d www.example.com -d cp.example.com | |||||||
| 
 | 
 | ||||||
| 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 | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| # 6. Use Apache mode | # 6. Use Apache mode | ||||||
| 
 | 
 | ||||||
| **(requires you be root/sudoer, since it is required to interact with apache server)** | **(requires you to be root/sudoer, since it is required to interact with Apache server)** | ||||||
| 
 | 
 | ||||||
| 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 should use Apache mode instead. This mode doesn't write any files to your web root folder. | ||||||
| 
 | 
 | ||||||
| Just set string "apache" as the second argument, 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. | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| acme.sh --issue --apache -d example.com -d www.example.com -d user.example.com | acme.sh --issue --apache -d example.com -d www.example.com -d cp.example.com | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| 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 DNS mode: | # 7. Use DNS mode: | ||||||
| 
 | 
 | ||||||
| Support the `dns-01` challenge. | Support the `dns-01` challenge. | ||||||
| 
 | 
 | ||||||
| ```bash | ```bash | ||||||
| acme.sh --issue --dns -d example.com -d www.example.com -d user.example.com | acme.sh --issue --dns -d example.com -d www.example.com -d cp.example.com | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| You should get the output like below: | You should get an output like below: | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| Add the following txt record: | Add the following txt record: | ||||||
| @ -226,7 +235,6 @@ Domain:_acme-challenge.www.example.com | |||||||
| Txt value:9ihDbjxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | Txt value:9ihDbjxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | ||||||
| 
 | 
 | ||||||
| Please add those txt records to the domains. Waiting for the dns to take effect. | Please add those txt records to the domains. Waiting for the dns to take effect. | ||||||
| 
 |  | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| Then just rerun with `renew` argument: | Then just rerun with `renew` argument: | ||||||
| @ -237,52 +245,60 @@ acme.sh --renew -d example.com | |||||||
| 
 | 
 | ||||||
| Ok, it's finished. | Ok, it's finished. | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| # 8. Automatic DNS API integration | # 8. Automatic DNS API integration | ||||||
| 
 | 
 | ||||||
| If your DNS provider supports API access, we can use API to automatically issue the certs. | If your DNS provider supports API access, we can use that API to automatically issue the certs. | ||||||
| 
 | 
 | ||||||
| You don't have do anything manually! | You don't have to do anything manually! | ||||||
| 
 | 
 | ||||||
| ### Currently acme.sh supports: | ### Currently acme.sh supports: | ||||||
| 
 | 
 | ||||||
| 1. Cloudflare.com API | 1. CloudFlare.com API | ||||||
| 2. Dnspod.cn API | 1. DNSPod.cn API | ||||||
| 3. Cloudxns.com API | 1. CloudXNS.com API | ||||||
| 4. Godaddy.com API | 1. GoDaddy.com API | ||||||
| 5. OVH, kimsufi, soyoustart and runabove API | 1. OVH, kimsufi, soyoustart and runabove API | ||||||
| 6. AWS Route 53, see: https://github.com/Neilpang/acme.sh/issues/65 | 1. AWS Route 53 | ||||||
| 7. PowerDNS API | 1. PowerDNS.com API | ||||||
| 8. lexicon dns api: https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api | 1. 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.) | ||||||
| 9. LuaDNS.com API | 1. LuaDNS.com API | ||||||
| 10. DNSMadeEasy.com API | 1. DNSMadeEasy.com API | ||||||
|  | 1. nsupdate API | ||||||
|  | 1. aliyun.com(阿里云) API | ||||||
|  | 1. ISPConfig 3.1 API | ||||||
|  | 1. Alwaysdata.com API | ||||||
|  | 1. Linode.com API | ||||||
|  | 1. FreeDNS (https://freedns.afraid.org/) | ||||||
| 
 | 
 | ||||||
| ##### More APIs are coming soon... | **More APIs coming soon...** | ||||||
| 
 | 
 | ||||||
| If your DNS provider is not on the supported list above, you can write your own script API easily. If you do please consider submitting a [Pull Request](https://github.com/Neilpang/acme.sh/pulls) and contribute to the project. | If your DNS provider is not on the supported list above, you can write your own DNS API script easily. If you do, please consider submitting a [Pull Request](https://github.com/Neilpang/acme.sh/pulls) and contribute it to the project. | ||||||
| 
 | 
 | ||||||
| For more details: [How to use dns api](dnsapi) | For more details: [How to use DNS API](dnsapi) | ||||||
| 
 | 
 | ||||||
| # 9. Issue ECC certificate: |  | ||||||
| 
 | 
 | ||||||
| `Let's Encrypt` now can issue **ECDSA** certificates. | # 9. Issue ECC certificates | ||||||
| 
 | 
 | ||||||
| And we also support it. | `Let's Encrypt` can now issue **ECDSA** certificates. | ||||||
|  | 
 | ||||||
|  | And we support them too! | ||||||
| 
 | 
 | ||||||
| Just set the `length` parameter with a prefix `ec-`. | Just set the `length` parameter with a prefix `ec-`. | ||||||
| 
 | 
 | ||||||
| For example: | For example: | ||||||
| 
 | 
 | ||||||
| ### Single domain ECC cerfiticate: | ### Single domain ECC cerfiticate | ||||||
| 
 | 
 | ||||||
| ```bash | ```bash | ||||||
| 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 --keylength ec-256 | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| SAN multi domain ECC certificate: | ### SAN multi domain ECC certificate | ||||||
| 
 | 
 | ||||||
| ```bash | ```bash | ||||||
| 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 last parameter above. | ||||||
| @ -294,40 +310,48 @@ Valid values are: | |||||||
| 3. **ec-521 (secp521r1,  "ECDSA P-521", which is not supported by Let's Encrypt yet.)** | 3. **ec-521 (secp521r1,  "ECDSA P-521", which is not supported by Let's Encrypt yet.)** | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # 10. How to renew the cert | # 10. How to renew the issued certs | ||||||
| 
 | 
 | ||||||
| No, you don't need to renew the certs manually.  All the certs will be renewed automatically every **60** days. | No, you don't need to renew the certs manually. All the certs will be renewed automatically every **60** days. | ||||||
| 
 | 
 | ||||||
| However, you can also force to renew any cert: | However, you can also force to renew any cert: | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| acme.sh --renew  -d  example.com --force | acme.sh --renew -d example.com --force | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| or, for ECC cert: | or, for ECC cert: | ||||||
|  | 
 | ||||||
| ``` | ``` | ||||||
| acme.sh --renew  -d  example.com  --force --ecc | acme.sh --renew -d example.com --force --ecc | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| # 11. How to upgrade `acme.sh` | # 11. How to upgrade `acme.sh` | ||||||
| acme.sh is in developing, it's strongly recommended to use the latest code. | 
 | ||||||
|  | acme.sh is in constant development, so it's strongly recommended to use the latest code. | ||||||
| 
 | 
 | ||||||
| You can update acme.sh to the latest code: | You can update acme.sh to the latest code: | ||||||
|  | 
 | ||||||
| ``` | ``` | ||||||
| acme.sh --upgrade | acme.sh --upgrade | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| You can enable auto upgrade: | You can also enable auto upgrade: | ||||||
|  | 
 | ||||||
| ``` | ``` | ||||||
| acme.sh  --upgrade  --auto-upgrade | acme.sh --upgrade --auto-upgrade | ||||||
| ``` | ``` | ||||||
| Then **acme.sh** will keep up to date automatically. | 
 | ||||||
|  | Then **acme.sh** will be kept up to date automatically. | ||||||
| 
 | 
 | ||||||
| Disable auto upgrade: | Disable auto upgrade: | ||||||
|  | 
 | ||||||
| ``` | ``` | ||||||
| acme.sh  --upgrade  --auto-upgrade 0 | acme.sh --upgrade --auto-upgrade 0 | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| # 12. Issue a cert from an existing CSR | # 12. Issue a cert from an existing CSR | ||||||
| 
 | 
 | ||||||
| https://github.com/Neilpang/acme.sh/wiki/Issue-a-cert-from-existing-CSR | https://github.com/Neilpang/acme.sh/wiki/Issue-a-cert-from-existing-CSR | ||||||
| @ -339,22 +363,25 @@ Speak ACME language using shell, directly to "Let's Encrypt". | |||||||
| 
 | 
 | ||||||
| TODO: | TODO: | ||||||
| 
 | 
 | ||||||
| # Acknowledgment | 
 | ||||||
|  | # Acknowledgments | ||||||
|  | 
 | ||||||
| 1. Acme-tiny: https://github.com/diafygi/acme-tiny | 1. Acme-tiny: https://github.com/diafygi/acme-tiny | ||||||
| 2. ACME protocol: https://github.com/ietf-wg-acme/acme | 2. ACME protocol: https://github.com/ietf-wg-acme/acme | ||||||
| 3. Certbot: https://github.com/certbot/certbot | 3. Certbot: https://github.com/certbot/certbot | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| # License & Others | # License & Others | ||||||
| 
 | 
 | ||||||
| License is GPLv3 | License is GPLv3 | ||||||
| 
 | 
 | ||||||
| Please Star and Fork me. | Please Star and Fork me. | ||||||
| 
 | 
 | ||||||
| [Issues](https://github.com/Neilpang/acme.sh/issues) and [pull requests](https://github.com/Neilpang/acme.sh/pulls) are welcomed. | [Issues](https://github.com/Neilpang/acme.sh/issues) and [pull requests](https://github.com/Neilpang/acme.sh/pulls) are welcome. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # Donate | # Donate | ||||||
| 1. PayPal:  donate@acme.sh | 
 | ||||||
|  | 1. PayPal: donate@acme.sh | ||||||
| 
 | 
 | ||||||
| [Donate List](https://github.com/Neilpang/acme.sh/wiki/Donate-list) | [Donate List](https://github.com/Neilpang/acme.sh/wiki/Donate-list) | ||||||
| 
 |  | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								deploy/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								deploy/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | #Using deploy api | ||||||
							
								
								
									
										81
									
								
								deploy/kong.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								deploy/kong.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,81 @@ | |||||||
|  | #!/usr/bin/env sh | ||||||
|  | 
 | ||||||
|  | # This deploy hook will deploy ssl cert on kong proxy engine based on api request_host parameter. | ||||||
|  | # Note that ssl plugin should be available on Kong instance | ||||||
|  | # The hook will match cdomain to request_host, in case of multiple domain it will always take the first | ||||||
|  | # one (acme.sh behaviour). | ||||||
|  | # If ssl config already exist it will update only cert and key not touching other parameter | ||||||
|  | # If ssl config doesn't exist it will only upload cert and key and not set other parameter | ||||||
|  | # Not that we deploy full chain | ||||||
|  | # See https://getkong.org/plugins/dynamic-ssl/ for other options | ||||||
|  | # Written by Geoffroi Genot <ggenot@voxbone.com> | ||||||
|  | 
 | ||||||
|  | ########  Public functions ##################### | ||||||
|  | 
 | ||||||
|  | #domain keyfile certfile cafile fullchain | ||||||
|  | kong_deploy() { | ||||||
|  |   _cdomain="$1" | ||||||
|  |   _ckey="$2" | ||||||
|  |   _ccert="$3" | ||||||
|  |   _cca="$4" | ||||||
|  |   _cfullchain="$5" | ||||||
|  |   _info "Deploying certificate on Kong instance" | ||||||
|  |   if [ -z "$KONG_URL" ]; then | ||||||
|  |     _debug "KONG_URL Not set, using default http://localhost:8001" | ||||||
|  |     KONG_URL="http://localhost:8001" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _debug _cdomain "$_cdomain" | ||||||
|  |   _debug _ckey "$_ckey" | ||||||
|  |   _debug _ccert "$_ccert" | ||||||
|  |   _debug _cca "$_cca" | ||||||
|  |   _debug _cfullchain "$_cfullchain" | ||||||
|  | 
 | ||||||
|  |   #Get uuid linked to the domain | ||||||
|  |   uuid=$(_get "$KONG_URL/apis?request_host=$_cdomain" | _normalizeJson | _egrep_o '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') | ||||||
|  |   if [ -z "$uuid" ]; then | ||||||
|  |     _err "Unable to get Kong uuid for domain $_cdomain" | ||||||
|  |     _err "Make sure that KONG_URL is correctly configured" | ||||||
|  |     _err "Make sure that a Kong api request_host match the domain" | ||||||
|  |     _err "Kong url: $KONG_URL" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  |   #Save kong url if it's succesful (First run case) | ||||||
|  |   _saveaccountconf KONG_URL "$KONG_URL" | ||||||
|  |   #Generate DEIM | ||||||
|  |   delim="-----MultipartDelimeter$(date "+%s%N")" | ||||||
|  |   nl="\015\012" | ||||||
|  |   #Set Header | ||||||
|  |   _H1="Content-Type: multipart/form-data; boundary=$delim" | ||||||
|  |   #Generate data for request (Multipart/form-data with mixed content) | ||||||
|  |   #set name to ssl | ||||||
|  |   content="--$delim${nl}Content-Disposition: form-data; name=\"name\"${nl}${nl}ssl" | ||||||
|  |   #add key | ||||||
|  |   content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"config.key\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")" | ||||||
|  |   #Add cert | ||||||
|  |   content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"config.cert\"; filename=\"$(basename "$_cfullchain")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cfullchain")" | ||||||
|  |   #Close multipart | ||||||
|  |   content="$content${nl}--$delim--${nl}" | ||||||
|  |   #Convert CRLF | ||||||
|  |   content=$(printf %b "$content") | ||||||
|  |   #DEBUG | ||||||
|  |   _debug header "$_H1" | ||||||
|  |   _debug content "$content" | ||||||
|  |   #Check if ssl plugins is aready enabled (if not => POST else => PATCH) | ||||||
|  |   ssl_uuid=$(_get "$KONG_URL/apis/$uuid/plugins" | _egrep_o '"id":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"[a-zA-Z0-9\-\,\"_\:]*"name":"ssl"' | _egrep_o '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') | ||||||
|  |   _debug ssl_uuid "$ssl_uuid" | ||||||
|  |   if [ -z "$ssl_uuid" ]; then | ||||||
|  |     #Post certificate to Kong | ||||||
|  |     response=$(_post "$content" "$KONG_URL/apis/$uuid/plugins" "" "POST") | ||||||
|  |   else | ||||||
|  |     #patch | ||||||
|  |     response=$(_post "$content" "$KONG_URL/apis/$uuid/plugins/$ssl_uuid" "" "PATCH") | ||||||
|  |   fi | ||||||
|  |   if ! [ "$(echo "$response" | _egrep_o "ssl")" = "ssl" ]; then | ||||||
|  |     _err "An error occured with cert upload. Check response:" | ||||||
|  |     _err "$response" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  |   _debug response "$response" | ||||||
|  |   _info "Certificate successfully deployed" | ||||||
|  | } | ||||||
							
								
								
									
										291
									
								
								dnsapi/README.md
									
									
									
									
									
								
							
							
						
						
									
										291
									
								
								dnsapi/README.md
									
									
									
									
									
								
							| @ -1,99 +1,80 @@ | |||||||
| # How to use dns api | # How to use DNS API | ||||||
| 
 | 
 | ||||||
| ## Use CloudFlare domain api to automatically issue cert | ## 1. Use CloudFlare domain API to automatically issue cert | ||||||
| 
 | 
 | ||||||
| For now, we support clourflare integeration. | First you need to login to your CloudFlare account to get your API key. | ||||||
| 
 |  | ||||||
| First you need to login to your clourflare account to get your api key. |  | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| export CF_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" | export CF_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" | ||||||
| 
 |  | ||||||
| export CF_Email="xxxx@sss.com" | export CF_Email="xxxx@sss.com" | ||||||
| 
 |  | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| Ok, let's issue cert now: | Ok, let's issue a cert now: | ||||||
| ``` | ``` | ||||||
| acme.sh   --issue   --dns dns_cf   -d example.com  -d www.example.com | acme.sh --issue --dns dns_cf -d example.com -d www.example.com | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| The `CF_Key` and `CF_Email`  will be saved in `~/.acme.sh/account.conf`, when next time you use cloudflare api, it will reuse this key. | The `CF_Key` and `CF_Email` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | ## 2. Use DNSPod.cn domain API to automatically issue cert | ||||||
| 
 | 
 | ||||||
| ## Use Dnspod.cn domain api to automatically issue cert | First you need to login to your DNSPod account to get your API Key and ID. | ||||||
| 
 |  | ||||||
| For now, we support dnspod.cn integeration. |  | ||||||
| 
 |  | ||||||
| First you need to login to your dnspod.cn account to get your api key and key id. |  | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| export DP_Id="1234" | export DP_Id="1234" | ||||||
| 
 |  | ||||||
| export DP_Key="sADDsdasdgdsf" | export DP_Key="sADDsdasdgdsf" | ||||||
| 
 |  | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| Ok, let's issue cert now: | Ok, let's issue a cert now: | ||||||
| ``` | ``` | ||||||
| acme.sh   --issue   --dns dns_dp   -d example.com  -d www.example.com | acme.sh --issue --dns dns_dp -d example.com -d www.example.com | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| The `DP_Id` and `DP_Key`  will be saved in `~/.acme.sh/account.conf`, when next time you use dnspod.cn api, it will reuse this key. | The `DP_Id` and `DP_Key` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ## Use Cloudxns.com domain api to automatically issue cert | ## 3. Use CloudXNS.com domain API to automatically issue cert | ||||||
| 
 | 
 | ||||||
| For now, we support Cloudxns.com integeration. | First you need to login to your CloudXNS account to get your API Key and Secret. | ||||||
| 
 |  | ||||||
| First you need to login to your Cloudxns.com account to get your api key and key secret. |  | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| export CX_Key="1234" | export CX_Key="1234" | ||||||
| 
 |  | ||||||
| export CX_Secret="sADDsdasdgdsf" | export CX_Secret="sADDsdasdgdsf" | ||||||
| 
 |  | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| Ok, let's issue cert now: | Ok, let's issue a cert now: | ||||||
| ``` | ``` | ||||||
| acme.sh   --issue   --dns dns_cx   -d example.com  -d www.example.com | acme.sh --issue --dns dns_cx -d example.com -d www.example.com | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| The `CX_Key` and `CX_Secret`  will be saved in `~/.acme.sh/account.conf`, when next time you use Cloudxns.com api, it will reuse this key. | The `CX_Key` and `CX_Secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ## Use Godaddy.com domain api to automatically issue cert | ## 4. Use GoDaddy.com domain API to automatically issue cert | ||||||
| 
 | 
 | ||||||
| We support Godaddy integration. | First you need to login to your GoDaddy account to get your API Key and Secret. | ||||||
| 
 |  | ||||||
| First you need to login to your Godaddy account to get your api key and api secret. |  | ||||||
| 
 | 
 | ||||||
| https://developer.godaddy.com/keys/ | https://developer.godaddy.com/keys/ | ||||||
| 
 | 
 | ||||||
| Please Create a Production key, instead of a Test key. | Please create a Production key, instead of a Test key. | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| export GD_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" | export GD_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" | ||||||
| 
 |  | ||||||
| export GD_Secret="asdfsdafdsfdsfdsfdsfdsafd" | export GD_Secret="asdfsdafdsfdsfdsfdsfdsafd" | ||||||
| 
 |  | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| Ok, let's issue cert now: | Ok, let's issue a cert now: | ||||||
| ``` | ``` | ||||||
| acme.sh   --issue   --dns dns_gd   -d example.com  -d www.example.com | acme.sh --issue --dns dns_gd -d example.com -d www.example.com | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| The `GD_Key` and `GD_Secret`  will be saved in `~/.acme.sh/account.conf`, when next time you use cloudflare api, it will reuse this key. | The `GD_Key` and `GD_Secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. | ||||||
| 
 | 
 | ||||||
| ## Use PowerDNS embedded api to automatically issue cert |  | ||||||
| 
 | 
 | ||||||
| We support PowerDNS embedded API integration. | ## 5. Use PowerDNS embedded API to automatically issue cert | ||||||
| 
 | 
 | ||||||
| First you need to enable api and set your api-token in PowerDNS configuration. | First you need to login to your PowerDNS account to enable the API and set your API-Token in the configuration. | ||||||
| 
 | 
 | ||||||
| https://doc.powerdns.com/md/httpapi/README/ | https://doc.powerdns.com/md/httpapi/README/ | ||||||
| 
 | 
 | ||||||
| @ -102,75 +83,245 @@ export PDNS_Url="http://ns.example.com:8081" | |||||||
| export PDNS_ServerId="localhost" | export PDNS_ServerId="localhost" | ||||||
| export PDNS_Token="0123456789ABCDEF" | export PDNS_Token="0123456789ABCDEF" | ||||||
| export PDNS_Ttl=60 | export PDNS_Ttl=60 | ||||||
| 
 |  | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| Ok, let's issue cert now: | Ok, let's issue a cert now: | ||||||
| ``` | ``` | ||||||
| acme.sh   --issue   --dns dns_pdns   -d example.com  -d www.example.com | acme.sh --issue --dns dns_pdns -d example.com -d www.example.com | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| The `PDNS_Url`, `PDNS_ServerId`, `PDNS_Token` and `PDNS_Ttl` will be saved in `~/.acme.sh/account.conf`. | The `PDNS_Url`, `PDNS_ServerId`, `PDNS_Token` and `PDNS_Ttl` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. | ||||||
| 
 | 
 | ||||||
| ## Use OVH/kimsufi/soyoustart/runabove API | 
 | ||||||
|  | ## 6. Use OVH/kimsufi/soyoustart/runabove API to automatically issue cert | ||||||
| 
 | 
 | ||||||
| https://github.com/Neilpang/acme.sh/wiki/How-to-use-OVH-domain-api | https://github.com/Neilpang/acme.sh/wiki/How-to-use-OVH-domain-api | ||||||
| 
 | 
 | ||||||
| # Use custom api |  | ||||||
| 
 | 
 | ||||||
| If your api is not supported yet,  you can write your own dns api. | ## 7. Use nsupdate to automatically issue cert | ||||||
| 
 |  | ||||||
| Let's assume you want to name it 'myapi', |  | ||||||
| 
 |  | ||||||
| 1. Create a bash script named  `~/.acme.sh/dns_myapi.sh`, |  | ||||||
| 2. In the script, you must have a function named `dns_myapi_add()`. Which will be called by acme.sh to add dns records. |  | ||||||
| 3. Then you can use your api to issue cert like: |  | ||||||
| 
 | 
 | ||||||
|  | First, generate a key for updating the zone | ||||||
| ``` | ``` | ||||||
| acme.sh  --issue  --dns  dns_myapi  -d example.com  -d www.example.com | b=$(dnssec-keygen -a hmac-sha512 -b 512 -n USER -K /tmp foo) | ||||||
|  | cat > /etc/named/keys/update.key <<EOF | ||||||
|  | key "update" { | ||||||
|  |     algorithm hmac-sha512; | ||||||
|  |     secret "$(awk '/^Key/{print $2}' /tmp/$b.private)"; | ||||||
|  | }; | ||||||
|  | EOF | ||||||
|  | rm -f /tmp/$b.{private,key} | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| For more details, please check our sample script: [dns_myapi.sh](dns_myapi.sh) | Include this key in your named configuration | ||||||
|  | ``` | ||||||
|  | include "/etc/named/keys/update.key"; | ||||||
|  | ``` | ||||||
| 
 | 
 | ||||||
| # Use lexicon dns api | Next, configure your zone to allow dynamic updates. | ||||||
| 
 | 
 | ||||||
| https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api | Depending on your named version, use either | ||||||
|  | ``` | ||||||
|  | zone "example.com" { | ||||||
|  |     type master; | ||||||
|  |     allow-update { key "update"; }; | ||||||
|  | }; | ||||||
|  | ``` | ||||||
|  | or | ||||||
|  | ``` | ||||||
|  | zone "example.com" { | ||||||
|  |     type master; | ||||||
|  |     update-policy { | ||||||
|  |         grant update subdomain example.com.; | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | ``` | ||||||
| 
 | 
 | ||||||
| ## Use LuaDNS domain API | Finally, make the DNS server and update Key available to `acme.sh` | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | export NSUPDATE_SERVER="dns.example.com" | ||||||
|  | export NSUPDATE_KEY="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa==" | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Ok, let's issue a cert now: | ||||||
|  | ``` | ||||||
|  | acme.sh --issue --dns dns_nsupdate -d example.com -d www.example.com | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | The `NSUPDATE_SERVER` and `NSUPDATE_KEY` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ## 8. Use LuaDNS domain API | ||||||
| 
 | 
 | ||||||
| Get your API token at https://api.luadns.com/settings | Get your API token at https://api.luadns.com/settings | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| export LUA_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" | export LUA_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" | ||||||
| 
 |  | ||||||
| export LUA_Email="xxxx@sss.com" | export LUA_Email="xxxx@sss.com" | ||||||
| 
 |  | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| To issue a cert: | To issue a cert: | ||||||
| ``` | ``` | ||||||
| acme.sh   --issue   --dns dns_lua --dnssleep 3  -d example.com  -d www.example.com | acme.sh --issue --dns dns_lua -d example.com -d www.example.com | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| The `LUA_Key` and `LUA_Email`  will be saved in `~/.acme.sh/account.conf`, and will be reused when needed. | The `LUA_Key` and `LUA_Email` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. | ||||||
| 
 | 
 | ||||||
| ## Use DNSMadeEasy domain API | 
 | ||||||
|  | ## 9. Use DNSMadeEasy domain API | ||||||
| 
 | 
 | ||||||
| Get your API credentials at https://cp.dnsmadeeasy.com/account/info | Get your API credentials at https://cp.dnsmadeeasy.com/account/info | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| export ME_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" | export ME_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" | ||||||
| 
 |  | ||||||
| export ME_Secret="qdfqsdfkjdskfj" | export ME_Secret="qdfqsdfkjdskfj" | ||||||
| 
 |  | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| To issue a cert: | To issue a cert: | ||||||
| ``` | ``` | ||||||
| acme.sh   --issue   --dns dns_me --dnssleep 3  -d example.com  -d www.example.com | acme.sh --issue --dns dns_me -d example.com -d www.example.com | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| The `ME_Key` and `ME_Secret`  will be saved in `~/.acme.sh/account.conf`, and will be reused when needed. | The `ME_Key` and `ME_Secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | ## 10. Use Amazon Route53 domain API | ||||||
| 
 | 
 | ||||||
|  | https://github.com/Neilpang/acme.sh/wiki/How-to-use-Amazon-Route53-API | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | export  AWS_ACCESS_KEY_ID=XXXXXXXXXX | ||||||
|  | export  AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXX | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | To issue a cert: | ||||||
|  | ``` | ||||||
|  | acme.sh --issue --dns dns_aws -d example.com -d www.example.com | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | The `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. | ||||||
|  | 
 | ||||||
|  | ## 11. Use Aliyun domain API to automatically issue cert | ||||||
|  | 
 | ||||||
|  | First you need to login to your Aliyun account to get your API key. | ||||||
|  | [https://ak-console.aliyun.com/#/accesskey](https://ak-console.aliyun.com/#/accesskey) | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | export Ali_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" | ||||||
|  | export Ali_Secret="jlsdflanljkljlfdsaklkjflsa" | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Ok, let's issue a cert now: | ||||||
|  | ``` | ||||||
|  | acme.sh --issue --dns dns_ali -d example.com -d www.example.com | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | The `Ali_Key` and `Ali_Secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. | ||||||
|  | 
 | ||||||
|  | ## 12. Use ISPConfig 3.1 API | ||||||
|  | 
 | ||||||
|  | This only works for ISPConfig 3.1 (and newer). | ||||||
|  | 
 | ||||||
|  | Create a Remote User in the ISPConfig Control Panel. The Remote User must have access to at least `DNS zone functions` and `DNS txt functions`. | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | export ISPC_User="xxx" | ||||||
|  | export ISPC_Password="xxx" | ||||||
|  | export ISPC_Api="https://ispc.domain.tld:8080/remote/json.php" | ||||||
|  | export ISPC_Api_Insecure=1 | ||||||
|  | ``` | ||||||
|  | If you have installed ISPConfig on a different port, then alter the 8080 accordingly. | ||||||
|  | Leaver ISPC_Api_Insecure set to 1 if you have not a valid ssl cert for your installation. Change it to 0 if you have a valid ssl cert. | ||||||
|  | 
 | ||||||
|  | To issue a cert: | ||||||
|  | ``` | ||||||
|  | acme.sh --issue --dns dns_ispconfig -d example.com -d www.example.com | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | The `ISPC_User`, `ISPC_Password`, `ISPC_Api`and `ISPC_Api_Insecure` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. | ||||||
|  | 
 | ||||||
|  | ## 13. Use Alwaysdata domain API | ||||||
|  | 
 | ||||||
|  | First you need to login to your Alwaysdata account to get your API Key. | ||||||
|  | 
 | ||||||
|  | ```sh | ||||||
|  | export AD_API_KEY="myalwaysdataapikey" | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Ok, let's issue a cert now: | ||||||
|  | 
 | ||||||
|  | ```sh | ||||||
|  | acme.sh --issue --dns dns_ad -d example.com -d www.example.com | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | The `AD_API_KEY` will be saved in `~/.acme.sh/account.conf` and will be reused | ||||||
|  | when needed. | ||||||
|  | 
 | ||||||
|  | ## 14. Use Linode domain API | ||||||
|  | 
 | ||||||
|  | First you need to login to your Linode account to get your API Key. | ||||||
|  | [https://manager.linode.com/profile/api](https://manager.linode.com/profile/api) | ||||||
|  | 
 | ||||||
|  | Then add an API key with label *ACME* and copy the new key. | ||||||
|  | 
 | ||||||
|  | ```sh | ||||||
|  | export LINODE_API_KEY="..." | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Due to the reload time of any changes in the DNS records, we have to use the `dnssleep` option to wait at least 15 minutes for the changes to take effect. | ||||||
|  | 
 | ||||||
|  | Ok, let's issue a cert now: | ||||||
|  | 
 | ||||||
|  | ```sh | ||||||
|  | acme.sh --issue --dns dns_linode --dnssleep 900 -d example.com -d www.example.com | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | The `LINODE_API_KEY` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. | ||||||
|  | 
 | ||||||
|  | ## 15. Use FreeDNS | ||||||
|  | 
 | ||||||
|  | FreeDNS (https://freedns.afraid.org/) does not provide an API to update DNS records (other than IPv4 and IPv6 | ||||||
|  | dynamic DNS addresses).  The acme.sh plugin therefore retrieves and updates domain TXT records by logging | ||||||
|  | into the FreeDNS website to read the HTML and posting updates as HTTP.  The plugin needs to know your | ||||||
|  | userid and password for the FreeDNS website. | ||||||
|  | 
 | ||||||
|  | ```sh | ||||||
|  | export FREEDNS_User="..." | ||||||
|  | export FREEDNS_Password="..." | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | You need only provide this the first time you run the acme.sh client with FreeDNS validation and then again | ||||||
|  | whenever you change your password at the FreeDNS site.  The acme.sh FreeDNS plugin does not store your userid | ||||||
|  | or password but rather saves an authentication token returned by FreeDNS in `~/.acme.sh/account.conf` and | ||||||
|  | reuses that when needed. | ||||||
|  | 
 | ||||||
|  | Now you can issue a certificate. | ||||||
|  | 
 | ||||||
|  | ```sh | ||||||
|  | acme.sh --issue --dns dns_freedns -d example.com -d www.example.com | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Note that you cannot use acme.sh automatic DNS validation for FreeDNS public domains or for a subdomain that | ||||||
|  | you create under a FreeDNS public domain.  You must own the top level domain in order to automaitcally | ||||||
|  | validate with acme.sh at FreeDNS. | ||||||
|  | 
 | ||||||
|  | # Use custom API | ||||||
|  | 
 | ||||||
|  | If your API is not supported yet, you can write your own DNS API. | ||||||
|  | 
 | ||||||
|  | Let's assume you want to name it 'myapi': | ||||||
|  | 
 | ||||||
|  | 1. Create a bash script named `~/.acme.sh/dns_myapi.sh`, | ||||||
|  | 2. In the script you must have a function named `dns_myapi_add()` which will be called by acme.sh to add the DNS records. | ||||||
|  | 3. Then you can use your API to issue cert like this: | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | acme.sh --issue --dns dns_myapi -d example.com -d www.example.com | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | For more details, please check our sample script: [dns_myapi.sh](dns_myapi.sh) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Use lexicon DNS API | ||||||
|  | 
 | ||||||
|  | https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api | ||||||
|  | |||||||
							
								
								
									
										147
									
								
								dnsapi/dns_ad.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								dnsapi/dns_ad.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,147 @@ | |||||||
|  | #!/usr/bin/env sh | ||||||
|  | 
 | ||||||
|  | # | ||||||
|  | #AD_API_KEY="sdfsdfsdfljlbjkljlkjsdfoiwje" | ||||||
|  | 
 | ||||||
|  | #This is the Alwaysdata api wrapper for acme.sh | ||||||
|  | # | ||||||
|  | #Author: Paul Koppen | ||||||
|  | #Report Bugs here: https://github.com/wpk-/acme.sh | ||||||
|  | 
 | ||||||
|  | AD_API_URL="https://$AD_API_KEY:@api.alwaysdata.com/v1" | ||||||
|  | 
 | ||||||
|  | ########  Public functions ##################### | ||||||
|  | 
 | ||||||
|  | #Usage: dns_myapi_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" | ||||||
|  | dns_ad_add() { | ||||||
|  |   fulldomain=$1 | ||||||
|  |   txtvalue=$2 | ||||||
|  | 
 | ||||||
|  |   if [ -z "$AD_API_KEY" ]; then | ||||||
|  |     AD_API_KEY="" | ||||||
|  |     _err "You didn't specify the AD api key yet." | ||||||
|  |     _err "Please create you key and try again." | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _saveaccountconf AD_API_KEY "$AD_API_KEY" | ||||||
|  | 
 | ||||||
|  |   _debug "First detect the root zone" | ||||||
|  |   if ! _get_root "$fulldomain"; then | ||||||
|  |     _err "invalid domain" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  |   _debug _domain_id "$_domain_id" | ||||||
|  |   _debug _sub_domain "$_sub_domain" | ||||||
|  |   _debug _domain "$_domain" | ||||||
|  | 
 | ||||||
|  |   _ad_tmpl_json="{\"domain\":$_domain_id,\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":\"$txtvalue\"}" | ||||||
|  | 
 | ||||||
|  |   if _ad_rest POST "record/" "$_ad_tmpl_json" && [ -z "$response" ]; then | ||||||
|  |     _info "txt record updated success." | ||||||
|  |     return 0 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   return 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #fulldomain txtvalue | ||||||
|  | dns_ad_rm() { | ||||||
|  |   fulldomain=$1 | ||||||
|  |   txtvalue=$2 | ||||||
|  | 
 | ||||||
|  |   _debug "First detect the root zone" | ||||||
|  |   if ! _get_root "$fulldomain"; then | ||||||
|  |     _err "invalid domain" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  |   _debug _domain_id "$_domain_id" | ||||||
|  |   _debug _sub_domain "$_sub_domain" | ||||||
|  |   _debug _domain "$_domain" | ||||||
|  | 
 | ||||||
|  |   _debug "Getting txt records" | ||||||
|  |   _ad_rest GET "record/?domain=$_domain_id&name=$_sub_domain" | ||||||
|  | 
 | ||||||
|  |   if [ -n "$response" ]; then | ||||||
|  |     record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1) | ||||||
|  |     _debug record_id "$record_id" | ||||||
|  |     if [ -z "$record_id" ]; then | ||||||
|  |       _err "Can not get record id to remove." | ||||||
|  |       return 1 | ||||||
|  |     fi | ||||||
|  |     if _ad_rest DELETE "record/$record_id/" && [ -z "$response" ]; then | ||||||
|  |       _info "txt record deleted success." | ||||||
|  |       return 0 | ||||||
|  |     fi | ||||||
|  |     _debug response "$response" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   return 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ####################  Private functions below ################################## | ||||||
|  | #_acme-challenge.www.domain.com | ||||||
|  | #returns | ||||||
|  | # _sub_domain=_acme-challenge.www | ||||||
|  | # _domain=domain.com | ||||||
|  | # _domain_id=12345 | ||||||
|  | _get_root() { | ||||||
|  |   domain=$1 | ||||||
|  |   i=2 | ||||||
|  |   p=1 | ||||||
|  | 
 | ||||||
|  |   if _ad_rest GET "domain/"; then | ||||||
|  |     response="$(echo "$response" | tr -d "\n" | sed 's/{/\n&/g')" | ||||||
|  |     while true; do | ||||||
|  |       h=$(printf "%s" "$domain" | cut -d . -f $i-100) | ||||||
|  |       _debug h "$h" | ||||||
|  |       if [ -z "$h" ]; then | ||||||
|  |         #not valid | ||||||
|  |         return 1 | ||||||
|  |       fi | ||||||
|  | 
 | ||||||
|  |       hostedzone="$(echo "$response" | _egrep_o "{.*\"name\":\s*\"$h\".*}")" | ||||||
|  |       if [ "$hostedzone" ]; then | ||||||
|  |         _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) | ||||||
|  |         if [ "$_domain_id" ]; then | ||||||
|  |           _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) | ||||||
|  |           _domain=$h | ||||||
|  |           return 0 | ||||||
|  |         fi | ||||||
|  |         return 1 | ||||||
|  |       fi | ||||||
|  |       p=$i | ||||||
|  |       i=$(_math "$i" + 1) | ||||||
|  |     done | ||||||
|  |   fi | ||||||
|  |   return 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #method uri qstr data | ||||||
|  | _ad_rest() { | ||||||
|  |   mtd="$1" | ||||||
|  |   ep="$2" | ||||||
|  |   data="$3" | ||||||
|  | 
 | ||||||
|  |   _debug mtd "$mtd" | ||||||
|  |   _debug ep "$ep" | ||||||
|  | 
 | ||||||
|  |   export _H1="Accept: application/json" | ||||||
|  |   export _H2="Content-Type: application/json" | ||||||
|  | 
 | ||||||
|  |   if [ "$mtd" != "GET" ]; then | ||||||
|  |     # both POST and DELETE. | ||||||
|  |     _debug data "$data" | ||||||
|  |     response="$(_post "$data" "$AD_API_URL/$ep" "" "$mtd")" | ||||||
|  |   else | ||||||
|  |     response="$(_get "$AD_API_URL/$ep")" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   if [ "$?" != "0" ]; then | ||||||
|  |     _err "error $ep" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  |   _debug2 response "$response" | ||||||
|  |   return 0 | ||||||
|  | } | ||||||
							
								
								
									
										187
									
								
								dnsapi/dns_ali.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								dnsapi/dns_ali.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,187 @@ | |||||||
|  | #!/usr/bin/env sh | ||||||
|  | 
 | ||||||
|  | Ali_API="https://alidns.aliyuncs.com/" | ||||||
|  | 
 | ||||||
|  | #Ali_Key="LTqIA87hOKdjevsf5" | ||||||
|  | #Ali_Secret="0p5EYueFNq501xnCPzKNbx6K51qPH2" | ||||||
|  | 
 | ||||||
|  | #Usage: dns_ali_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" | ||||||
|  | dns_ali_add() { | ||||||
|  |   fulldomain=$1 | ||||||
|  |   txtvalue=$2 | ||||||
|  | 
 | ||||||
|  |   if [ -z "$Ali_Key" ] || [ -z "$Ali_Secret" ]; then | ||||||
|  |     Ali_Key="" | ||||||
|  |     Ali_Secret="" | ||||||
|  |     _err "You don't specify aliyun api key and secret yet." | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   #save the api key and secret to the account conf file. | ||||||
|  |   _saveaccountconf Ali_Key "$Ali_Key" | ||||||
|  |   _saveaccountconf Ali_Secret "$Ali_Secret" | ||||||
|  | 
 | ||||||
|  |   _debug "First detect the root zone" | ||||||
|  |   if ! _get_root "$fulldomain"; then | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _debug "Add record" | ||||||
|  |   _add_record_query "$_domain" "$_sub_domain" "$txtvalue" && _ali_rest "Add record" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | dns_ali_rm() { | ||||||
|  |   fulldomain=$1 | ||||||
|  |   _clean | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ####################  Private functions below ################################## | ||||||
|  | 
 | ||||||
|  | _get_root() { | ||||||
|  |   domain=$1 | ||||||
|  |   i=2 | ||||||
|  |   p=1 | ||||||
|  |   while true; do | ||||||
|  |     h=$(printf "%s" "$domain" | cut -d . -f $i-100) | ||||||
|  |     if [ -z "$h" ]; then | ||||||
|  |       #not valid | ||||||
|  |       return 1 | ||||||
|  |     fi | ||||||
|  | 
 | ||||||
|  |     _describe_records_query "$h" | ||||||
|  |     if ! _ali_rest "Get root" "ignore"; then | ||||||
|  |       return 1 | ||||||
|  |     fi | ||||||
|  | 
 | ||||||
|  |     if _contains "$response" "PageNumber"; then | ||||||
|  |       _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) | ||||||
|  |       _debug _sub_domain "$_sub_domain" | ||||||
|  |       _domain="$h" | ||||||
|  |       _debug _domain "$_domain" | ||||||
|  |       return 0 | ||||||
|  |     fi | ||||||
|  |     p="$i" | ||||||
|  |     i=$(_math "$i" + 1) | ||||||
|  |   done | ||||||
|  |   return 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | _ali_rest() { | ||||||
|  |   signature=$(printf "%s" "GET&%2F&$(_ali_urlencode "$query")" | _hmac "sha1" "$(printf "%s" "$Ali_Secret&" | _hex_dump | tr -d " ")" | _base64) | ||||||
|  |   signature=$(_ali_urlencode "$signature") | ||||||
|  |   url="$Ali_API?$query&Signature=$signature" | ||||||
|  | 
 | ||||||
|  |   if ! response="$(_get "$url")"; then | ||||||
|  |     _err "Error <$1>" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   if [ -z "$2" ]; then | ||||||
|  |     message="$(printf "%s" "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")" | ||||||
|  |     if [ -n "$message" ]; then | ||||||
|  |       _err "$message" | ||||||
|  |       return 1 | ||||||
|  |     fi | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _debug2 response "$response" | ||||||
|  |   return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | _ali_urlencode() { | ||||||
|  |   _str="$1" | ||||||
|  |   _str_len=${#_str} | ||||||
|  |   _u_i=1 | ||||||
|  |   while [ "$_u_i" -le "$_str_len" ]; do | ||||||
|  |     _str_c="$(printf "%s" "$_str" | cut -c "$_u_i")" | ||||||
|  |     case $_str_c in [a-zA-Z0-9.~_-]) | ||||||
|  |       printf "%s" "$_str_c" | ||||||
|  |       ;; | ||||||
|  |     *) | ||||||
|  |       printf "%%%02X" "'$_str_c" | ||||||
|  |       ;; | ||||||
|  |     esac | ||||||
|  |     _u_i="$(_math "$_u_i" + 1)" | ||||||
|  |   done | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | _ali_nonce() { | ||||||
|  |   #_head_n 1 </dev/urandom | _digest "sha256" hex | cut -c 1-31 | ||||||
|  |   #Not so good... | ||||||
|  |   date +"%s%N" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | _check_exist_query() { | ||||||
|  |   query='' | ||||||
|  |   query=$query'AccessKeyId='$Ali_Key | ||||||
|  |   query=$query'&Action=DescribeDomainRecords' | ||||||
|  |   query=$query'&DomainName='$1 | ||||||
|  |   query=$query'&Format=json' | ||||||
|  |   query=$query'&RRKeyWord=_acme-challenge' | ||||||
|  |   query=$query'&SignatureMethod=HMAC-SHA1' | ||||||
|  |   query=$query"&SignatureNonce=$(_ali_nonce)" | ||||||
|  |   query=$query'&SignatureVersion=1.0' | ||||||
|  |   query=$query'&Timestamp='$(_timestamp) | ||||||
|  |   query=$query'&TypeKeyWord=TXT' | ||||||
|  |   query=$query'&Version=2015-01-09' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | _add_record_query() { | ||||||
|  |   query='' | ||||||
|  |   query=$query'AccessKeyId='$Ali_Key | ||||||
|  |   query=$query'&Action=AddDomainRecord' | ||||||
|  |   query=$query'&DomainName='$1 | ||||||
|  |   query=$query'&Format=json' | ||||||
|  |   query=$query'&RR='$2 | ||||||
|  |   query=$query'&SignatureMethod=HMAC-SHA1' | ||||||
|  |   query=$query"&SignatureNonce=$(_ali_nonce)" | ||||||
|  |   query=$query'&SignatureVersion=1.0' | ||||||
|  |   query=$query'&Timestamp='$(_timestamp) | ||||||
|  |   query=$query'&Type=TXT' | ||||||
|  |   query=$query'&Value='$3 | ||||||
|  |   query=$query'&Version=2015-01-09' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | _delete_record_query() { | ||||||
|  |   query='' | ||||||
|  |   query=$query'AccessKeyId='$Ali_Key | ||||||
|  |   query=$query'&Action=DeleteDomainRecord' | ||||||
|  |   query=$query'&Format=json' | ||||||
|  |   query=$query'&RecordId='$1 | ||||||
|  |   query=$query'&SignatureMethod=HMAC-SHA1' | ||||||
|  |   query=$query"&SignatureNonce=$(_ali_nonce)" | ||||||
|  |   query=$query'&SignatureVersion=1.0' | ||||||
|  |   query=$query'&Timestamp='$(_timestamp) | ||||||
|  |   query=$query'&Version=2015-01-09' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | _describe_records_query() { | ||||||
|  |   query='' | ||||||
|  |   query=$query'AccessKeyId='$Ali_Key | ||||||
|  |   query=$query'&Action=DescribeDomainRecords' | ||||||
|  |   query=$query'&DomainName='$1 | ||||||
|  |   query=$query'&Format=json' | ||||||
|  |   query=$query'&SignatureMethod=HMAC-SHA1' | ||||||
|  |   query=$query"&SignatureNonce=$(_ali_nonce)" | ||||||
|  |   query=$query'&SignatureVersion=1.0' | ||||||
|  |   query=$query'&Timestamp='$(_timestamp) | ||||||
|  |   query=$query'&Version=2015-01-09' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | _clean() { | ||||||
|  |   _check_exist_query "$_domain" | ||||||
|  |   if ! _ali_rest "Check exist records" "ignore"; then | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   records="$(echo "$response" -n | _egrep_o "\"RecordId\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")" | ||||||
|  |   printf "%s" "$records" \ | ||||||
|  |     | while read -r record_id; do | ||||||
|  |       _delete_record_query "$record_id" | ||||||
|  |       _ali_rest "Delete record $record_id" "ignore" | ||||||
|  |     done | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | _timestamp() { | ||||||
|  |   date -u +"%Y-%m-%dT%H%%3A%M%%3A%SZ" | ||||||
|  | } | ||||||
							
								
								
									
										227
									
								
								dnsapi/dns_aws.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								dnsapi/dns_aws.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,227 @@ | |||||||
|  | #!/usr/bin/env sh | ||||||
|  | 
 | ||||||
|  | # | ||||||
|  | #AWS_ACCESS_KEY_ID="sdfsdfsdfljlbjkljlkjsdfoiwje" | ||||||
|  | # | ||||||
|  | #AWS_SECRET_ACCESS_KEY="xxxxxxx" | ||||||
|  | 
 | ||||||
|  | #This is the Amazon Route53 api wrapper for acme.sh | ||||||
|  | 
 | ||||||
|  | AWS_HOST="route53.amazonaws.com" | ||||||
|  | AWS_URL="https://$AWS_HOST" | ||||||
|  | 
 | ||||||
|  | AWS_WIKI="https://github.com/Neilpang/acme.sh/wiki/How-to-use-Amazon-Route53-API" | ||||||
|  | 
 | ||||||
|  | ########  Public functions ##################### | ||||||
|  | 
 | ||||||
|  | #Usage: dns_myapi_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" | ||||||
|  | dns_aws_add() { | ||||||
|  |   fulldomain=$1 | ||||||
|  |   txtvalue=$2 | ||||||
|  | 
 | ||||||
|  |   if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then | ||||||
|  |     AWS_ACCESS_KEY_ID="" | ||||||
|  |     AWS_SECRET_ACCESS_KEY="" | ||||||
|  |     _err "You don't specify aws route53 api key id and and api key secret yet." | ||||||
|  |     _err "Please create you key and try again. see $(__green $AWS_WIKI)" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   if [ -z "$AWS_SESSION_TOKEN" ]; then | ||||||
|  |     _saveaccountconf AWS_ACCESS_KEY_ID "$AWS_ACCESS_KEY_ID" | ||||||
|  |     _saveaccountconf AWS_SECRET_ACCESS_KEY "$AWS_SECRET_ACCESS_KEY" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _debug "First detect the root zone" | ||||||
|  |   if ! _get_root "$fulldomain"; then | ||||||
|  |     _err "invalid domain" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  |   _debug _domain_id "$_domain_id" | ||||||
|  |   _debug _sub_domain "$_sub_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>" | ||||||
|  | 
 | ||||||
|  |   if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then | ||||||
|  |     _info "txt record updated success." | ||||||
|  |     return 0 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   return 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #fulldomain txtvalue | ||||||
|  | dns_aws_rm() { | ||||||
|  |   fulldomain=$1 | ||||||
|  |   txtvalue=$2 | ||||||
|  | 
 | ||||||
|  |   _debug "First detect the root zone" | ||||||
|  |   if ! _get_root "$fulldomain"; then | ||||||
|  |     _err "invalid domain" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  |   _debug _domain_id "$_domain_id" | ||||||
|  |   _debug _sub_domain "$_sub_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>" | ||||||
|  | 
 | ||||||
|  |   if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then | ||||||
|  |     _info "txt record deleted success." | ||||||
|  |     return 0 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   return 1 | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ####################  Private functions below ################################## | ||||||
|  | 
 | ||||||
|  | _get_root() { | ||||||
|  |   domain=$1 | ||||||
|  |   i=2 | ||||||
|  |   p=1 | ||||||
|  | 
 | ||||||
|  |   if aws_rest GET "2013-04-01/hostedzone"; then | ||||||
|  |     _debug "response" "$response" | ||||||
|  |     while true; do | ||||||
|  |       h=$(printf "%s" "$domain" | cut -d . -f $i-100) | ||||||
|  |       if [ -z "$h" ]; then | ||||||
|  |         #not valid | ||||||
|  |         return 1 | ||||||
|  |       fi | ||||||
|  | 
 | ||||||
|  |       if _contains "$response" "<Name>$h.</Name>"; then | ||||||
|  |         hostedzone="$(echo "$response" | _egrep_o "<HostedZone><Id>[^<]*<.Id><Name>$h.<.Name>.*<.HostedZone>")" | ||||||
|  |         _debug hostedzone "$hostedzone" | ||||||
|  |         if [ -z "$hostedzone" ]; then | ||||||
|  |           _err "Error, can not get hostedzone." | ||||||
|  |           return 1 | ||||||
|  |         fi | ||||||
|  |         _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "<Id>.*<.Id>" | head -n 1 | _egrep_o ">.*<" | tr -d "<>") | ||||||
|  |         if [ "$_domain_id" ]; then | ||||||
|  |           _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) | ||||||
|  |           _domain=$h | ||||||
|  |           return 0 | ||||||
|  |         fi | ||||||
|  |         return 1 | ||||||
|  |       fi | ||||||
|  |       p=$i | ||||||
|  |       i=$(_math "$i" + 1) | ||||||
|  |     done | ||||||
|  |   fi | ||||||
|  |   return 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #method uri qstr data | ||||||
|  | aws_rest() { | ||||||
|  |   mtd="$1" | ||||||
|  |   ep="$2" | ||||||
|  |   qsr="$3" | ||||||
|  |   data="$4" | ||||||
|  | 
 | ||||||
|  |   _debug mtd "$mtd" | ||||||
|  |   _debug ep "$ep" | ||||||
|  |   _debug qsr "$qsr" | ||||||
|  |   _debug data "$data" | ||||||
|  | 
 | ||||||
|  |   CanonicalURI="/$ep" | ||||||
|  |   _debug2 CanonicalURI "$CanonicalURI" | ||||||
|  | 
 | ||||||
|  |   CanonicalQueryString="$qsr" | ||||||
|  |   _debug2 CanonicalQueryString "$CanonicalQueryString" | ||||||
|  | 
 | ||||||
|  |   RequestDate="$(date -u +"%Y%m%dT%H%M%SZ")" | ||||||
|  |   _debug2 RequestDate "$RequestDate" | ||||||
|  | 
 | ||||||
|  |   #RequestDate="20161120T141056Z" ############## | ||||||
|  | 
 | ||||||
|  |   export _H1="x-amz-date: $RequestDate" | ||||||
|  | 
 | ||||||
|  |   aws_host="$AWS_HOST" | ||||||
|  |   CanonicalHeaders="host:$aws_host\nx-amz-date:$RequestDate\n" | ||||||
|  |   SignedHeaders="host;x-amz-date" | ||||||
|  |   if [ -n "$AWS_SESSION_TOKEN" ]; then | ||||||
|  |     export _H2="x-amz-security-token: $AWS_SESSION_TOKEN" | ||||||
|  |     CanonicalHeaders="${CanonicalHeaders}x-amz-security-token:$AWS_SESSION_TOKEN\n" | ||||||
|  |     SignedHeaders="${SignedHeaders};x-amz-security-token" | ||||||
|  |   fi | ||||||
|  |   _debug2 CanonicalHeaders "$CanonicalHeaders" | ||||||
|  |   _debug2 SignedHeaders "$SignedHeaders" | ||||||
|  | 
 | ||||||
|  |   RequestPayload="$data" | ||||||
|  |   _debug2 RequestPayload "$RequestPayload" | ||||||
|  | 
 | ||||||
|  |   Hash="sha256" | ||||||
|  | 
 | ||||||
|  |   CanonicalRequest="$mtd\n$CanonicalURI\n$CanonicalQueryString\n$CanonicalHeaders\n$SignedHeaders\n$(printf "%s" "$RequestPayload" | _digest "$Hash" hex)" | ||||||
|  |   _debug2 CanonicalRequest "$CanonicalRequest" | ||||||
|  | 
 | ||||||
|  |   HashedCanonicalRequest="$(printf "$CanonicalRequest%s" | _digest "$Hash" hex)" | ||||||
|  |   _debug2 HashedCanonicalRequest "$HashedCanonicalRequest" | ||||||
|  | 
 | ||||||
|  |   Algorithm="AWS4-HMAC-SHA256" | ||||||
|  |   _debug2 Algorithm "$Algorithm" | ||||||
|  | 
 | ||||||
|  |   RequestDateOnly="$(echo "$RequestDate" | cut -c 1-8)" | ||||||
|  |   _debug2 RequestDateOnly "$RequestDateOnly" | ||||||
|  | 
 | ||||||
|  |   Region="us-east-1" | ||||||
|  |   Service="route53" | ||||||
|  | 
 | ||||||
|  |   CredentialScope="$RequestDateOnly/$Region/$Service/aws4_request" | ||||||
|  |   _debug2 CredentialScope "$CredentialScope" | ||||||
|  | 
 | ||||||
|  |   StringToSign="$Algorithm\n$RequestDate\n$CredentialScope\n$HashedCanonicalRequest" | ||||||
|  | 
 | ||||||
|  |   _debug2 StringToSign "$StringToSign" | ||||||
|  | 
 | ||||||
|  |   kSecret="AWS4$AWS_SECRET_ACCESS_KEY" | ||||||
|  | 
 | ||||||
|  |   #kSecret="wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY" ############################ | ||||||
|  | 
 | ||||||
|  |   _debug2 kSecret "$kSecret" | ||||||
|  | 
 | ||||||
|  |   kSecretH="$(printf "%s" "$kSecret" | _hex_dump | tr -d " ")" | ||||||
|  |   _debug2 kSecretH "$kSecretH" | ||||||
|  | 
 | ||||||
|  |   kDateH="$(printf "$RequestDateOnly%s" | _hmac "$Hash" "$kSecretH" hex)" | ||||||
|  |   _debug2 kDateH "$kDateH" | ||||||
|  | 
 | ||||||
|  |   kRegionH="$(printf "$Region%s" | _hmac "$Hash" "$kDateH" hex)" | ||||||
|  |   _debug2 kRegionH "$kRegionH" | ||||||
|  | 
 | ||||||
|  |   kServiceH="$(printf "$Service%s" | _hmac "$Hash" "$kRegionH" hex)" | ||||||
|  |   _debug2 kServiceH "$kServiceH" | ||||||
|  | 
 | ||||||
|  |   kSigningH="$(printf "aws4_request%s" | _hmac "$Hash" "$kServiceH" hex)" | ||||||
|  |   _debug2 kSigningH "$kSigningH" | ||||||
|  | 
 | ||||||
|  |   signature="$(printf "$StringToSign%s" | _hmac "$Hash" "$kSigningH" hex)" | ||||||
|  |   _debug2 signature "$signature" | ||||||
|  | 
 | ||||||
|  |   Authorization="$Algorithm Credential=$AWS_ACCESS_KEY_ID/$CredentialScope, SignedHeaders=$SignedHeaders, Signature=$signature" | ||||||
|  |   _debug2 Authorization "$Authorization" | ||||||
|  | 
 | ||||||
|  |   _H3="Authorization: $Authorization" | ||||||
|  |   _debug _H3 "$_H3" | ||||||
|  | 
 | ||||||
|  |   url="$AWS_URL/$ep" | ||||||
|  | 
 | ||||||
|  |   if [ "$mtd" = "GET" ]; then | ||||||
|  |     response="$(_get "$url")" | ||||||
|  |   else | ||||||
|  |     response="$(_post "$data" "$url")" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _ret="$?" | ||||||
|  |   if [ "$_ret" = "0" ]; then | ||||||
|  |     if _contains "$response" "<ErrorResponse"; then | ||||||
|  |       _err "Response error:$response" | ||||||
|  |       return 1 | ||||||
|  |     fi | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   return "$_ret" | ||||||
|  | } | ||||||
| @ -22,6 +22,12 @@ dns_cf_add() { | |||||||
|     return 1 |     return 1 | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|  |   if ! _contains "$CF_Email" "@"; then | ||||||
|  |     _err "It seems that the CF_Email=$CF_Email is not a valid email address." | ||||||
|  |     _err "Please check and retry." | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|   #save the api key and email to the account conf file. |   #save the api key and email to the account conf file. | ||||||
|   _saveaccountconf CF_Key "$CF_Key" |   _saveaccountconf CF_Key "$CF_Key" | ||||||
|   _saveaccountconf CF_Email "$CF_Email" |   _saveaccountconf CF_Email "$CF_Email" | ||||||
| @ -49,9 +55,7 @@ dns_cf_add() { | |||||||
|     _info "Adding record" |     _info "Adding record" | ||||||
|     if _cf_rest POST "zones/$_domain_id/dns_records" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then |     if _cf_rest POST "zones/$_domain_id/dns_records" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then | ||||||
|       if printf -- "%s" "$response" | grep "$fulldomain" >/dev/null; then |       if printf -- "%s" "$response" | grep "$fulldomain" >/dev/null; then | ||||||
|         _info "Added, sleeping 10 seconds" |         _info "Added, OK" | ||||||
|         sleep 10 |  | ||||||
|         #todo: check if the record takes effect |  | ||||||
|         return 0 |         return 0 | ||||||
|       else |       else | ||||||
|         _err "Add txt record error." |         _err "Add txt record error." | ||||||
| @ -66,9 +70,7 @@ dns_cf_add() { | |||||||
| 
 | 
 | ||||||
|     _cf_rest PUT "zones/$_domain_id/dns_records/$record_id" "{\"id\":\"$record_id\",\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"zone_id\":\"$_domain_id\",\"zone_name\":\"$_domain\"}" |     _cf_rest PUT "zones/$_domain_id/dns_records/$record_id" "{\"id\":\"$record_id\",\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"zone_id\":\"$_domain_id\",\"zone_name\":\"$_domain\"}" | ||||||
|     if [ "$?" = "0" ]; then |     if [ "$?" = "0" ]; then | ||||||
|       _info "Updated, sleeping 10 seconds" |       _info "Updated, OK" | ||||||
|       sleep 10 |  | ||||||
|       #todo: check if the record takes effect |  | ||||||
|       return 0 |       return 0 | ||||||
|     fi |     fi | ||||||
|     _err "Update error" |     _err "Update error" | ||||||
| @ -77,13 +79,48 @@ dns_cf_add() { | |||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #fulldomain | #fulldomain txtvalue | ||||||
| dns_cf_rm() { | dns_cf_rm() { | ||||||
|   fulldomain=$1 |   fulldomain=$1 | ||||||
|  |   txtvalue=$2 | ||||||
|  |   _debug "First detect the root zone" | ||||||
|  |   if ! _get_root "$fulldomain"; then | ||||||
|  |     _err "invalid domain" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  |   _debug _domain_id "$_domain_id" | ||||||
|  |   _debug _sub_domain "$_sub_domain" | ||||||
|  |   _debug _domain "$_domain" | ||||||
|  | 
 | ||||||
|  |   _debug "Getting txt records" | ||||||
|  |   _cf_rest GET "zones/${_domain_id}/dns_records?type=TXT&name=$fulldomain&content=$txtvalue" | ||||||
|  | 
 | ||||||
|  |   if ! printf "%s" "$response" | grep \"success\":true >/dev/null; then | ||||||
|  |     _err "Error" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2) | ||||||
|  |   _debug count "$count" | ||||||
|  |   if [ "$count" = "0" ]; then | ||||||
|  |     _info "Don't need to remove." | ||||||
|  |   else | ||||||
|  |     record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | head -n 1) | ||||||
|  |     _debug "record_id" "$record_id" | ||||||
|  |     if [ -z "$record_id" ]; then | ||||||
|  |       _err "Can not get record id to remove." | ||||||
|  |       return 1 | ||||||
|  |     fi | ||||||
|  |     if ! _cf_rest DELETE "zones/$_domain_id/dns_records/$record_id"; then | ||||||
|  |       _err "Delete record error." | ||||||
|  |       return 1 | ||||||
|  |     fi | ||||||
|  |     _contains "$response" '"success":true' | ||||||
|  |   fi | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ####################  Private functions bellow ################################## | ####################  Private functions below ################################## | ||||||
| #_acme-challenge.www.domain.com | #_acme-challenge.www.domain.com | ||||||
| #returns | #returns | ||||||
| # _sub_domain=_acme-challenge.www | # _sub_domain=_acme-challenge.www | ||||||
| @ -95,6 +132,7 @@ _get_root() { | |||||||
|   p=1 |   p=1 | ||||||
|   while true; do |   while true; do | ||||||
|     h=$(printf "%s" "$domain" | cut -d . -f $i-100) |     h=$(printf "%s" "$domain" | cut -d . -f $i-100) | ||||||
|  |     _debug h "$h" | ||||||
|     if [ -z "$h" ]; then |     if [ -z "$h" ]; then | ||||||
|       #not valid |       #not valid | ||||||
|       return 1 |       return 1 | ||||||
| @ -104,8 +142,8 @@ _get_root() { | |||||||
|       return 1 |       return 1 | ||||||
|     fi |     fi | ||||||
| 
 | 
 | ||||||
|     if printf "%s" "$response" | grep "\"name\":\"$h\"" >/dev/null; then |     if _contains "$response" "\"name\":\"$h\"" >/dev/null; then | ||||||
|       _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \") |       _domain_id=$(printf "%s\n" "$response" | _egrep_o "\[.\"id\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \") | ||||||
|       if [ "$_domain_id" ]; then |       if [ "$_domain_id" ]; then | ||||||
|         _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) |         _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) | ||||||
|         _domain=$h |         _domain=$h | ||||||
| @ -125,11 +163,11 @@ _cf_rest() { | |||||||
|   data="$3" |   data="$3" | ||||||
|   _debug "$ep" |   _debug "$ep" | ||||||
| 
 | 
 | ||||||
|   _H1="X-Auth-Email: $CF_Email" |   export _H1="X-Auth-Email: $CF_Email" | ||||||
|   _H2="X-Auth-Key: $CF_Key" |   export _H2="X-Auth-Key: $CF_Key" | ||||||
|   _H3="Content-Type: application/json" |   export _H3="Content-Type: application/json" | ||||||
| 
 | 
 | ||||||
|   if [ "$data" ]; then |   if [ "$m" != "GET" ]; then | ||||||
|     _debug data "$data" |     _debug data "$data" | ||||||
|     response="$(_post "$data" "$CF_Api/$ep" "" "$m")" |     response="$(_post "$data" "$CF_Api/$ep" "" "$m")" | ||||||
|   else |   else | ||||||
|  | |||||||
| @ -58,7 +58,15 @@ dns_cx_add() { | |||||||
| #fulldomain | #fulldomain | ||||||
| dns_cx_rm() { | dns_cx_rm() { | ||||||
|   fulldomain=$1 |   fulldomain=$1 | ||||||
| 
 |   REST_API="$CX_Api" | ||||||
|  |   if _get_root "$fulldomain"; then | ||||||
|  |     record_id="" | ||||||
|  |     existing_records "$_domain" "$_sub_domain" | ||||||
|  |     if ! [ "$record_id" = "" ]; then | ||||||
|  |       _rest DELETE "record/$record_id/$_domain_id" "{}" | ||||||
|  |       _info "Deleted record ${fulldomain}" | ||||||
|  |     fi | ||||||
|  |   fi | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #usage:  root  sub | #usage:  root  sub | ||||||
| @ -69,12 +77,12 @@ 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 | ||||||
|   count=0 | 
 | ||||||
|   seg=$(printf "%s\n" "$response" | _egrep_o "{[^\{]*host\":\"$_sub_domain\"[^\}]*\}") |   seg=$(printf "%s\n" "$response" | _egrep_o '"record_id":[^{]*host":"'"$_sub_domain"'"[^}]*\}') | ||||||
|   _debug seg "$seg" |   _debug seg "$seg" | ||||||
|   if [ -z "$seg" ]; then |   if [ -z "$seg" ]; then | ||||||
|     return 0 |     return 0 | ||||||
| @ -82,7 +90,7 @@ existing_records() { | |||||||
| 
 | 
 | ||||||
|   if printf "%s" "$response" | grep '"type":"TXT"' >/dev/null; then |   if printf "%s" "$response" | grep '"type":"TXT"' >/dev/null; then | ||||||
|     count=1 |     count=1 | ||||||
|     record_id=$(printf "%s\n" "$seg" | _egrep_o "\"record_id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \") |     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 | ||||||
|   fi |   fi | ||||||
| @ -123,7 +131,7 @@ update_record() { | |||||||
|   return 1 |   return 1 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ####################  Private functions bellow ################################## | ####################  Private functions below ################################## | ||||||
| #_acme-challenge.www.domain.com | #_acme-challenge.www.domain.com | ||||||
| #returns | #returns | ||||||
| # _sub_domain=_acme-challenge.www | # _sub_domain=_acme-challenge.www | ||||||
| @ -147,9 +155,9 @@ _get_root() { | |||||||
|     fi |     fi | ||||||
| 
 | 
 | ||||||
|     if _contains "$response" "$h."; then |     if _contains "$response" "$h."; then | ||||||
|       seg=$(printf "%s" "$response" | _egrep_o "\{[^\{]*\"$h\.\"[^\}]*\}") |       seg=$(printf "%s\n" "$response" | _egrep_o '"id":[^{]*"'"$h"'."[^}]*}') | ||||||
|       _debug seg "$seg" |       _debug seg "$seg" | ||||||
|       _domain_id=$(printf "%s" "$seg" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \") |       _domain_id=$(printf "%s\n" "$seg" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \") | ||||||
|       _debug _domain_id "$_domain_id" |       _debug _domain_id "$_domain_id" | ||||||
|       if [ "$_domain_id" ]; then |       if [ "$_domain_id" ]; then | ||||||
|         _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) |         _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) | ||||||
| @ -170,7 +178,7 @@ _get_root() { | |||||||
| _rest() { | _rest() { | ||||||
|   m=$1 |   m=$1 | ||||||
|   ep="$2" |   ep="$2" | ||||||
|   _debug "$ep" |   _debug ep "$ep" | ||||||
|   url="$REST_API/$ep" |   url="$REST_API/$ep" | ||||||
|   _debug url "$url" |   _debug url "$url" | ||||||
| 
 | 
 | ||||||
| @ -185,10 +193,10 @@ _rest() { | |||||||
|   hmac=$(printf "%s" "$sec" | _digest md5 hex) |   hmac=$(printf "%s" "$sec" | _digest md5 hex) | ||||||
|   _debug hmac "$hmac" |   _debug hmac "$hmac" | ||||||
| 
 | 
 | ||||||
|   _H1="API-KEY: $CX_Key" |   export _H1="API-KEY: $CX_Key" | ||||||
|   _H2="API-REQUEST-DATE: $cdate" |   export _H2="API-REQUEST-DATE: $cdate" | ||||||
|   _H3="API-HMAC: $hmac" |   export _H3="API-HMAC: $hmac" | ||||||
|   _H4="Content-Type: application/json" |   export _H4="Content-Type: application/json" | ||||||
| 
 | 
 | ||||||
|   if [ "$data" ]; then |   if [ "$data" ]; then | ||||||
|     response="$(_post "$data" "$url" "" "$m")" |     response="$(_post "$data" "$url" "" "$m")" | ||||||
|  | |||||||
| @ -6,9 +6,8 @@ | |||||||
| # | # | ||||||
| #DP_Key="sADDsdasdgdsf" | #DP_Key="sADDsdasdgdsf" | ||||||
| 
 | 
 | ||||||
| DP_Api="https://dnsapi.cn" | REST_API="https://dnsapi.cn" | ||||||
| 
 | 
 | ||||||
| #REST_API |  | ||||||
| ########  Public functions ##################### | ########  Public functions ##################### | ||||||
| 
 | 
 | ||||||
| #Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" | #Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" | ||||||
| @ -24,8 +23,6 @@ dns_dp_add() { | |||||||
|     return 1 |     return 1 | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   REST_API="$DP_Api" |  | ||||||
| 
 |  | ||||||
|   #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 DP_Id "$DP_Id" | ||||||
|   _saveaccountconf DP_Key "$DP_Key" |   _saveaccountconf DP_Key "$DP_Key" | ||||||
| @ -50,9 +47,39 @@ dns_dp_add() { | |||||||
|   fi |   fi | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #fulldomain | #fulldomain txtvalue | ||||||
| dns_dp_rm() { | dns_dp_rm() { | ||||||
|   fulldomain=$1 |   fulldomain=$1 | ||||||
|  |   txtvalue=$2 | ||||||
|  |   _debug "First detect the root zone" | ||||||
|  |   if ! _get_root "$fulldomain"; then | ||||||
|  |     _err "invalid domain" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   if ! _rest POST "Record.List" "login_token=$DP_Id,$DP_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain"; then | ||||||
|  |     _err "Record.Lis error." | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   if _contains "$response" 'No records'; then | ||||||
|  |     _info "Don't need to remove." | ||||||
|  |     return 0 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   record_id=$(echo "$response" | _egrep_o '{[^{]*"value":"'"$txtvalue"'"' | cut -d , -f 1 | cut -d : -f 2 | tr -d \") | ||||||
|  |   _debug record_id "$record_id" | ||||||
|  |   if [ -z "$record_id" ]; then | ||||||
|  |     _err "Can not get record id." | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   if ! _rest POST "Record.Remove" "login_token=$DP_Id,$DP_Key&format=json&domain_id=$_domain_id&record_id=$record_id"; then | ||||||
|  |     _err "Record.Remove error." | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _contains "$response" "Action completed successful" | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -75,8 +102,9 @@ existing_records() { | |||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   if _contains "$response" "Action completed successful"; then |   if _contains "$response" "Action completed successful"; then | ||||||
|     count=$(printf "%s" "$response" | grep '<type>TXT</type>' | wc -l) |     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) |     record_id=$(printf "%s" "$response" | grep '^<id>' | tail -1 | cut -d '>' -f 2 | cut -d '<' -f 1) | ||||||
|  |     _debug record_id "$record_id" | ||||||
|     return 0 |     return 0 | ||||||
|   else |   else | ||||||
|     _err "get existing records error." |     _err "get existing records error." | ||||||
| @ -130,7 +158,7 @@ update_record() { | |||||||
|   return 1 #error |   return 1 #error | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ####################  Private functions bellow ################################## | ####################  Private functions below ################################## | ||||||
| #_acme-challenge.www.domain.com | #_acme-challenge.www.domain.com | ||||||
| #returns | #returns | ||||||
| # _sub_domain=_acme-challenge.www | # _sub_domain=_acme-challenge.www | ||||||
| @ -171,7 +199,7 @@ _get_root() { | |||||||
| 
 | 
 | ||||||
| #Usage: method  URI  data | #Usage: method  URI  data | ||||||
| _rest() { | _rest() { | ||||||
|   m=$1 |   m="$1" | ||||||
|   ep="$2" |   ep="$2" | ||||||
|   data="$3" |   data="$3" | ||||||
|   _debug "$ep" |   _debug "$ep" | ||||||
| @ -179,11 +207,11 @@ _rest() { | |||||||
| 
 | 
 | ||||||
|   _debug url "$url" |   _debug url "$url" | ||||||
| 
 | 
 | ||||||
|   if [ "$data" ]; then |   if [ "$m" = "GET" ]; then | ||||||
|     _debug2 data "$data" |     response="$(_get "$url" | tr -d '\r')" | ||||||
|     response="$(_post "$data" "$url")" |  | ||||||
|   else |   else | ||||||
|     response="$(_get "$url")" |     _debug2 data "$data" | ||||||
|  |     response="$(_post "$data" "$url" | tr -d '\r')" | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   if [ "$?" != "0" ]; then |   if [ "$?" != "0" ]; then | ||||||
|  | |||||||
							
								
								
									
										375
									
								
								dnsapi/dns_freedns.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										375
									
								
								dnsapi/dns_freedns.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,375 @@ | |||||||
|  | #!/usr/bin/env sh | ||||||
|  | 
 | ||||||
|  | #This file name is "dns_freedns.sh" | ||||||
|  | #So, here must be a method dns_freedns_add() | ||||||
|  | #Which will be called by acme.sh to add the txt record to your api system. | ||||||
|  | #returns 0 means success, otherwise error. | ||||||
|  | # | ||||||
|  | #Author: David Kerr | ||||||
|  | #Report Bugs here: https://github.com/dkerr64/acme.sh | ||||||
|  | # | ||||||
|  | ########  Public functions ##################### | ||||||
|  | 
 | ||||||
|  | # Export FreeDNS userid and password in folowing variables... | ||||||
|  | #  FREEDNS_User=username | ||||||
|  | #  FREEDNS_Password=password | ||||||
|  | # login cookie is saved in acme account config file so userid / pw | ||||||
|  | # need to be set only when changed. | ||||||
|  | 
 | ||||||
|  | #Usage: dns_freedns_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" | ||||||
|  | dns_freedns_add() { | ||||||
|  |   fulldomain="$1" | ||||||
|  |   txtvalue="$2" | ||||||
|  | 
 | ||||||
|  |   _info "Add TXT record using FreeDNS" | ||||||
|  |   _debug "fulldomain: $fulldomain" | ||||||
|  |   _debug "txtvalue: $txtvalue" | ||||||
|  | 
 | ||||||
|  |   if [ -z "$FREEDNS_User" ] || [ -z "$FREEDNS_Password" ]; then | ||||||
|  |     FREEDNS_User="" | ||||||
|  |     FREEDNS_Password="" | ||||||
|  |     if [ -z "$FREEDNS_COOKIE" ]; then | ||||||
|  |       _err "You did not specify the FreeDNS username and password yet." | ||||||
|  |       _err "Please export as FREEDNS_User / FREEDNS_Password and try again." | ||||||
|  |       return 1 | ||||||
|  |     fi | ||||||
|  |     using_cached_cookies="true" | ||||||
|  |   else | ||||||
|  |     FREEDNS_COOKIE="$(_freedns_login "$FREEDNS_User" "$FREEDNS_Password")" | ||||||
|  |     if [ -z "$FREEDNS_COOKIE" ]; then | ||||||
|  |       return 1 | ||||||
|  |     fi | ||||||
|  |     using_cached_cookies="false" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _debug "FreeDNS login cookies: $FREEDNS_COOKIE (cached = $using_cached_cookies)" | ||||||
|  | 
 | ||||||
|  |   _saveaccountconf FREEDNS_COOKIE "$FREEDNS_COOKIE" | ||||||
|  | 
 | ||||||
|  |   # split our full domain name into two parts... | ||||||
|  |   i="$(echo "$fulldomain" | tr '.' ' ' | wc -w)" | ||||||
|  |   i="$(_math "$i" - 1)" | ||||||
|  |   top_domain="$(echo "$fulldomain" | cut -d. -f "$i"-100)" | ||||||
|  |   i="$(_math "$i" - 1)" | ||||||
|  |   sub_domain="$(echo "$fulldomain" | cut -d. -f -"$i")" | ||||||
|  | 
 | ||||||
|  |   # Sometimes FreeDNS does not reurn the subdomain page but rather  | ||||||
|  |   # returns a page regarding becoming a premium member.  This usually | ||||||
|  |   # happens after a period of inactivity.  Immediately trying again | ||||||
|  |   # returns the correct subdomain page.  So, we will try twice to | ||||||
|  |   # load the page and obtain our domain ID | ||||||
|  |   attempts=2 | ||||||
|  |   while [ "$attempts" -gt "0" ]; do | ||||||
|  |     attempts="$(_math "$attempts" - 1)" | ||||||
|  | 
 | ||||||
|  |     htmlpage="$(_freedns_retrieve_subdomain_page "$FREEDNS_COOKIE")" | ||||||
|  |     if [ "$?" != "0" ]; then | ||||||
|  |       if [ "$using_cached_cookies" = "true" ]; then | ||||||
|  |         _err "Has your FreeDNS username and password channged?  If so..." | ||||||
|  |         _err "Please export as FREEDNS_User / FREEDNS_Password and try again." | ||||||
|  |       fi | ||||||
|  |       return 1 | ||||||
|  |     fi | ||||||
|  | 
 | ||||||
|  |     # Now convert the tables in the HTML to CSV.  This litte gem from | ||||||
|  |     # http://stackoverflow.com/questions/1403087/how-can-i-convert-an-html-table-to-csv     | ||||||
|  |     subdomain_csv="$(echo "$htmlpage" \ | ||||||
|  |       | grep -i -e '</\?TABLE\|</\?TD\|</\?TR\|</\?TH' \ | ||||||
|  |       | sed 's/^[\ \t]*//g' \ | ||||||
|  |       | tr -d '\n' \ | ||||||
|  |       | sed 's/<\/TR[^>]*>/\n/Ig' \ | ||||||
|  |       | sed 's/<\/\?\(TABLE\|TR\)[^>]*>//Ig' \ | ||||||
|  |       | sed 's/^<T[DH][^>]*>\|<\/\?T[DH][^>]*>$//Ig' \ | ||||||
|  |       | sed 's/<\/T[DH][^>]*><T[DH][^>]*>/,/Ig' \ | ||||||
|  |       | grep 'edit.php?' \ | ||||||
|  |       | grep "$top_domain")" | ||||||
|  |     # The above beauty ends with striping out rows that do not have an | ||||||
|  |     # href to edit.php and do not have the top domain we are looking for. | ||||||
|  |     # So all we should be left with is CSV of table of subdomains we are | ||||||
|  |     # interested in. | ||||||
|  | 
 | ||||||
|  |     # Now we have to read through this table and extract the data we need | ||||||
|  |     lines="$(echo "$subdomain_csv" | wc -l)" | ||||||
|  |     nl=' | ||||||
|  | ' | ||||||
|  |     i=0 | ||||||
|  |     found=0 | ||||||
|  |     while [ "$i" -lt "$lines" ]; do | ||||||
|  |       i="$(_math "$i" + 1)" | ||||||
|  |       line="$(echo "$subdomain_csv" | cut -d "$nl" -f "$i")" | ||||||
|  |       tmp="$(echo "$line" | cut -d ',' -f 1)" | ||||||
|  |       if [ $found = 0 ] && _startswith "$tmp" "<td>$top_domain"; then | ||||||
|  |         # this line will contain DNSdomainid for the top_domain | ||||||
|  |         DNSdomainid="$(echo "$line" | cut -d ',' -f 2 | sed 's/^.*domain_id=//;s/>.*//')" | ||||||
|  |         found=1 | ||||||
|  |       else | ||||||
|  |         # lines contain DNS records for all subdomains | ||||||
|  |         DNSname="$(echo "$line" | cut -d ',' -f 2 | sed 's/^[^>]*>//;s/<\/a>.*//')" | ||||||
|  |         DNStype="$(echo "$line" | cut -d ',' -f 3)" | ||||||
|  |         if [ "$DNSname" = "$fulldomain" ] && [ "$DNStype" = "TXT" ]; then | ||||||
|  |           DNSdataid="$(echo "$line" | cut -d ',' -f 2 | sed 's/^.*data_id=//;s/>.*//')" | ||||||
|  |           # Now get current value for the TXT record.  This method may | ||||||
|  |           # not produce accurate results as the value field is truncated | ||||||
|  |           # on this webpage. To get full value we would need to load | ||||||
|  |           # another page. However we don't really need this so long as | ||||||
|  |           # there is only one TXT record for the acme chalenge subdomain. | ||||||
|  |           DNSvalue="$(echo "$line" | cut -d ',' -f 4 | sed 's/^[^"]*"//;s/".*//;s/<\/td>.*//')" | ||||||
|  |           if [ $found != 0 ]; then | ||||||
|  |             break | ||||||
|  |             # we are breaking out of the loop at the first match of DNS name | ||||||
|  |             # and DNS type (if we are past finding the domainid). This assumes | ||||||
|  |             # that there is only ever one TXT record for the LetsEncrypt/acme | ||||||
|  |             # challenge subdomain.  This seems to be a reasonable assumption | ||||||
|  |             # as the acme client deletes the TXT record on successful validation. | ||||||
|  |           fi | ||||||
|  |         else | ||||||
|  |           DNSname="" | ||||||
|  |           DNStype="" | ||||||
|  |         fi | ||||||
|  |       fi | ||||||
|  |     done | ||||||
|  | 
 | ||||||
|  |     _debug "DNSname: $DNSname DNStype: $DNStype DNSdomainid: $DNSdomainid DNSdataid: $DNSdataid" | ||||||
|  |     _debug "DNSvalue: $DNSvalue" | ||||||
|  | 
 | ||||||
|  |     if [ -z "$DNSdomainid" ]; then | ||||||
|  |       # If domain ID is empty then something went wrong (top level | ||||||
|  |       # domain not found at FreeDNS). | ||||||
|  |       if [ "$attempts" = "0" ]; then | ||||||
|  |         # exhausted maximum retry attempts | ||||||
|  |         _debug "$htmlpage" | ||||||
|  |         _debug "$subdomain_csv" | ||||||
|  |         _err "Domain $top_domain not found at FreeDNS" | ||||||
|  |         return 1 | ||||||
|  |       fi | ||||||
|  |     else | ||||||
|  |       # break out of the 'retry' loop... we have found our domain ID | ||||||
|  |       break | ||||||
|  |     fi | ||||||
|  |     _info "Domain $top_domain not found at FreeDNS" | ||||||
|  |     _info "Retry loading subdomain page ($attempts attempts remaining)" | ||||||
|  |   done | ||||||
|  | 
 | ||||||
|  |   if [ -z "$DNSdataid" ]; then | ||||||
|  |     # If data ID is empty then specific subdomain does not exist yet, need | ||||||
|  |     # to create it this should always be the case as the acme client | ||||||
|  |     # deletes the entry after domain is validated. | ||||||
|  |     _freedns_add_txt_record "$FREEDNS_COOKIE" "$DNSdomainid" "$sub_domain" "$txtvalue" | ||||||
|  |     return $? | ||||||
|  |   else | ||||||
|  |     if [ "$txtvalue" = "$DNSvalue" ]; then | ||||||
|  |       # if value in TXT record matches value requested then DNS record | ||||||
|  |       # does not need to be updated. But... | ||||||
|  |       # Testing value match fails.  Website is truncating the value field. | ||||||
|  |       # So for now we will always go down the else path.  Though in theory | ||||||
|  |       # should never come here anyway as the acme client deletes | ||||||
|  |       # the TXT record on successful validation, so we should not even | ||||||
|  |       # have found a TXT record !! | ||||||
|  |       _info "No update necessary for $fulldomain at FreeDNS" | ||||||
|  |       return 0 | ||||||
|  |     else | ||||||
|  |       # Delete the old TXT record (with the wrong value) | ||||||
|  |       _freedns_delete_txt_record "$FREEDNS_COOKIE" "$DNSdataid" | ||||||
|  |       if [ "$?" = "0" ]; then | ||||||
|  |         # And add in new TXT record with the value provided | ||||||
|  |         _freedns_add_txt_record "$FREEDNS_COOKIE" "$DNSdomainid" "$sub_domain" "$txtvalue" | ||||||
|  |       fi | ||||||
|  |       return $? | ||||||
|  |     fi | ||||||
|  |   fi | ||||||
|  |   return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #Usage: fulldomain txtvalue | ||||||
|  | #Remove the txt record after validation. | ||||||
|  | dns_freedns_rm() { | ||||||
|  |   fulldomain="$1" | ||||||
|  |   txtvalue="$2" | ||||||
|  | 
 | ||||||
|  |   _info "Delete TXT record using FreeDNS" | ||||||
|  |   _debug "fulldomain: $fulldomain" | ||||||
|  |   _debug "txtvalue: $txtvalue" | ||||||
|  | 
 | ||||||
|  |   # Need to read cookie from conf file again in case new value set | ||||||
|  |   # during login to FreeDNS when TXT record was created. | ||||||
|  |   # acme.sh does not have a _readaccountconf() fuction | ||||||
|  |   FREEDNS_COOKIE="$(_read_conf "$ACCOUNT_CONF_PATH" "FREEDNS_COOKIE")" | ||||||
|  |   _debug "FreeDNS login cookies: $FREEDNS_COOKIE" | ||||||
|  | 
 | ||||||
|  |   # Sometimes FreeDNS does not reurn the subdomain page but rather  | ||||||
|  |   # returns a page regarding becoming a premium member.  This usually | ||||||
|  |   # happens after a period of inactivity.  Immediately trying again | ||||||
|  |   # returns the correct subdomain page.  So, we will try twice to | ||||||
|  |   # load the page and obtain our TXT record. | ||||||
|  |   attempts=2 | ||||||
|  |   while [ "$attempts" -gt "0" ]; do | ||||||
|  |     attempts="$(_math "$attempts" - 1)" | ||||||
|  | 
 | ||||||
|  |     htmlpage="$(_freedns_retrieve_subdomain_page "$FREEDNS_COOKIE")" | ||||||
|  |     if [ "$?" != "0" ]; then | ||||||
|  |       return 1 | ||||||
|  |     fi | ||||||
|  | 
 | ||||||
|  |     # Now convert the tables in the HTML to CSV.  This litte gem from | ||||||
|  |     # http://stackoverflow.com/questions/1403087/how-can-i-convert-an-html-table-to-csv | ||||||
|  |     subdomain_csv="$(echo "$htmlpage" \ | ||||||
|  |       | grep -i -e '</\?TABLE\|</\?TD\|</\?TR\|</\?TH' \ | ||||||
|  |       | sed 's/^[\ \t]*//g' \ | ||||||
|  |       | tr -d '\n' \ | ||||||
|  |       | sed 's/<\/TR[^>]*>/\n/Ig' \ | ||||||
|  |       | sed 's/<\/\?\(TABLE\|TR\)[^>]*>//Ig' \ | ||||||
|  |       | sed 's/^<T[DH][^>]*>\|<\/\?T[DH][^>]*>$//Ig' \ | ||||||
|  |       | sed 's/<\/T[DH][^>]*><T[DH][^>]*>/,/Ig' \ | ||||||
|  |       | grep 'edit.php?' \ | ||||||
|  |       | grep "$fulldomain")" | ||||||
|  |     # The above beauty ends with striping out rows that do not have an | ||||||
|  |     # href to edit.php and do not have the domain name we are looking for. | ||||||
|  |     # So all we should be left with is CSV of table of subdomains we are | ||||||
|  |     # interested in. | ||||||
|  | 
 | ||||||
|  |     # Now we have to read through this table and extract the data we need | ||||||
|  |     lines="$(echo "$subdomain_csv" | wc -l)" | ||||||
|  |     nl=' | ||||||
|  | ' | ||||||
|  |     i=0 | ||||||
|  |     found=0 | ||||||
|  |     while [ "$i" -lt "$lines" ]; do | ||||||
|  |       i="$(_math "$i" + 1)" | ||||||
|  |       line="$(echo "$subdomain_csv" | cut -d "$nl" -f "$i")" | ||||||
|  |       DNSname="$(echo "$line" | cut -d ',' -f 2 | sed 's/^[^>]*>//;s/<\/a>.*//')" | ||||||
|  |       DNStype="$(echo "$line" | cut -d ',' -f 3)" | ||||||
|  |       if [ "$DNSname" = "$fulldomain" ] && [ "$DNStype" = "TXT" ]; then | ||||||
|  |         DNSdataid="$(echo "$line" | cut -d ',' -f 2 | sed 's/^.*data_id=//;s/>.*//')" | ||||||
|  |         DNSvalue="$(echo "$line" | cut -d ',' -f 4 | sed 's/^[^"]*"//;s/".*//;s/<\/td>.*//')" | ||||||
|  |         _debug "DNSvalue: $DNSvalue" | ||||||
|  |         #     if [ "$DNSvalue" = "$txtvalue" ]; then | ||||||
|  |         # Testing value match fails.  Website is truncating the value | ||||||
|  |         # field. So for now we will assume that there is only one TXT | ||||||
|  |         # field for the sub domain and just delete it. Currently this | ||||||
|  |         # is a safe assumption. | ||||||
|  |         _freedns_delete_txt_record "$FREEDNS_COOKIE" "$DNSdataid" | ||||||
|  |         return $? | ||||||
|  |         #     fi | ||||||
|  |       fi | ||||||
|  |     done | ||||||
|  |   done | ||||||
|  | 
 | ||||||
|  |   # If we get this far we did not find a match (after two attempts) | ||||||
|  |   # Not necessarily an error, but log anyway. | ||||||
|  |   _debug2 "$subdomain_csv" | ||||||
|  |   _info "Cannot delete TXT record for $fulldomain/$txtvalue. Does not exist at FreeDNS" | ||||||
|  |   return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ####################  Private functions below ################################## | ||||||
|  | 
 | ||||||
|  | # usage: _freedns_login username password | ||||||
|  | # print string "cookie=value" etc. | ||||||
|  | # returns 0 success | ||||||
|  | _freedns_login() { | ||||||
|  |   export _H1="Accept-Language:en-US" | ||||||
|  |   username="$1" | ||||||
|  |   password="$2" | ||||||
|  |   url="https://freedns.afraid.org/zc.php?step=2" | ||||||
|  | 
 | ||||||
|  |   _debug "Login to FreeDNS as user $username" | ||||||
|  | 
 | ||||||
|  |   htmlpage="$(_post "username=$(printf '%s' "$username" | _url_encode)&password=$(printf '%s' "$password" | _url_encode)&submit=Login&action=auth" "$url")" | ||||||
|  | 
 | ||||||
|  |   if [ "$?" != "0" ]; then | ||||||
|  |     _err "FreeDNS login failed for user $username bad RC from _post" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   cookies="$(grep -i '^Set-Cookie.*dns_cookie.*$' "$HTTP_HEADER" | _head_n 1 | tr -d "\r\n" | cut -d " " -f 2)" | ||||||
|  | 
 | ||||||
|  |   # if cookies is not empty then logon successful | ||||||
|  |   if [ -z "$cookies" ]; then | ||||||
|  |     _debug "$htmlpage" | ||||||
|  |     _err "FreeDNS login failed for user $username. Check $HTTP_HEADER file" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   printf "%s" "$cookies" | ||||||
|  |   return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # usage _freedns_retrieve_subdomain_page login_cookies | ||||||
|  | # echo page retrieved (html) | ||||||
|  | # returns 0 success | ||||||
|  | _freedns_retrieve_subdomain_page() { | ||||||
|  |   export _H1="Cookie:$1" | ||||||
|  |   export _H2="Accept-Language:en-US" | ||||||
|  |   url="https://freedns.afraid.org/subdomain/" | ||||||
|  | 
 | ||||||
|  |   _debug "Retrieve subdmoain page from FreeDNS" | ||||||
|  | 
 | ||||||
|  |   htmlpage="$(_get "$url")" | ||||||
|  | 
 | ||||||
|  |   if [ "$?" != "0" ]; then | ||||||
|  |     _err "FreeDNS retrieve subdomins failed bad RC from _get" | ||||||
|  |     return 1 | ||||||
|  |   elif [ -z "$htmlpage" ]; then | ||||||
|  |     _err "FreeDNS returned empty subdomain page" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _debug2 "$htmlpage" | ||||||
|  | 
 | ||||||
|  |   printf "%s" "$htmlpage" | ||||||
|  |   return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # usage _freedns_add_txt_record login_cookies domain_id subdomain value | ||||||
|  | # returns 0 success | ||||||
|  | _freedns_add_txt_record() { | ||||||
|  |   export _H1="Cookie:$1" | ||||||
|  |   export _H2="Accept-Language:en-US" | ||||||
|  |   domain_id="$2" | ||||||
|  |   subdomain="$3" | ||||||
|  |   value="$(printf '%s' "$4" | _url_encode)" | ||||||
|  |   url="http://freedns.afraid.org/subdomain/save.php?step=2" | ||||||
|  | 
 | ||||||
|  |   htmlpage="$(_post "type=TXT&domain_id=$domain_id&subdomain=$subdomain&address=%22$value%22&send=Save%21" "$url")" | ||||||
|  | 
 | ||||||
|  |   if [ "$?" != "0" ]; then | ||||||
|  |     _err "FreeDNS failed to add TXT record for $subdomain bad RC from _post" | ||||||
|  |     return 1 | ||||||
|  |   elif ! grep "200 OK" "$HTTP_HEADER" >/dev/null; then | ||||||
|  |     _debug "$htmlpage" | ||||||
|  |     _err "FreeDNS failed to add TXT record for $subdomain. Check $HTTP_HEADER file" | ||||||
|  |     return 1 | ||||||
|  |   elif _contains "$htmlpage" "security code was incorrect"; then | ||||||
|  |     _debug "$htmlpage" | ||||||
|  |     _err "FreeDNS failed to add TXT record for $subdomain as FreeDNS requested seurity code" | ||||||
|  |     _err "Note that you cannot use automatic DNS validation for FreeDNS public domains" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _debug2 "$htmlpage" | ||||||
|  |   _info "Added acme challenge TXT record for $fulldomain at FreeDNS" | ||||||
|  |   return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # usage _freedns_delete_txt_record login_cookies data_id | ||||||
|  | # returns 0 success | ||||||
|  | _freedns_delete_txt_record() { | ||||||
|  |   export _H1="Cookie:$1" | ||||||
|  |   export _H2="Accept-Language:en-US" | ||||||
|  |   data_id="$2" | ||||||
|  |   url="https://freedns.afraid.org/subdomain/delete2.php" | ||||||
|  | 
 | ||||||
|  |   htmlheader="$(_get "$url?data_id%5B%5D=$data_id&submit=delete+selected" "onlyheader")" | ||||||
|  | 
 | ||||||
|  |   if [ "$?" != "0" ]; then | ||||||
|  |     _err "FreeDNS failed to delete TXT record for $data_id bad RC from _get" | ||||||
|  |     return 1 | ||||||
|  |   elif ! _contains "$htmlheader" "200 OK"; then | ||||||
|  |     _debug "$htmlheader" | ||||||
|  |     _err "FreeDNS failed to delete TXT record $data_id" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _info "Deleted acme challenge TXT record for $fulldomain at FreeDNS" | ||||||
|  |   return 0 | ||||||
|  | } | ||||||
| @ -59,7 +59,7 @@ dns_gd_rm() { | |||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ####################  Private functions bellow ################################## | ####################  Private functions below ################################## | ||||||
| #_acme-challenge.www.domain.com | #_acme-challenge.www.domain.com | ||||||
| #returns | #returns | ||||||
| # _sub_domain=_acme-challenge.www | # _sub_domain=_acme-challenge.www | ||||||
| @ -98,8 +98,8 @@ _gd_rest() { | |||||||
|   data="$3" |   data="$3" | ||||||
|   _debug "$ep" |   _debug "$ep" | ||||||
| 
 | 
 | ||||||
|   _H1="Authorization: sso-key $GD_Key:$GD_Secret" |   export _H1="Authorization: sso-key $GD_Key:$GD_Secret" | ||||||
|   _H2="Content-Type: application/json" |   export _H2="Content-Type: application/json" | ||||||
| 
 | 
 | ||||||
|   if [ "$data" ]; then |   if [ "$data" ]; then | ||||||
|     _debug data "$data" |     _debug data "$data" | ||||||
|  | |||||||
							
								
								
									
										177
									
								
								dnsapi/dns_ispconfig.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										177
									
								
								dnsapi/dns_ispconfig.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,177 @@ | |||||||
|  | #!/usr/bin/env sh | ||||||
|  | 
 | ||||||
|  | # ISPConfig 3.1 API | ||||||
|  | # User must provide login data and URL to the ISPConfig installation incl. port. The remote user in ISPConfig must have access to: | ||||||
|  | # - DNS zone Functions | ||||||
|  | # - DNS txt Functions | ||||||
|  | 
 | ||||||
|  | # Report bugs to https://github.com/sjau/acme.sh | ||||||
|  | 
 | ||||||
|  | # Values to export: | ||||||
|  | # export ISPC_User="remoteUser" | ||||||
|  | # export ISPC_Password="remotePassword" | ||||||
|  | # export ISPC_Api="https://ispc.domain.tld:8080/remote/json.php" | ||||||
|  | # export ISPC_Api_Insecure=1     # Set 1 for insecure and 0 for secure -> difference is whether ssl cert is checked for validity (0) or whether it is just accepted (1) | ||||||
|  | 
 | ||||||
|  | ########  Public functions ##################### | ||||||
|  | 
 | ||||||
|  | #Usage: dns_myapi_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" | ||||||
|  | dns_ispconfig_add() { | ||||||
|  |   fulldomain="${1}" | ||||||
|  |   txtvalue="${2}" | ||||||
|  |   _debug "Calling: dns_ispconfig_add() '${fulldomain}' '${txtvalue}'" | ||||||
|  |   _ISPC_credentials && _ISPC_login && _ISPC_getZoneInfo && _ISPC_addTxt | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #Usage: dns_myapi_rm   _acme-challenge.www.domain.com | ||||||
|  | dns_ispconfig_rm() { | ||||||
|  |   fulldomain="${1}" | ||||||
|  |   _debug "Calling: dns_ispconfig_rm() '${fulldomain}'" | ||||||
|  |   _ISPC_credentials && _ISPC_login && _ISPC_rmTxt | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ####################  Private functions below ################################## | ||||||
|  | 
 | ||||||
|  | _ISPC_credentials() { | ||||||
|  |   if [ -z "${ISPC_User}" ] || [ -z "$ISPC_Password" ] || [ -z "${ISPC_Api}" ] || [ -z "${ISPC_Api_Insecure}" ]; then | ||||||
|  |     ISPC_User="" | ||||||
|  |     ISPC_Password="" | ||||||
|  |     ISPC_Api="" | ||||||
|  |     ISPC_Api_Insecure="" | ||||||
|  |     _err "You haven't specified the ISPConfig Login data, URL and whether you want check the ISPC SSL cert. Please try again." | ||||||
|  |     return 1 | ||||||
|  |   else | ||||||
|  |     _saveaccountconf ISPC_User "${ISPC_User}" | ||||||
|  |     _saveaccountconf ISPC_Password "${ISPC_Password}" | ||||||
|  |     _saveaccountconf ISPC_Api "${ISPC_Api}" | ||||||
|  |     _saveaccountconf ISPC_Api_Insecure "${ISPC_Api_Insecure}" | ||||||
|  |     # Set whether curl should use secure or insecure mode | ||||||
|  |     export HTTPS_INSECURE="${ISPC_Api_Insecure}" | ||||||
|  |   fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | _ISPC_login() { | ||||||
|  |   _info "Getting Session ID" | ||||||
|  |   curData="{\"username\":\"${ISPC_User}\",\"password\":\"${ISPC_Password}\",\"client_login\":false}" | ||||||
|  |   curResult="$(_post "${curData}" "${ISPC_Api}?login")" | ||||||
|  |   _debug "Calling _ISPC_login: '${curData}' '${ISPC_Api}?login'" | ||||||
|  |   _debug "Result of _ISPC_login: '$curResult'" | ||||||
|  |   if _contains "${curResult}" '"code":"ok"'; then | ||||||
|  |     sessionID=$(echo "${curResult}" | _egrep_o "response.*" | cut -d ':' -f 2 | cut -d '"' -f 2) | ||||||
|  |     _info "Retrieved Session ID." | ||||||
|  |     _debug "Session ID: '${sessionID}'" | ||||||
|  |   else | ||||||
|  |     _err "Couldn't retrieve the Session ID." | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | _ISPC_getZoneInfo() { | ||||||
|  |   _info "Getting Zoneinfo" | ||||||
|  |   zoneEnd=false | ||||||
|  |   curZone="${fulldomain}" | ||||||
|  |   while [ "${zoneEnd}" = false ]; do | ||||||
|  |     # we can strip the first part of the fulldomain, since it's just the _acme-challenge string | ||||||
|  |     curZone="${curZone#*.}" | ||||||
|  |     # suffix . needed for zone -> domain.tld. | ||||||
|  |     curData="{\"session_id\":\"${sessionID}\",\"primary_id\":{\"origin\":\"${curZone}.\"}}" | ||||||
|  |     curResult="$(_post "${curData}" "${ISPC_Api}?dns_zone_get")" | ||||||
|  |     _debug "Calling _ISPC_getZoneInfo: '${curData}' '${ISPC_Api}?login'" | ||||||
|  |     _debug "Result of _ISPC_getZoneInfo: '$curResult'" | ||||||
|  |     if _contains "${curResult}" '"id":"'; then | ||||||
|  |       zoneFound=true | ||||||
|  |       zoneEnd=true | ||||||
|  |       _info "Retrieved zone data." | ||||||
|  |       _debug "Zone data: '${curResult}'" | ||||||
|  |     fi | ||||||
|  |     if [ "${curZone#*.}" != "$curZone" ]; then | ||||||
|  |       _debug2 "$curZone still contains a '.' - so we can check next higher level" | ||||||
|  |     else | ||||||
|  |       zoneEnd=true | ||||||
|  |       _err "Couldn't retrieve zone data." | ||||||
|  |       return 1 | ||||||
|  |     fi | ||||||
|  |   done | ||||||
|  |   if [ "${zoneFound}" ]; then | ||||||
|  |     server_id=$(echo "${curResult}" | _egrep_o "server_id.*" | cut -d ':' -f 2 | cut -d '"' -f 2) | ||||||
|  |     _debug "Server ID: '${server_id}'" | ||||||
|  |     case "${server_id}" in | ||||||
|  |       '' | *[!0-9]*) | ||||||
|  |         _err "Server ID is not numeric." | ||||||
|  |         return 1 | ||||||
|  |         ;; | ||||||
|  |       *) _info "Retrieved Server ID" ;; | ||||||
|  |     esac | ||||||
|  |     zone=$(echo "${curResult}" | _egrep_o "\"id.*" | cut -d ':' -f 2 | cut -d '"' -f 2) | ||||||
|  |     _debug "Zone: '${zone}'" | ||||||
|  |     case "${zone}" in | ||||||
|  |       '' | *[!0-9]*) | ||||||
|  |         _err "Zone ID is not numeric." | ||||||
|  |         return 1 | ||||||
|  |         ;; | ||||||
|  |       *) _info "Retrieved Zone ID" ;; | ||||||
|  |     esac | ||||||
|  |     client_id=$(echo "${curResult}" | _egrep_o "sys_userid.*" | cut -d ':' -f 2 | cut -d '"' -f 2) | ||||||
|  |     _debug "Client ID: '${client_id}'" | ||||||
|  |     case "${client_id}" in | ||||||
|  |       '' | *[!0-9]*) | ||||||
|  |         _err "Client ID is not numeric." | ||||||
|  |         return 1 | ||||||
|  |         ;; | ||||||
|  |       *) _info "Retrieved Client ID." ;; | ||||||
|  |     esac | ||||||
|  |     zoneFound="" | ||||||
|  |     zoneEnd="" | ||||||
|  |   fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | _ISPC_addTxt() { | ||||||
|  |   curSerial="$(date +%s)" | ||||||
|  |   curStamp="$(date +'%F %T')" | ||||||
|  |   params="\"server_id\":\"${server_id}\",\"zone\":\"${zone}\",\"name\":\"${fulldomain}.\",\"type\":\"txt\",\"data\":\"${txtvalue}\",\"aux\":\"0\",\"ttl\":\"3600\",\"active\":\"y\",\"stamp\":\"${curStamp}\",\"serial\":\"${curSerial}\"" | ||||||
|  |   curData="{\"session_id\":\"${sessionID}\",\"client_id\":\"${client_id}\",\"params\":{${params}}}" | ||||||
|  |   curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_add")" | ||||||
|  |   _debug "Calling _ISPC_addTxt: '${curData}' '${ISPC_Api}?dns_txt_add'" | ||||||
|  |   _debug "Result of _ISPC_addTxt: '$curResult'" | ||||||
|  |   record_id=$(echo "${curResult}" | _egrep_o "\"response.*" | cut -d ':' -f 2 | cut -d '"' -f 2) | ||||||
|  |   _debug "Record ID: '${record_id}'" | ||||||
|  |   case "${record_id}" in | ||||||
|  |     '' | *[!0-9]*) | ||||||
|  |       _err "Couldn't add ACME Challenge TXT record to zone." | ||||||
|  |       return 1 | ||||||
|  |       ;; | ||||||
|  |     *) _info "Added ACME Challenge TXT record to zone." ;; | ||||||
|  |   esac | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | _ISPC_rmTxt() { | ||||||
|  |   # Need to get the record ID. | ||||||
|  |   curData="{\"session_id\":\"${sessionID}\",\"primary_id\":{\"name\":\"${fulldomain}.\",\"type\":\"TXT\"}}" | ||||||
|  |   curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_get")" | ||||||
|  |   _debug "Calling _ISPC_rmTxt: '${curData}' '${ISPC_Api}?dns_txt_get'" | ||||||
|  |   _debug "Result of _ISPC_rmTxt: '$curResult'" | ||||||
|  |   if _contains "${curResult}" '"code":"ok"'; then | ||||||
|  |     record_id=$(echo "${curResult}" | _egrep_o "\"id.*" | cut -d ':' -f 2 | cut -d '"' -f 2) | ||||||
|  |     _debug "Record ID: '${record_id}'" | ||||||
|  |     case "${record_id}" in | ||||||
|  |       '' | *[!0-9]*) | ||||||
|  |         _err "Record ID is not numeric." | ||||||
|  |         return 1 | ||||||
|  |         ;; | ||||||
|  |       *) | ||||||
|  |         unset IFS | ||||||
|  |         _info "Retrieved Record ID." | ||||||
|  |         curData="{\"session_id\":\"${sessionID}\",\"primary_id\":\"${record_id}\"}" | ||||||
|  |         curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_delete")" | ||||||
|  |         _debug "Calling _ISPC_rmTxt: '${curData}' '${ISPC_Api}?dns_txt_delete'" | ||||||
|  |         _debug "Result of _ISPC_rmTxt: '$curResult'" | ||||||
|  |         if _contains "${curResult}" '"code":"ok"'; then | ||||||
|  |           _info "Removed ACME Challenge TXT record from zone." | ||||||
|  |         else | ||||||
|  |           _err "Couldn't remove ACME Challenge TXT record from zone." | ||||||
|  |           return 1 | ||||||
|  |         fi | ||||||
|  |         ;; | ||||||
|  |     esac | ||||||
|  |   fi | ||||||
|  | } | ||||||
| @ -2,7 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| # dns api wrapper of lexicon for acme.sh | # dns api wrapper of lexicon for acme.sh | ||||||
| 
 | 
 | ||||||
| lexicon_url="https://github.com/AnalogJ/lexicon" | # https://github.com/AnalogJ/lexicon | ||||||
| lexicon_cmd="lexicon" | lexicon_cmd="lexicon" | ||||||
| 
 | 
 | ||||||
| wiki="https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api" | wiki="https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api" | ||||||
| @ -30,7 +30,9 @@ dns_lexicon_add() { | |||||||
|   _savedomainconf PROVIDER "$PROVIDER" |   _savedomainconf PROVIDER "$PROVIDER" | ||||||
|   export PROVIDER |   export PROVIDER | ||||||
| 
 | 
 | ||||||
|   Lx_name=$(echo LEXICON_"${PROVIDER}"_USERNAME | tr '[a-z]' '[A-Z]') |   # e.g. busybox-ash does not know [:upper:] | ||||||
|  |   # shellcheck disable=SC2018,SC2019 | ||||||
|  |   Lx_name=$(echo LEXICON_"${PROVIDER}"_USERNAME | tr 'a-z' 'A-Z') | ||||||
|   Lx_name_v=$(eval echo \$"$Lx_name") |   Lx_name_v=$(eval echo \$"$Lx_name") | ||||||
|   _debug "$Lx_name" "$Lx_name_v" |   _debug "$Lx_name" "$Lx_name_v" | ||||||
|   if [ "$Lx_name_v" ]; then |   if [ "$Lx_name_v" ]; then | ||||||
| @ -38,7 +40,8 @@ dns_lexicon_add() { | |||||||
|     eval export "$Lx_name" |     eval export "$Lx_name" | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   Lx_token=$(echo LEXICON_"${PROVIDER}"_TOKEN | tr '[a-z]' '[A-Z]') |   # shellcheck disable=SC2018,SC2019 | ||||||
|  |   Lx_token=$(echo LEXICON_"${PROVIDER}"_TOKEN | tr 'a-z' 'A-Z') | ||||||
|   Lx_token_v=$(eval echo \$"$Lx_token") |   Lx_token_v=$(eval echo \$"$Lx_token") | ||||||
|   _debug "$Lx_token" "$Lx_token_v" |   _debug "$Lx_token" "$Lx_token_v" | ||||||
|   if [ "$Lx_token_v" ]; then |   if [ "$Lx_token_v" ]; then | ||||||
| @ -46,7 +49,8 @@ dns_lexicon_add() { | |||||||
|     eval export "$Lx_token" |     eval export "$Lx_token" | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   Lx_password=$(echo LEXICON_"${PROVIDER}"_PASSWORD | tr '[a-z]' '[A-Z]') |   # shellcheck disable=SC2018,SC2019 | ||||||
|  |   Lx_password=$(echo LEXICON_"${PROVIDER}"_PASSWORD | tr 'a-z' 'A-Z') | ||||||
|   Lx_password_v=$(eval echo \$"$Lx_password") |   Lx_password_v=$(eval echo \$"$Lx_password") | ||||||
|   _debug "$Lx_password" "$Lx_password_v" |   _debug "$Lx_password" "$Lx_password_v" | ||||||
|   if [ "$Lx_password_v" ]; then |   if [ "$Lx_password_v" ]; then | ||||||
| @ -54,7 +58,8 @@ dns_lexicon_add() { | |||||||
|     eval export "$Lx_password" |     eval export "$Lx_password" | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   Lx_domaintoken=$(echo LEXICON_"${PROVIDER}"_DOMAINTOKEN | tr '[a-z]' '[A-Z]') |   # shellcheck disable=SC2018,SC2019 | ||||||
|  |   Lx_domaintoken=$(echo LEXICON_"${PROVIDER}"_DOMAINTOKEN | tr 'a-z' 'A-Z') | ||||||
|   Lx_domaintoken_v=$(eval echo \$"$Lx_domaintoken") |   Lx_domaintoken_v=$(eval echo \$"$Lx_domaintoken") | ||||||
|   _debug "$Lx_domaintoken" "$Lx_domaintoken_v" |   _debug "$Lx_domaintoken" "$Lx_domaintoken_v" | ||||||
|   if [ "$Lx_domaintoken_v" ]; then |   if [ "$Lx_domaintoken_v" ]; then | ||||||
|  | |||||||
							
								
								
									
										183
									
								
								dnsapi/dns_linode.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										183
									
								
								dnsapi/dns_linode.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,183 @@ | |||||||
|  | #!/usr/bin/env sh | ||||||
|  | 
 | ||||||
|  | #Author: Philipp Grosswiler <philipp.grosswiler@swiss-design.net> | ||||||
|  | 
 | ||||||
|  | LINODE_API_URL="https://api.linode.com/?api_key=$LINODE_API_KEY&api_action=" | ||||||
|  | 
 | ||||||
|  | ########  Public functions ##################### | ||||||
|  | 
 | ||||||
|  | #Usage: dns_linode_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" | ||||||
|  | dns_linode_add() { | ||||||
|  |   fulldomain="${1}" | ||||||
|  |   txtvalue="${2}" | ||||||
|  | 
 | ||||||
|  |   if ! _Linode_API; then | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _info "Using Linode" | ||||||
|  |   _debug "Calling: dns_linode_add() '${fulldomain}' '${txtvalue}'" | ||||||
|  | 
 | ||||||
|  |   _debug "First detect the root zone" | ||||||
|  |   if ! _get_root "$fulldomain"; then | ||||||
|  |     _err "Domain does not exist." | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  |   _debug _domain_id "$_domain_id" | ||||||
|  |   _debug _sub_domain "$_sub_domain" | ||||||
|  |   _debug _domain "$_domain" | ||||||
|  | 
 | ||||||
|  |   _parameters="&DomainID=$_domain_id&Type=TXT&Name=$_sub_domain&Target=$txtvalue" | ||||||
|  | 
 | ||||||
|  |   if _rest GET "domain.resource.create" "$_parameters" && [ -n "$response" ]; then | ||||||
|  |     _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"ResourceID\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1) | ||||||
|  |     _debug _resource_id "$_resource_id" | ||||||
|  | 
 | ||||||
|  |     if [ -z "$_resource_id" ]; then | ||||||
|  |       _err "Error adding the domain resource." | ||||||
|  |       return 1 | ||||||
|  |     fi | ||||||
|  | 
 | ||||||
|  |     _info "Domain resource successfully added." | ||||||
|  |     return 0 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   return 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #Usage: dns_linode_rm   _acme-challenge.www.domain.com | ||||||
|  | dns_linode_rm() { | ||||||
|  |   fulldomain="${1}" | ||||||
|  | 
 | ||||||
|  |   if ! _Linode_API; then | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _info "Using Linode" | ||||||
|  |   _debug "Calling: dns_linode_rm() '${fulldomain}'" | ||||||
|  | 
 | ||||||
|  |   _debug "First detect the root zone" | ||||||
|  |   if ! _get_root "$fulldomain"; then | ||||||
|  |     _err "Domain does not exist." | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  |   _debug _domain_id "$_domain_id" | ||||||
|  |   _debug _sub_domain "$_sub_domain" | ||||||
|  |   _debug _domain "$_domain" | ||||||
|  | 
 | ||||||
|  |   _parameters="&DomainID=$_domain_id" | ||||||
|  | 
 | ||||||
|  |   if _rest GET "domain.resource.list" "$_parameters" && [ -n "$response" ]; then | ||||||
|  |     response="$(echo "$response" | tr -d "\n" | sed 's/{/\n&/g')" | ||||||
|  | 
 | ||||||
|  |     resource="$(echo "$response" | _egrep_o "{.*\"NAME\":\s*\"$_sub_domain\".*}")" | ||||||
|  |     if [ "$resource" ]; then | ||||||
|  |       _resource_id=$(printf "%s\n" "$resource" | _egrep_o "\"RESOURCEID\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) | ||||||
|  |       if [ "$_resource_id" ]; then | ||||||
|  |         _debug _resource_id "$_resource_id" | ||||||
|  | 
 | ||||||
|  |         _parameters="&DomainID=$_domain_id&ResourceID=$_resource_id" | ||||||
|  | 
 | ||||||
|  |         if _rest GET "domain.resource.delete" "$_parameters" && [ -n "$response" ]; then | ||||||
|  |           _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"ResourceID\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1) | ||||||
|  |           _debug _resource_id "$_resource_id" | ||||||
|  | 
 | ||||||
|  |           if [ -z "$_resource_id" ]; then | ||||||
|  |             _err "Error deleting the domain resource." | ||||||
|  |             return 1 | ||||||
|  |           fi | ||||||
|  | 
 | ||||||
|  |           _info "Domain resource successfully deleted." | ||||||
|  |           return 0 | ||||||
|  |         fi | ||||||
|  |       fi | ||||||
|  | 
 | ||||||
|  |       return 1 | ||||||
|  |     fi | ||||||
|  | 
 | ||||||
|  |     return 0 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   return 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ####################  Private functions below ################################## | ||||||
|  | 
 | ||||||
|  | _Linode_API() { | ||||||
|  |   if [ -z "$LINODE_API_KEY" ]; then | ||||||
|  |     LINODE_API_KEY="" | ||||||
|  | 
 | ||||||
|  |     _err "You didn't specify the Linode API key yet." | ||||||
|  |     _err "Please create your key and try again." | ||||||
|  | 
 | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   _saveaccountconf LINODE_API_KEY "$LINODE_API_KEY" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ####################  Private functions below ################################## | ||||||
|  | #_acme-challenge.www.domain.com | ||||||
|  | #returns | ||||||
|  | # _sub_domain=_acme-challenge.www | ||||||
|  | # _domain=domain.com | ||||||
|  | # _domain_id=12345 | ||||||
|  | _get_root() { | ||||||
|  |   domain=$1 | ||||||
|  |   i=2 | ||||||
|  |   p=1 | ||||||
|  | 
 | ||||||
|  |   if _rest GET "domain.list"; then | ||||||
|  |     response="$(echo "$response" | tr -d "\n" | sed 's/{/\n&/g')" | ||||||
|  |     while true; do | ||||||
|  |       h=$(printf "%s" "$domain" | cut -d . -f $i-100) | ||||||
|  |       _debug h "$h" | ||||||
|  |       if [ -z "$h" ]; then | ||||||
|  |         #not valid | ||||||
|  |         return 1 | ||||||
|  |       fi | ||||||
|  | 
 | ||||||
|  |       hostedzone="$(echo "$response" | _egrep_o "{.*\"DOMAIN\":\s*\"$h\".*}")" | ||||||
|  |       if [ "$hostedzone" ]; then | ||||||
|  |         _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"DOMAINID\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) | ||||||
|  |         if [ "$_domain_id" ]; then | ||||||
|  |           _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) | ||||||
|  |           _domain=$h | ||||||
|  |           return 0 | ||||||
|  |         fi | ||||||
|  |         return 1 | ||||||
|  |       fi | ||||||
|  |       p=$i | ||||||
|  |       i=$(_math "$i" + 1) | ||||||
|  |     done | ||||||
|  |   fi | ||||||
|  |   return 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #method method action data | ||||||
|  | _rest() { | ||||||
|  |   mtd="$1" | ||||||
|  |   ep="$2" | ||||||
|  |   data="$3" | ||||||
|  | 
 | ||||||
|  |   _debug mtd "$mtd" | ||||||
|  |   _debug ep "$ep" | ||||||
|  | 
 | ||||||
|  |   export _H1="Accept: application/json" | ||||||
|  |   export _H2="Content-Type: application/json" | ||||||
|  | 
 | ||||||
|  |   if [ "$mtd" != "GET" ]; then | ||||||
|  |     # both POST and DELETE. | ||||||
|  |     _debug data "$data" | ||||||
|  |     response="$(_post "$data" "$LINODE_API_URL$ep" "" "$mtd")" | ||||||
|  |   else | ||||||
|  |     response="$(_get "$LINODE_API_URL$ep$data")" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   if [ "$?" != "0" ]; then | ||||||
|  |     _err "error $ep" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  |   _debug2 response "$response" | ||||||
|  |   return 0 | ||||||
|  | } | ||||||
| @ -46,12 +46,12 @@ dns_lua_add() { | |||||||
|     return 1 |     return 1 | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$fulldomain\"" | wc -l) |   count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$fulldomain.\",\"type\":\"TXT\"" | wc -l | tr -d " ") | ||||||
|   _debug count "$count" |   _debug count "$count" | ||||||
|   if [ "$count" = "0" ]; then |   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 printf -- "%s" "$response" | grep "$fulldomain" >/dev/null; then |       if _contains "$response" "$fulldomain"; then | ||||||
|         _info "Added" |         _info "Added" | ||||||
|         #todo: check if the record takes effect |         #todo: check if the record takes effect | ||||||
|         return 0 |         return 0 | ||||||
| @ -63,11 +63,11 @@ dns_lua_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 "\"id\":[^,]*,\"name\":\"$fulldomain.\",\"type\":\"TXT\"" | cut -d: -f2 | cut -d, -f1) |     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" |     _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}" |     _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" ]; then |     if [ "$?" = "0" ] && _contains "$response" "updated_at"; then | ||||||
|       _info "Updated!" |       _info "Updated!" | ||||||
|       #todo: check if the record takes effect |       #todo: check if the record takes effect | ||||||
|       return 0 |       return 0 | ||||||
| @ -84,7 +84,7 @@ dns_lua_rm() { | |||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ####################  Private functions bellow ################################## | ####################  Private functions below ################################## | ||||||
| #_acme-challenge.www.domain.com | #_acme-challenge.www.domain.com | ||||||
| #returns | #returns | ||||||
| # _sub_domain=_acme-challenge.www | # _sub_domain=_acme-challenge.www | ||||||
| @ -99,6 +99,7 @@ _get_root() { | |||||||
|   fi |   fi | ||||||
|   while true; do |   while true; do | ||||||
|     h=$(printf "%s" "$domain" | cut -d . -f $i-100) |     h=$(printf "%s" "$domain" | cut -d . -f $i-100) | ||||||
|  |     _debug h "$h" | ||||||
|     if [ -z "$h" ]; then |     if [ -z "$h" ]; then | ||||||
|       #not valid |       #not valid | ||||||
|       return 1 |       return 1 | ||||||
| @ -106,6 +107,7 @@ _get_root() { | |||||||
| 
 | 
 | ||||||
|     if _contains "$response" "\"name\":\"$h\""; then |     if _contains "$response" "\"name\":\"$h\""; then | ||||||
|       _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*,\"name\":\"$h\"" | cut -d : -f 2 | cut -d , -f 1) |       _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*,\"name\":\"$h\"" | cut -d : -f 2 | cut -d , -f 1) | ||||||
|  |       _debug _domain_id "$_domain_id" | ||||||
|       if [ "$_domain_id" ]; then |       if [ "$_domain_id" ]; then | ||||||
|         _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) |         _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) | ||||||
|         _domain="$h" |         _domain="$h" | ||||||
| @ -125,8 +127,8 @@ _LUA_rest() { | |||||||
|   data="$3" |   data="$3" | ||||||
|   _debug "$ep" |   _debug "$ep" | ||||||
| 
 | 
 | ||||||
|   _H1="Accept: application/json" |   export _H1="Accept: application/json" | ||||||
|   _H2="Authorization: Basic $LUA_auth" |   export _H2="Authorization: Basic $LUA_auth" | ||||||
|   if [ "$data" ]; then |   if [ "$data" ]; then | ||||||
|     _debug data "$data" |     _debug data "$data" | ||||||
|     response="$(_post "$data" "$LUA_Api/$ep" "" "$m")" |     response="$(_post "$data" "$LUA_Api/$ep" "" "$m")" | ||||||
|  | |||||||
| @ -81,7 +81,7 @@ dns_me_rm() { | |||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ####################  Private functions bellow ################################## | ####################  Private functions below ################################## | ||||||
| #_acme-challenge.www.domain.com | #_acme-challenge.www.domain.com | ||||||
| #returns | #returns | ||||||
| # _sub_domain=_acme-challenge.www | # _sub_domain=_acme-challenge.www | ||||||
| @ -103,7 +103,7 @@ _get_root() { | |||||||
|     fi |     fi | ||||||
| 
 | 
 | ||||||
|     if _contains "$response" "\"name\":\"$h\""; then |     if _contains "$response" "\"name\":\"$h\""; then | ||||||
|       _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*" | head -n 1 | cut -d : -f 2) |       _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*" | head -n 1 | cut -d : -f 2 | tr -d '}') | ||||||
|       if [ "$_domain_id" ]; then |       if [ "$_domain_id" ]; then | ||||||
|         _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) |         _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) | ||||||
|         _domain="$h" |         _domain="$h" | ||||||
| @ -124,11 +124,11 @@ _me_rest() { | |||||||
|   _debug "$ep" |   _debug "$ep" | ||||||
| 
 | 
 | ||||||
|   cdate=$(date -u +"%a, %d %b %Y %T %Z") |   cdate=$(date -u +"%a, %d %b %Y %T %Z") | ||||||
|   hmac=$(printf "%s" "$cdate" | _hmac sha1 "$ME_Secret" 1) |   hmac=$(printf "%s" "$cdate" | _hmac sha1 "$(printf "%s" "$ME_Secret" | _hex_dump | tr -d " ")" hex) | ||||||
| 
 | 
 | ||||||
|   _H1="x-dnsme-apiKey: $ME_Key" |   export _H1="x-dnsme-apiKey: $ME_Key" | ||||||
|   _H2="x-dnsme-requestDate: $cdate" |   export _H2="x-dnsme-requestDate: $cdate" | ||||||
|   _H3="x-dnsme-hmac: $hmac" |   export _H3="x-dnsme-hmac: $hmac" | ||||||
| 
 | 
 | ||||||
|   if [ "$data" ]; then |   if [ "$data" ]; then | ||||||
|     _debug data "$data" |     _debug data "$data" | ||||||
|  | |||||||
| @ -5,48 +5,31 @@ | |||||||
| #So, here must be a method   dns_myapi_add() | #So, here must be a method   dns_myapi_add() | ||||||
| #Which will be called by acme.sh to add the txt record to your api system. | #Which will be called by acme.sh to add the txt record to your api system. | ||||||
| #returns 0 means success, otherwise error. | #returns 0 means success, otherwise error. | ||||||
| 
 | # | ||||||
|  | #Author: Neilpang | ||||||
|  | #Report Bugs here: https://github.com/Neilpang/acme.sh | ||||||
|  | # | ||||||
| ########  Public functions ##################### | ########  Public functions ##################### | ||||||
| 
 | 
 | ||||||
| #Usage: dns_myapi_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" | #Usage: dns_myapi_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" | ||||||
| dns_myapi_add() { | dns_myapi_add() { | ||||||
|   fulldomain=$1 |   fulldomain=$1 | ||||||
|   txtvalue=$2 |   txtvalue=$2 | ||||||
|  |   _info "Using myapi" | ||||||
|  |   _debug fulldomain "$fulldomain" | ||||||
|  |   _debug txtvalue "$txtvalue" | ||||||
|   _err "Not implemented!" |   _err "Not implemented!" | ||||||
|   return 1 |   return 1 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #fulldomain | #Usage: fulldomain txtvalue | ||||||
|  | #Remove the txt record after validation. | ||||||
| dns_myapi_rm() { | dns_myapi_rm() { | ||||||
|   fulldomain=$1 |   fulldomain=$1 | ||||||
| 
 |   txtvalue=$2 | ||||||
|  |   _info "Using myapi" | ||||||
|  |   _debug fulldomain "$fulldomain" | ||||||
|  |   _debug txtvalue "$txtvalue" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ####################  Private functions bellow ################################## | ####################  Private functions below ################################## | ||||||
| _info() { |  | ||||||
|   if [ -z "$2" ]; then |  | ||||||
|     echo "[$(date)] $1" |  | ||||||
|   else |  | ||||||
|     echo "[$(date)] $1='$2'" |  | ||||||
|   fi |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| _err() { |  | ||||||
|   _info "$@" >&2 |  | ||||||
|   return 1 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| _debug() { |  | ||||||
|   if [ -z "$DEBUG" ]; then |  | ||||||
|     return |  | ||||||
|   fi |  | ||||||
|   _err "$@" |  | ||||||
|   return 0 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| _debug2() { |  | ||||||
|   if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then |  | ||||||
|     _debug "$@" |  | ||||||
|   fi |  | ||||||
|   return |  | ||||||
| } |  | ||||||
|  | |||||||
							
								
								
									
										58
									
								
								dnsapi/dns_nsupdate.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										58
									
								
								dnsapi/dns_nsupdate.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,58 @@ | |||||||
|  | #!/usr/bin/env sh | ||||||
|  | 
 | ||||||
|  | ########  Public functions ##################### | ||||||
|  | 
 | ||||||
|  | #Usage: dns_nsupdate_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" | ||||||
|  | dns_nsupdate_add() { | ||||||
|  |   fulldomain=$1 | ||||||
|  |   txtvalue=$2 | ||||||
|  |   _checkKeyFile || return 1 | ||||||
|  |   [ -n "${NSUPDATE_SERVER}" ] || NSUPDATE_SERVER="localhost" | ||||||
|  |   # save the dns server and key to the account conf file. | ||||||
|  |   _saveaccountconf NSUPDATE_SERVER "${NSUPDATE_SERVER}" | ||||||
|  |   _saveaccountconf NSUPDATE_KEY "${NSUPDATE_KEY}" | ||||||
|  |   _info "adding ${fulldomain}. 60 in txt \"${txtvalue}\"" | ||||||
|  |   nsupdate -k "${NSUPDATE_KEY}" <<EOF | ||||||
|  | server ${NSUPDATE_SERVER} | ||||||
|  | update add ${fulldomain}. 60 in txt "${txtvalue}" | ||||||
|  | send | ||||||
|  | EOF | ||||||
|  |   if [ $? -ne 0 ]; then | ||||||
|  |     _err "error updating domain" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #Usage: dns_nsupdate_rm   _acme-challenge.www.domain.com | ||||||
|  | dns_nsupdate_rm() { | ||||||
|  |   fulldomain=$1 | ||||||
|  |   _checkKeyFile || return 1 | ||||||
|  |   [ -n "${NSUPDATE_SERVER}" ] || NSUPDATE_SERVER="localhost" | ||||||
|  |   _info "removing ${fulldomain}. txt" | ||||||
|  |   nsupdate -k "${NSUPDATE_KEY}" <<EOF | ||||||
|  | server ${NSUPDATE_SERVER} | ||||||
|  | update delete ${fulldomain}. txt | ||||||
|  | send | ||||||
|  | EOF | ||||||
|  |   if [ $? -ne 0 ]; then | ||||||
|  |     _err "error updating domain" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ####################  Private functions below ################################## | ||||||
|  | 
 | ||||||
|  | _checkKeyFile() { | ||||||
|  |   if [ -z "${NSUPDATE_KEY}" ]; then | ||||||
|  |     _err "you must specify a path to the nsupdate key file" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  |   if [ ! -r "${NSUPDATE_KEY}" ]; then | ||||||
|  |     _err "key ${NSUPDATE_KEY} is unreadable" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | } | ||||||
| @ -182,7 +182,7 @@ dns_ovh_rm() { | |||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ####################  Private functions bellow ################################## | ####################  Private functions below ################################## | ||||||
| 
 | 
 | ||||||
| _ovh_authentication() { | _ovh_authentication() { | ||||||
| 
 | 
 | ||||||
| @ -273,12 +273,12 @@ _ovh_rest() { | |||||||
|   _ovh_hex="$(printf "%s" "$_ovh_p" | _digest sha1 hex)" |   _ovh_hex="$(printf "%s" "$_ovh_p" | _digest sha1 hex)" | ||||||
|   _debug2 _ovh_hex "$_ovh_hex" |   _debug2 _ovh_hex "$_ovh_hex" | ||||||
| 
 | 
 | ||||||
|   _H1="X-Ovh-Application: $OVH_AK" |   export _H1="X-Ovh-Application: $OVH_AK" | ||||||
|   _H2="X-Ovh-Signature: \$1\$$_ovh_hex" |   export _H2="X-Ovh-Signature: \$1\$$_ovh_hex" | ||||||
|   _debug2 _H2 "$_H2" |   _debug2 _H2 "$_H2" | ||||||
|   _H3="X-Ovh-Timestamp: $_ovh_t" |   export _H3="X-Ovh-Timestamp: $_ovh_t" | ||||||
|   _H4="X-Ovh-Consumer: $OVH_CK" |   export _H4="X-Ovh-Consumer: $OVH_CK" | ||||||
|   _H5="Content-Type: application/json;charset=utf-8" |   export _H5="Content-Type: application/json;charset=utf-8" | ||||||
|   if [ "$data" ] || [ "$m" = "POST" ] || [ "$m" = "PUT" ]; then |   if [ "$data" ] || [ "$m" = "POST" ] || [ "$m" = "PUT" ]; then | ||||||
|     _debug data "$data" |     _debug data "$data" | ||||||
|     response="$(_post "$data" "$_ovh_url" "" "$m")" |     response="$(_post "$data" "$_ovh_url" "" "$m")" | ||||||
|  | |||||||
| @ -12,6 +12,8 @@ DEFAULT_PDNS_TTL=60 | |||||||
| 
 | 
 | ||||||
| ########  Public functions ##################### | ########  Public functions ##################### | ||||||
| #Usage: add _acme-challenge.www.domain.com "123456789ABCDEF0000000000000000000000000000000000000" | #Usage: add _acme-challenge.www.domain.com "123456789ABCDEF0000000000000000000000000000000000000" | ||||||
|  | #fulldomain | ||||||
|  | #txtvalue | ||||||
| dns_pdns_add() { | dns_pdns_add() { | ||||||
|   fulldomain=$1 |   fulldomain=$1 | ||||||
|   txtvalue=$2 |   txtvalue=$2 | ||||||
| @ -50,7 +52,7 @@ dns_pdns_add() { | |||||||
|     _saveaccountconf PDNS_Ttl "$PDNS_Ttl" |     _saveaccountconf PDNS_Ttl "$PDNS_Ttl" | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   _debug "First detect the root zone" |   _debug "Detect root zone" | ||||||
|   if ! _get_root "$fulldomain"; then |   if ! _get_root "$fulldomain"; then | ||||||
|     _err "invalid domain" |     _err "invalid domain" | ||||||
|     return 1 |     return 1 | ||||||
| @ -68,6 +70,18 @@ dns_pdns_add() { | |||||||
| dns_pdns_rm() { | dns_pdns_rm() { | ||||||
|   fulldomain=$1 |   fulldomain=$1 | ||||||
| 
 | 
 | ||||||
|  |   _debug "Detect root zone" | ||||||
|  |   if ! _get_root "$fulldomain"; then | ||||||
|  |     _err "invalid domain" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  |   _debug _domain "$_domain" | ||||||
|  | 
 | ||||||
|  |   if ! rm_record "$_domain" "$fulldomain"; then | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   return 0 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| set_record() { | set_record() { | ||||||
| @ -76,18 +90,47 @@ set_record() { | |||||||
|   full=$2 |   full=$2 | ||||||
|   txtvalue=$3 |   txtvalue=$3 | ||||||
| 
 | 
 | ||||||
|   if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root." "{\"rrsets\": [{\"name\": \"$full.\", \"changetype\": \"REPLACE\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [{\"name\": \"$full.\", \"type\": \"TXT\", \"content\": \"\\\"$txtvalue\\\"\", \"disabled\": false, \"ttl\": $PDNS_Ttl}]}]}"; then |   if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root." "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [{\"name\": \"$full.\", \"type\": \"TXT\", \"content\": \"\\\"$txtvalue\\\"\", \"disabled\": false, \"ttl\": $PDNS_Ttl}]}]}"; then | ||||||
|     _err "Set txt record error." |     _err "Set txt record error." | ||||||
|     return 1 |     return 1 | ||||||
|   fi |   fi | ||||||
|   if ! _pdns_rest "PUT" "/api/v1/servers/$PDNS_ServerId/zones/$root./notify"; then | 
 | ||||||
|     _err "Notify servers error." |   if ! notify_slaves "$root"; then | ||||||
|     return 1 |     return 1 | ||||||
|   fi |   fi | ||||||
|  | 
 | ||||||
|   return 0 |   return 0 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ####################  Private functions bellow ################################## | rm_record() { | ||||||
|  |   _info "Remove record" | ||||||
|  |   root=$1 | ||||||
|  |   full=$2 | ||||||
|  | 
 | ||||||
|  |   if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root." "{\"rrsets\": [{\"changetype\": \"DELETE\", \"name\": \"$full.\", \"type\": \"TXT\"}]}"; then | ||||||
|  |     _err "Delete txt record error." | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   if ! notify_slaves "$root"; then | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | notify_slaves() { | ||||||
|  |   root=$1 | ||||||
|  | 
 | ||||||
|  |   if ! _pdns_rest "PUT" "/api/v1/servers/$PDNS_ServerId/zones/$root./notify"; then | ||||||
|  |     _err "Notify slaves error." | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ####################  Private functions below ################################## | ||||||
| #_acme-challenge.www.domain.com | #_acme-challenge.www.domain.com | ||||||
| #returns | #returns | ||||||
| # _domain=domain.com | # _domain=domain.com | ||||||
| @ -113,6 +156,7 @@ _get_root() { | |||||||
|     i=$(_math $i + 1) |     i=$(_math $i + 1) | ||||||
|   done |   done | ||||||
|   _debug "$domain not found" |   _debug "$domain not found" | ||||||
|  | 
 | ||||||
|   return 1 |   return 1 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -121,7 +165,7 @@ _pdns_rest() { | |||||||
|   ep=$2 |   ep=$2 | ||||||
|   data=$3 |   data=$3 | ||||||
| 
 | 
 | ||||||
|   _H1="X-API-Key: $PDNS_Token" |   export _H1="X-API-Key: $PDNS_Token" | ||||||
| 
 | 
 | ||||||
|   if [ ! "$method" = "GET" ]; then |   if [ ! "$method" = "GET" ]; then | ||||||
|     _debug data "$data" |     _debug data "$data" | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user